Resulta que el principio de depuración subyacente de gdb es tan simple

I. Introducción

Este artículo hablará sobre el famoso BGF . No mencionemos su rica experiencia. Como su hermano GCC , nació con una llave de oro y su estatus en la familia GNU es inquebrantable. Creo que todos los ingenieros de desarrollo integrados han usado gdb para depurar programas. Si dices que no lo has usado, solo puede significar que tu experiencia de desarrollo no es lo suficientemente dura y necesitas seguir siendo superado por BUG.

Todos sabemos que al compilar con gcc, puede usar la opción -g para incrustar más información de depuración en el archivo ejecutable, entonces, ¿qué información de depuración está incrustada? ¿Cómo interactúa esta información de depuración con las instrucciones binarias? Al depurar, ¿cómo obtener la información de contexto en la pila de llamadas de función en la información de depuración?

En respuesta a las dudas anteriores, el hermano Dao usó dos artículos para describir a fondo los problemas más profundos en la parte inferior, para que pueda disfrutar viéndolos a la vez.

El primer artículo es el actual. El contenido principal es introducir el principio de depuración subyacente de GDB. Veamos qué mecanismo utiliza GDB para controlar el orden de ejecución del programa depurado.

En el segundo artículo, elegimos un lenguaje LUA compacto y bien equipado para analizar, desde el análisis del código fuente hasta la pila de llamadas de función, desde el conjunto de instrucciones hasta la modificación de la biblioteca de depuración , todo de una vez.

Hay más contenido y puede tomar más tiempo terminar de leer este artículo. Por su salud, no se recomienda leer este artículo mientras está en cuclillas.

Dos, modelo de depuración de GDB

La depuración de GDB incluye dos programas: el programa gdb y el programa depurado. Según si estos dos programas se ejecutan en la misma computadora, el modelo de depuración de GDB se puede dividir en dos tipos:

  1. Depuración local
  2. Depuración remota

Depuración local : el depurador y el programa depurado se ejecutan en la misma computadora .

Depuración remota : el programa de depuración se ejecuta en una computadora y el programa depurado se ejecuta en otra computadora .

El programa de depuración visual no es el punto, es solo un shell utilizado para encapsular GDB. Podemos usar la ventana del terminal oscuro para ingresar manualmente los comandos de depuración; también podemos elegir el entorno de desarrollo integrado (IDE), que tiene depuración incrustada en el IDE , de modo que podemos usar varios botones en lugar de ingresar manualmente los comandos de depuración.

En comparación con la depuración local, hay un programa GdbServer más en depuración remota. Tanto él como el programa de destino se ejecutan en la máquina de destino , que puede ser una computadora x86 o una placa ARM. La línea roja en la figura indica la comunicación entre GDB y GdbServer a través de la red o puerto serie. Dado que se trata de comunicación, se debe requerir un conjunto de protocolos de comunicación: protocolo RSP , el nombre completo es: GDB Remote Serial Protocol (protocolo de comunicación remota GDB).

Con respecto al formato y contenido específicos del protocolo de comunicación, no debemos preocuparnos, solo necesitamos saber: son todas cadenas , con un carácter de inicio fijo ('$') y un carácter de finalización ('#'), y dos dieciséis al final El carácter ASCII de la base se utiliza como suma de comprobación, y es suficiente saber tanto. En cuanto a más detalles, si puede echar un vistazo al XX inactivo, de hecho, estos acuerdos, como todo tipo de regulaciones extrañas en la sociedad, están pensados ​​por un montón de ladrillos en el inodoro.

En el segundo artículo que explica LUA, implementaremos un prototipo de depuración remota similar. El protocolo de comunicación también es una cadena, después de simplificar directamente el protocolo HTTP, se usa y es muy claro y conveniente.

Tres, instrucciones de depuración de GDB

En aras de la integridad, aquí hay algunos comandos de depuración de GDB publicados aquí, solo con conocimiento perceptivo.

Además, no todas las instrucciones se enumeran aquí. Todas las instrucciones enumeradas son de uso común y más fáciles de entender. Al explicar LUA, elegiremos algunas de las instrucciones para una comparación detallada, incluido el mecanismo de implementación subyacente.

Cada comando de depuración tiene muchas opciones de comando. Por ejemplo, los puntos de interrupción incluyen: establecer puntos de interrupción, eliminar puntos de interrupción, puntos de interrupción condicionales, deshabilitarlos y habilitarlos temporalmente . El objetivo de este artículo es comprender el mecanismo de depuración subyacente de gdb, por lo que el uso de estas instrucciones en la capa de aplicación ya no se enumera Hay muchos recursos en la red.

Cuarto, la relación entre GDB y el programa depurado

Para la conveniencia de la descripción, primero escriba el programa en C más simple :

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}

Comando de compilación:

$ gcc -g prueba.c -o prueba

Depuramos la prueba del programa ejecutable e ingresamos el comando:

$ gdb ./test

El resultado es el siguiente:

En la última línea, puede ver que el cursor está parpadeando Este es el programa gdb esperando que le enviemos comandos de depuración.

Cuando la ventana de terminal oscura anterior estaba ejecutando gdb ./test, sucedieron muchas cosas complicadas en el sistema operativo:

El sistema primero inicia el proceso gdb . Este proceso llama a la función del sistema fork () para crear un proceso hijo . Este proceso hijo hace dos cosas:

  1. Llame a la función del sistema ptrace (PTRACE_TRACEME, [otros parámetros]);
  2. La prueba del programa ejecutable se carga y ejecuta a través de execc, luego el programa de prueba comienza a ejecutarse en este subproceso.

Un punto adicional: a veces se hace referencia a él como un programa y a veces como un proceso en el texto. "Programa" describe un concepto estático , es decir, un grupo de datos que se encuentran en el disco duro, y "proceso" describe un proceso dinámico . Una vez que el programa se lee y se carga en la memoria, hay un bloque de control de tareas (un estructura de datos) se utiliza especialmente para gestionar este proceso.

Después de sentar las bases durante mucho tiempo, finalmente es el turno del protagonista de debutar, es decir, la función de llamada del sistema ptrace (los parámetros se explicarán más adelante) .Es con su ayuda que gdb tiene potentes capacidades de depuración. El prototipo de función es:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

Echemos un vistazo a la introducción a esta función en man:

Tracer es el programa de depuración, que puede entenderse como el programa gdb; tracee es el programa depurado, que corresponde a la prueba del programa de destino en la figura. A los extranjeros generalmente les gusta usar -er y -ee para expresar la relación activa y pasiva, por ejemplo, el empleado es el empleador (jefe) y el empleado es el empleado en apuros (trabajador que golpea).

La función del sistema ptrace es una llamada al sistema para el seguimiento de procesos proporcionada por el kernel de Linux. A través de ella, un proceso (gdb) puede leer y escribir el espacio de instrucciones, el espacio de datos, la pila y registrar los valores de otro proceso (prueba). Y el proceso gdb se hace cargo de todas las señales del proceso de prueba, lo que significa que todas las señales enviadas por el sistema al proceso de prueba son recibidas por el proceso gdb. De esta manera, la ejecución del proceso de prueba es controlada por gdb para lograr el propósito de la depuración.

En otras palabras, si no hay depuración de gdb, hay una interacción directa entre el sistema operativo y el proceso de destino; si se utiliza gdb para depurar el programa, la señal enviada por el sistema operativo al proceso de destino será interceptada por gdb , y gdb determinarán de acuerdo con los atributos de la señal: Al continuar ejecutando el programa de destino, si transferir la señal actualmente interceptada al programa de destino, de esta manera, el programa de destino realizará las acciones correspondientes bajo el comando del señal enviada por gdb.

Cinco, cómo GDB depura el proceso de servicio que se ha ejecutado

¿Hay algún socio pequeño que plantee esta pregunta? La prueba del programa depurado anterior se ejecuta desde el principio, ¿puede usar gdb para depurar un proceso de servicio que ya está en ejecución? La respuesta es sí. Se trata del primer parámetro de la función del sistema ptrace, este parámetro es un valor enumerado, de los cuales dos son importantes: PTRACE_TRACEME y PTRACE_ATTACH < .

En la explicación anterior, el parámetro utilizado por el proceso hijo para llamar a la función del sistema ptrace es PTRACE_TRACEME . Preste atención al texto naranja: el proceso hijo llama a ptrace, que es equivalente al proceso hijo que le dice al sistema operativo: el proceso gdb es mi papá. Si quieres enviarme una señal, por favor envíala directamente al proceso de gdb!

Si desea depurar un proceso B ya ejecutado , debe llamar a ptrace ( PTRACE_ATTACH , [otros parámetros]) en el proceso padre de gdb. En este momento, el proceso gdb se adjuntará (vinculará) al proceso ejecutado B , gdb adopta proceso B como su propio proceso hijo , y el comportamiento del proceso hijo B es equivalente a una operación PTRACE_TRACEME. En este momento, el proceso gdb enviará la señal SIGSTO al proceso hijo B. Después de que el proceso hijo B reciba la señal SIGSTOP, suspenderá la ejecución y entrará en el estado TASK_STOPED, lo que indica que está listo para ser depurado.

Por lo tanto, ya sea que esté depurando un programa nuevo o un programa de servicio que ya está en ejecución, a través de la llamada al sistema ptrace, el resultado final es: el programa gdb es el proceso padre, el programa depurado es el proceso hijo y todas las señales del proceso hijo Todos son asumidas por el BGF proceso principal, y el proceso de gDB padre puede ver y modificar la información interna del proceso hijo, incluyendo pilas, registros, y así sucesivamente .

Con respecto a la vinculación, hay varias restricciones que deben entenderse: no se permite la vinculación automática, no se permiten vinculaciones múltiples al mismo proceso y no se permite vincular el proceso No. 1.

Seis, espiando cómo GDB implementa instrucciones de puntos de interrupción

El principio terminó, aquí establecemos una instrucción de depuración de punto de interrupción (break) , para echar un vistazo al mecanismo de depuración interno de gdb.
Aún así, tome el código anterior como ejemplo y vuelva a publicar el código aquí:

#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = a + b;
    printf("c = %d \n", c);
    return 0;
}

Echemos un vistazo a cómo se ve el código de desmontaje compilado, la instrucción de compilación:

gcc -S test.c; prueba de gato. S)

Aquí solo se publica una parte del código de desmontaje, siempre que se pueda explicar el principio subyacente, se logrará nuestro objetivo.

Como se mencionó anteriormente, después de ejecutar gdb ./test, gdb bifurcará un proceso hijo. Este proceso hijo primero llama a ptrace y luego ejecuta el programa de prueba, para que el entorno de depuración esté listo.

Juntamos el código fuente y el código ensamblador para una fácil comprensión:

Ingrese el comando de punto de interrupción "break 5" en la ventana de depuración, y gdb hace dos cosas en este momento:

  1. La décima línea de código ensamblador correspondiente a la quinta línea de código fuente se almacena en la lista enlazada de puntos de interrupción .
  2. En la décima línea del código ensamblador, inserte la instrucción de interrupción INT3, lo que significa que la décima línea del código ensamblador se reemplaza por INT3 .

Luego, continúe ingresando el comando de ejecución "ejecutar" en la ventana de depuración ( ejecutar hasta que llegue a un punto de interrupción y pausar ). Cuando el puntero de la PC (un puntero interno que apunta a la línea de código que se ejecutará) en el código de ensamblaje es ejecutado en la línea 10, se encuentra Es una instrucción INT3, por lo que el sistema operativo envía una señal SIGTRAP al proceso de prueba.

En este momento, se ha ejecutado la décima línea del código ensamblador y el puntero de la PC apunta a la undécima línea.

Como se mencionó anteriormente, cualquier señal enviada por el sistema operativo para probar es asumida por gdb, lo que significa que gdb recibirá la señal SIGTRAP primero, y gdb encuentra que el código ensamblador actual está ejecutando la línea 10, por lo que va a la lista de puntos de interrupción. En la búsqueda, se encuentra que la décima línea de código se almacena en la lista vinculada, lo que indica que se establece un punto de interrupción en la décima línea. Entonces gdb hizo dos operaciones más:

  1. Reemplace la décima línea "INT3" en el código ensamblador con el código original en la lista de puntos de interrupción.
  2. Retroceda un paso el puntero de la PC, es decir, ajústelo a la línea 10.

Luego, gdb continúa esperando las instrucciones de depuración del usuario.

En este momento, es equivalente a que la siguiente instrucción ejecutada sea la décima línea del código ensamblador , que es la quinta línea del código fuente . Desde el punto de vista de nuestro depurador, el programa que se está depurando se pausa en el punto de interrupción en la línea 5. En este punto, podemos continuar ingresando otros comandos de depuración para depurar, como: ver valores de variables, ver información de pila, modificar valores de variables locales , etc. Espere.

Siete, observando cómo GDB implementa la instrucción de un solo paso a continuación

Tome el código fuente y el código ensamblador ahora como ejemplo, asumiendo que el programa se detiene en la sexta línea del código fuente, es decir, la undécima línea del código ensamblador:

Ingrese el comando de ejecución de un solo paso a continuación en la ventana de depuración . Nuestro objetivo es ejecutar una línea de código , es decir, terminar de ejecutar la sexta línea del código fuente y luego detenernos en la séptima línea . Cuando gdb recibe la siguiente ejecución, calculará la séptima línea del código fuente, que debe corresponder a la 14a línea del código ensamblador , por lo que gdb controla el puntero de la PC en el código ensamblador para ejecutar hasta el final de la decimotercera línea , es decir, la PC apunta a las primeras 14 líneas cuando se detiene y luego continúa esperando los comandos de depuración introducidos por el usuario.

8. Resumen

A través de las dos instrucciones de depuración de break y next, hemos entendido cómo se manejan las instrucciones de depuración en gdb. Por supuesto, hay muchas más instrucciones de depuración en gdb, incluida la adquisición más complicada de información de la pila, la modificación de los valores de las variables, etc. Los amigos interesados ​​pueden continuar siguiéndolos en profundidad.

Cuando escriba la biblioteca de depuración en el lenguaje LUA más adelante, discutiré este tema con más profundidad y detalle Después de todo, el lenguaje LUA es más pequeño y más simple. También mostraré la parte del código de cómo configurar el puntero de la PC en el código LUA, para que tengamos una mejor comprensión y comprensión de la implementación interna de un lenguaje de programación, y también podamos grabar un video, para que podamos mejorar explicar los detalles internos del lenguaje LUA.


Si este artículo puede brindarle un poco de ayuda, bienvenido a comentar, reenviar y compartir con sus amigos.

Continuaré resumiendo la experiencia de combate real en el proceso de desarrollo de proyectos integrados en IOT Internet of Things Town de número público público , ¡creo que no te decepcionará!

Supongo que te gusta

Origin blog.csdn.net/u012296253/article/details/111150497
Recomendado
Clasificación