¡Domine la herramienta de depuración de GDB y solucione errores fácilmente!

1. ¿Qué es el BGF?

gdb es la abreviatura de GNU debugger, que es una herramienta de depuración de programación.

  • Sitio web oficial del BGF: https://www.gnu.org/software/gdb/
  • Lenguajes de programación aplicables para GDB: Ada/C/C++/objetive-c/Pascal etc.
  • Cómo funciona GDB: depuración local y depuración remota.

La última versión del lanzamiento actual es 8.0 y GDB puede ejecutarse en los sistemas operativos Linux y Windows.

1.1 Instalar e iniciar GDB

  1. gdb -v verifica si la instalación es exitosa, si no, instálelo (debe asegurarse de que el compilador esté instalado, como gcc).

  2. iniciar gdb

    1. gdb test_file.exe para iniciar la depuración de gdb, es decir, especificar directamente el nombre del archivo ejecutable que se depurará

    2. Ingrese gdb directamente para comenzar y use el archivo de comando test_file.exe para especificar el nombre del archivo después de ingresar gdb

    3. Si el archivo de ejecución de destino requiere parámetros de entrada y salida (como parámetros de recepción argv[]), los parámetros se pueden especificar de tres maneras:

      1. Al iniciar gdb, gdb --args text_file.exe
      2. Después de ingresar gdb, ejecute set args param_1
      3. Después de ingresar a la depuración de gdb, ejecute param_1 o inicie para_1

1.2 Funciones de gdb

  • Inicie el programa, puede ejecutar el programa como desee de acuerdo con los requisitos definidos por el usuario.
  • Permite que el programa que se está depurando se detenga en el punto de interrupción de depuración especificado por el usuario (el punto de interrupción puede ser una expresión condicional).
  • Cuando el programa se detiene, puede verificar lo que sucedió en el programa en ese momento. Por ejemplo, puede imprimir el valor de una variable.
  • Cambiar dinámicamente el entorno de ejecución del programa variable.

1.3 El uso de gdb

ejecuta el programa

run(r)运行程序,如果要加参数,则是run arg1 arg2 ... 

ver código fuente

list(l):查看最近十行源码
list fun:查看fun函数源代码
list file:fun:查看flie文件中的fun函数源代码

Configuración de puntos de interrupción y visualización de puntos de interrupción

break 行号/fun设置断点。
break file:行号/fun设置断点。
break if<condition>:条件成立时程序停住。
info break(缩写:i b):查看断点。
watch expr:一旦expr值发生改变,程序停住。
delete n:删除断点。

depuración de un solo paso

continue(c):运行至下一个断点。
step(s):单步跟踪,进入函数,类似于VC中的step in。
next(n):单步跟踪,不进入函数,类似于VC中的step out。
finish:运行程序,知道当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
until:当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序知道退出循环体。

Ver datos de tiempo de ejecución

print(p):查看运行时的变量以及表达式。
ptype:查看类型。
print array:打印数组所有元素。
print *array@len:查看动态内存。len是查看数组array的元素个数。
print x=5:改变运行时数据。

1.4 Errores de programa

  • Error de compilación: Al escribir el programa, no se cumple con la especificación del idioma, lo que genera un error de compilación. Por ejemplo: errores gramaticales.
  • Error de tiempo de ejecución: el compilador no puede detectar este tipo de error, pero puede provocar que el programa se bloquee durante el tiempo de ejecución. Por ejemplo: dirección de memoria acceso ilegal.
  • Errores lógicos: compilar y ejecutar está bien, pero el programa no hace lo que esperamos que haga.

Error de segmento de depuración de 1,5 gdb

¿Qué es una falla de segmento? La falla de segmentación es un error causado por acceder a una dirección ilegal.

  • Acceda al área de datos del sistema, especialmente escriba datos en la dirección de memoria protegida por el sistema. Por ejemplo: acceder a la dirección cuya dirección es 0.
  • Memoria fuera de límites (matriz fuera de límites, inconsistencia de tipo de variable, etc.) accede a un área de memoria que no pertenece al programa actual.

Gdb depura fallas de segmento, puede ejecutar el programa directamente.Cuando el programa falla, gdb imprimirá la información en ejecución, por ejemplo: después de recibir la señal SIGSEGV, puede usar el comando para imprimir la información de seguimiento de la pila y luego modificar el programa según el código de error del btprograma.

Depuración de archivos 1.6.core

6.1 archivo central

Cuando un programa falla, generalmente se genera un archivo llamado corearchivo. El archivo central registra la imagen de la memoria cuando el programa falla y agrega información de depuración.El proceso de generación del archivo central se llama core dump(核心已转储). El sistema no genera este archivo por defecto.

6.2 Configurar para generar un archivo central

  • ulimit -c: compruebe el estado del volcado del núcleo.
  • ulimit -c xxxx: establece el tamaño del archivo principal.
  • ulimit -c unlimited: Tamaño ilimitado del archivo central.

Archivo central de depuración 6.3 gdb

ulimit -c xxxxDespués de configurar , vuelva a ejecutar el programa y se producirá un error de segmento. En este momento, corese generará un archivo, utilice gdb coreel archivo principal de depuración y utilice btel comando para imprimir la información de seguimiento de la pila.

Dos, comandos comunes de GDB

  • Lo siguiente usa test_file.c como el nombre del ejemplo del programa fuente, test_file.exe como el nombre del ejemplo del archivo ejecutable y param_1 como el nombre del ejemplo del parámetro.
  • (gdb) significa ejecutarse en modo de depuración gdb
  • En general, hay dos métodos de uso común, es decir, la depuración de punto de interrupción y la depuración de un solo paso .
  • list(l): código fuente de la lista
  • quit(q): salir del modo de depuración gdb
  • Después de ingresar gdb, ingrese ayuda para ver las instrucciones para todos los comandos

2.1 Ver código fuente

list [nombre de la función] [número de líneas]

2.2 Depuración de puntos de interrupción

(1) Establecer un punto de interrupción:

  • a. break + [número de línea del código fuente][nombre de la función del código fuente][dirección de memoria]
  • b. break... if condition... puede ser cualquiera de los parámetros anteriores, y condition es una condición. Por ejemplo, en el cuerpo del bucle, puede establecer break... if i = 100 para establecer el número de bucles

eliminar punto de interrupción

(gdb) ubicación clara: la ubicación del parámetro suele ser el número de línea de una determinada línea de código o el nombre de una función específica. Cuando el parámetro de ubicación es el nombre de función de una función, significa eliminar todos los puntos de interrupción en la entrada de la función.

(gdb) delete [puntos de interrupción] [num] : el parámetro de puntos de interrupción es opcional, y el parámetro num es el número del punto de interrupción especificado, que se puede eliminar para eliminar un determinado punto de interrupción, no todos.

deshabilitar punto de interrupción

**disable [puntos de interrupción] [num…]: **el parámetro de puntos de interrupción es opcional; num… significa que puede haber varios parámetros, y cada parámetro es el número del punto de interrupción que se deshabilitará. Si especifica num..., el comando deshabilitar deshabilitará el punto de interrupción con el número especificado; de lo contrario, si no establece num..., deshabilitar deshabilitará todos los puntos de interrupción en el programa actual.

activar punto de interrupción

  1. habilitar [puntos de interrupción] [num...] activar múltiples puntos de interrupción especificados con num... parámetros, si num... no está configurado, significa activar todos los puntos de interrupción deshabilitados
  2. habilitar [puntos de interrupción] una vez num... Activa temporalmente múltiples puntos de interrupción numerados por num..., pero el punto de interrupción solo se puede usar una vez, y luego volverá automáticamente al estado deshabilitado
  3. enable [breakpoints] count num... Activar temporalmente múltiples puntos de interrupción numerados por num..., los puntos de interrupción se pueden usar contar veces y luego ingresar al estado deshabilitado
  4. enable [breakpoints] delete num... Active múltiples puntos de interrupción numerados num..., pero los puntos de interrupción solo se pueden usar una vez y luego se eliminarán de forma permanente.

break(b): es un punto de interrupción común, y hay dos formas de puntos de interrupción

(gdb) ubicación de ruptura // b ubicación, la ubicación representa la ubicación del punto de ruptura

imagen

(gdb) break … if cond // b … if cond, lo que significa que si la condición de cond es verdadera, el punto de interrupción estará en “…”

Al usar el comando de condición para establecer expresiones condicionales para diferentes tipos de puntos de interrupción, solo cuando las expresiones condicionales son verdaderas (el valor es Verdadero), se activará el punto de interrupción correspondiente y el programa se suspenderá.

tbreak : El comando tbreak puede verse como otra versión del comando break. El uso y las funciones de los comandos tbreak y break son muy similares. La única diferencia es que el punto de interrupción alcanzado por el comando tbreak solo se usará una vez, incluso después el programa se suspende El punto de interrupción desaparecerá automáticamente.

rbreak : a diferencia de los comandos break y tbreak, el comando rbreak actúa sobre funciones en programas C y C++, y se interrumpirá al comienzo de la función especificada.

  • (gdb) tbreak expresión regular

    • regex representa una expresión regular que se rompe al comienzo de la función coincidente
  • El punto de interrupción establecido por el comando tbreak tiene el mismo efecto que el del comando break, siempre existirá y no desaparecerá automáticamente.

watch : este comando llega a un punto de interrupción de observación, que puede monitorear el valor de una variable o expresión. Solo cuando el valor de la variable monitoreada (expresión) cambie, el programa dejará de ejecutarse.

  • (gdb) ver cond

    • cond representa la variable o expresión a monitorear

Comando rwatch: siempre que haya una operación para leer el valor de la variable de destino (expresión) en el programa, el programa dejará de ejecutarse;

Comando awatch: siempre que haya una operación para leer el valor de la variable de destino (expresión) o cambiar el valor en el programa, el programa dejará de ejecutarse.

catch : la función de capturar un punto de interrupción es monitorear la ocurrencia de un determinado evento en el programa, como cuando ocurre una determinada excepción en el programa, cuando se carga una biblioteca dinámica, etc. Una vez que se alcanza el tiempo objetivo, el programa se detiene ejecutando

(2) Observe el punto de ruptura:

  • a.ver + [variable] [expresión] Detiene el programa cuando cambia el valor de la variable o expresión.
  • b.rwatch + [variable] [expresión] Cuando se lee la variable o expresión, detiene el programa.
  • c, awatch + [variable] [expresión] Cuando la variable o expresión se lee o escribe, detiene el programa.

(3) Establecer el punto de captura:

catch + event Cuando ocurre el evento, detiene el programa.

evento puede ser el siguiente:

  • a, throw Una excepción lanzada por C++. (lanzar es una palabra clave)
  • B. catch Una excepción detectada por C++. (atrapar es una palabra clave)
  • C. Cuando exec llame al sistema, llame a exec. (exec es una palabra clave, actualmente esta función solo está disponible en HP-UX)
  • d. Cuando fork llama al sistema para llamar a fork. (fork es una palabra clave, actualmente esta función solo está disponible en HP-UX)
  • e.Cuando vfork llama al sistema para llamar a vfork. (vfork es una palabra clave, actualmente esta función solo está disponible en HP-UX)
  • f, cargar o cargar Al cargar una biblioteca compartida (biblioteca de enlace dinámico). (cargar es una palabra clave, actualmente esta función solo está disponible en HP-UX)
  • g, descargar o descargar al descargar una biblioteca compartida (biblioteca de enlace dinámico). (descargar es una palabra clave, actualmente esta función solo está disponible en HP-UX)

(4) Señal de captura:

manejar + [argu] + señales

Señales: Es una señal definida por Linux/Unix, SIGINT significa una señal de carácter de interrupción, es decir, la señal de Ctrl+C, SIGBUS significa una señal de falla de hardware, SIGCHLD significa una señal para cambiar el estado de un proceso hijo; SIGKILL significa una señal para terminar la operación del programa, y ​​así sucesivamente.

argumento:

  • nostop Cuando el programa que se está depurando recibe una señal, GDB no detendrá la ejecución del programa, sino que imprimirá un mensaje para informarle que recibió dicha señal.
  • stop GDB detiene su programa cuando el programa que se está depurando recibe una señal.
  • print GDB muestra un mensaje cuando el programa que se está depurando recibe una señal.
  • noprint Cuando el programa que se está depurando recibe una señal, GDB no le dirá que se recibió la señal.
  • pasar o no ignorar GDB no procesa señales cuando el programa que se está depurando las recibe. Esto significa que GDB entregará esta señal al programa que se está depurando para su procesamiento.
  • nopass o ignore Cuando el programa depurado recibe una señal, GDB no permitirá que el programa depurado maneje la señal.

(5) Interrupción del hilo:

romper [linespec] subproceso [threadno] [if …]

linespec El número de línea del código fuente donde se establece el punto de interrupción. Por ejemplo: test.c:12 significa que el archivo establece un punto de interrupción para la línea 12 en test.c.

threadno El ID del hilo. Es asignado por GDB, y puede ver la información de subprocesos del programa en ejecución ingresando subprocesos de información.

si... Establece una condición de interrupción.

Ver información:

(1) Ver datos:

imprimir variable ver variables

print *array@len Ver la matriz (array es el puntero de la matriz, len es la longitud de datos requerida)

El formato de salida se puede configurar agregando parámetros:

/ 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。 
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/f 按浮点数格式显示变量。

(2) Ver memoria

examine /nfu + dirección de memoria (variable de puntero)

  • n indica la longitud de la memoria de visualización
  • f indica el formato de salida (ver arriba)
  • u indica el número de bytes especificado (b un solo byte; h doble byte; w cuatro bytes; g ocho bytes; el valor predeterminado es cuatro bytes)
  如:x /10cw pFilePath  (pFilePath为一个字符串指针,指针占4字节)
     x 为examine命令的简写。

(3) Ver información de la pila

retroceder [-n][n]

  • n indica que solo se imprime la información de la pila de la capa n en la parte superior de la pila.
  • -n significa imprimir solo la información de la pila de n capas por encima de la parte inferior de la pila.
  • Sin parámetros, significa imprimir toda la información de la pila.

2.3 Depuración en un solo paso

run®

continuar©

siguiente

  • Formato de comando: (gdb) next count: count indica cuántas líneas de código ejecutar en un solo paso, y el valor predeterminado es 1 línea
  • Su característica más importante es que cuando se encuentra una declaración que contiene una función de llamada, sin importar cuántas líneas de código contenga la función, la siguiente instrucción se ejecutará en un solo paso. Es decir, para la función llamada, el siguiente comando solo la tratará como una línea de código.

pasos)

  • (gdb) contador de pasos: el parámetro contador indica el número de líneas ejecutadas a la vez, y el valor predeterminado es 1 línea.
  • Normalmente, el comando de paso y el siguiente comando tienen la misma función y ambos ejecutan el programa paso a paso. La diferencia es que cuando la línea de código ejecutada por el comando de paso contiene una función, ingresará a la función y detendrá la ejecución en la primera línea de código de la función.

hasta que (tú)

  • (gdb) hasta: el comando hasta sin parámetros puede hacer que el depurador de GDB se ejecute rápidamente a través del cuerpo del bucle actual y se ejecute hasta que el cuerpo del bucle se detenga. Tenga en cuenta que el comando till no juega este papel bajo ninguna circunstancia, solo cuando se ejecuta hasta el final del cuerpo del bucle (la última línea de código), el comando till tendrá este efecto; de lo contrario, el comando till tiene el mismo funciona como el siguiente comando, solo programa de ejecución de un solo paso

(gdb) hasta la ubicación: la ubicación del parámetro es el número de línea de una determinada línea de código

Ver el valor de la variable

imprimir§

  • p num_1: El parámetro num_1 se utiliza para hacer referencia a la variable o expresión de destino que se va a ver o modificar
  • Su función es generar o modificar el valor de la variable o expresión especificada en el proceso de depuración del programa GDB

es juego

  • (gdb) mostrar expr
  • (gdb) visualización/expr fmt
  • expr indica la variable o expresión de destino que se va a visualizar; el parámetro fmt se utiliza para especificar el formato de la variable o expresión de salida

imagen

  • (gdb) no mostrar número...
  • (gdb) eliminar el número de visualización...
  • El parámetro num... indica el número de la variable o expresión de destino, y el número de números puede ser múltiple
  • (gdb) deshabilitar número de visualización...
  • Deshabilitar la visualización automática de variables o expresiones activas en la lista
  • (gdb) habilitar número de visualización...
  • También puede activar una variable o expresión que actualmente está deshabilitada
  • Al igual que el comando de impresión, el comando de visualización también se usa durante la fase de depuración para ver el valor de una variable o expresión.
  • La diferencia entre ellos es que cuando se utiliza el comando de visualización para ver el valor de una variable o expresión, cada vez que el programa se detiene (como en la ejecución de un solo paso), el depurador de GDB lo imprimirá automáticamente, mientras que el comando de impresión no lo hará.

Mando GDB Comando: Manejo de señal

→(gdb) manejar el modo de señal Entre ellos, el parámetro de señal indica la señal de destino que se establecerá, que suele ser el nombre completo (SIGINT) o la abreviatura (la parte después de eliminar 'SIG', como INT) de una determinada señal; si desea especificar todo, las señales pueden representarse por todo.

El parámetro de modo se usa para especificar la forma en que GDB maneja la información de destino, y su valor puede ser el siguiente:

  • ostop: Cuando se produce la señal, GDB no suspenderá el programa, puede seguir ejecutándose, pero imprimirá un mensaje de aviso indicándonos que se ha producido la señal;
  • stop: Cuando se produce la señal, GDB suspenderá la ejecución del programa.
  • noprint: GDB no imprimirá ninguna información de aviso cuando se produzca la señal;
  • imprimir: cuando se produce una señal, GDB imprimirá la información de aviso necesaria;
  • nopass (o ignorar): mientras GDB captura la señal de destino, el programa no puede procesar la señal por sí mismo;
  • pasar (o no ignorar): mientras que la depuración de GDB captura la señal de destino, también permite que el programa maneje automáticamente la señal.

En el modo gdb, puede ver la información de diferentes señales a través de señales de información o señales de información <signal_name> (por ejemplo, señales de información SIGINT).

Comandos GDB frame y backtrace: ver información de la pila

(gdb) frame spec Este comando puede seleccionar el marco de pila especificado por el parámetro spec como el marco de pila actual. Hay tres métodos comúnmente utilizados para especificar el valor del parámetro de especificación:

  1. Especificado por el número del marco de pila. 0 es el número de marco de pila correspondiente a la función llamada actualmente, y la función correspondiente al marco de pila con el número más grande suele ser la función principal main();
  2. Con la ayuda de la especificación de dirección del marco de pila. La dirección del marco de pila se puede ver en la información impresa por el comando de marco de información (se discutirá más adelante);
  3. Especificado por el nombre de función de la función. Tenga en cuenta que si se trata de una función recursiva similar que corresponde a múltiples marcos de pila, este método especifica el marco de pila con el número más pequeño.

(gdb) marco de información Podemos ver la información almacenada en el marco de pila actual

Este comando imprimirá la siguiente información del marco de pila actual en secuencia:

  • El número del marco de pila actual y la dirección del marco de pila;
  • La dirección de almacenamiento de la función correspondiente al marco de pila actual y la dirección del almacenamiento de código cuando se llama a la función
  • La persona que llama de la función actual, la dirección del marco de pila correspondiente;
  • El lenguaje de programación utilizado para escribir este marco de pila;
  • La dirección de almacenamiento y el valor del parámetro de función;
  • La dirección de almacenamiento de la variable local en la función;
  • Las variables de registro almacenadas en el marco de la pila, como el registro de instrucciones (representado por rip en el entorno de 64 bits y eip en el entorno de 32 bits), el registro del puntero base de la pila (representado por rbp en el entorno de 64 bits, y ebp en el entorno de 32 bits), etc.

Además, también puede usar info args el comando para ver el valor de cada parámetro de la función actual; use info locals el comando para ver el valor de cada variable local en la función actual.

(gdb) backtrace [-full] [n] se usa para imprimir información de todos los marcos de pila en el entorno de depuración actual

Entre ellos, los parámetros encerrados entre [] son ​​opcionales y sus significados son:

  • n: un valor entero, cuando es un entero positivo, significa imprimir la información de los n marcos de pila más internos, cuando n es un número entero negativo, significa imprimir la información de los n marcos de pila más externos;
  • -completo: imprime los valores de las variables locales mientras se imprime la información del marco de pila.

Edición de GDB y búsqueda de código fuente.

Comando de edición GDB: editar archivos

  • (gdb) editar [ubicación]

  • (gdb) editar [nombre de archivo]: [ubicación]

    • ubicación representa la ubicación en el programa. Este comando indica activar la ubicación especificada del archivo y luego editarlo.
    • Si encuentra un error "bash: /bin/ex: No existe tal archivo o directorio", porque el editor predeterminado de GDB es ex, debe especificar el editor, como export EDITOR=/usr/bin/vim o export EDITOR =/usr/bin/vi

Comando de búsqueda GDB: buscar archivos

  • buscar

  • búsqueda inversa

    • El formato de comando del primer elemento significa buscar hacia adelante desde el comienzo de la línea actual, y el segundo elemento significa buscar hacia atrás desde el comienzo de la línea actual. Entre ellos, regexp es una expresión regular.. Una expresión regular describe un patrón de coincidencia de cadena, que se puede usar para verificar si una cadena contiene una determinada subcadena, reemplazar la subcadena coincidente o extraer una determinada condición de una cadena. Muchos lenguajes de programación admiten el uso de expresiones regulares.

Tres, uso del depurador GDB

En términos generales, GDB lo ayuda principalmente a completar las siguientes cuatro funciones:

1. Inicie su programa y podrá ejecutarlo como desee de acuerdo con sus requisitos personalizados.
2. Permita que el programa que se está depurando se detenga en el punto de interrupción que especifique. (El punto de interrupción puede ser una expresión condicional)
3. Cuando se detiene el programa, puede verificar qué sucedió en su programa en ese momento.
4. Cambia dinámicamente el entorno de ejecución de tu programa.

De lo anterior, GDB no es diferente de las herramientas generales de depuración. Básicamente completa estas funciones. Sin embargo, en detalles, encontrará que GDB es una poderosa herramienta de depuración. Es posible que esté más acostumbrado a las herramientas gráficas de depuración, pero a veces, la herramienta de depuración de la línea de comandos tiene funciones que la herramienta gráfica no puede completar. Vamos a verlos uno por uno.

Un ejemplo de depuración:

源程序:tst.c

1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d /n", result );
24 printf("result[1-250] = %d /n", func(250) );
25 }

Compilar y generar archivos ejecutables: (bajo Linux)

hchen/test> cc -g tst.c -o tst

Depurar con GDB:

hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-SUSE-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。

Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。

Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>

Bueno, con el conocimiento perceptivo anterior, conozcamos gdb sistemáticamente.

Comandos gdb básicos:

GDB常用命令	格式	含义	简写
list	List [开始,结束]	列出文件的代码清单	l
prit	Print 变量名	打印变量内容	p
break	Break [行号或函数名]	设置断点	b
continue	Continue [开始,结束]	继续运行	c
info	Info 变量名	列出信息	i
next	Next	下一行	n
step	Step	进入函数(步入)	S
display	Display 变量名	显示参数	 
file	File 文件名(可以是绝对路径和相对路径)	加载文件	 
run	Run args	运行程序	r

Cuatro, combate real de GDB

Aquí hay un ejemplo práctico usando el comando anterior:

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-RedHat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.
(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) run -t cdai
Starting program: /root/Temp/bufbomb/bufbomb -t cdai
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686

(gdb) bt
#0  0x08048ad6 in getbuf ()
#1  0x08048db2 in test ()
#2  0x08049085 in launch ()
#3  0x08049257 in main ()
(gdb) info frame 0
Stack frame at 0xffffb540:
 eip = 0x8048ad6 in getbuf; saved eip 0x8048db2
 called by frame at 0xffffb560
 Arglist at 0xffffb538, args: 
 Locals at 0xffffb538, Previous frame's sp is 0xffffb540
 Saved registers:
  ebp at 0xffffb538, eip at 0xffffb53c
(gdb) info registers
eax            0xc      12
ecx            0xffffb548       -19128
edx            0xc8c340 13157184
ebx            0x0      0
esp            0xffffb510       0xffffb510
ebp            0xffffb538       0xffffb538
esi            0x804b018        134524952
edi            0xffffffff       -1
eip            0x8048ad6        0x8048ad6 <getbuf+6>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99
(gdb) x/10x $sp
0xffffb510:     0xf7ffc6b0      0x00000001      0x00000001      0xffffb564
0xffffb520:     0x08048448      0x0804a12c      0xffffb548      0x00c8aff4
0xffffb530:     0x0804b018      0xffffffff

(gdb) si
0x08048ad9 in getbuf ()
(gdb) si
0x08048adc in getbuf ()
(gdb) si
0x080489c0 in Gets ()
(gdb) n
Single stepping until exit from function Gets,
which has no line number information.
Type string:123
0x08048ae1 in getbuf ()
(gdb) si
0x08048ae2 in getbuf ()
(gdb) c
Continuing.
Dud: getbuf returned 0x1
Better luck next time

Program exited normally.
(gdb) quit

4.1 Depuración inversa

La función de depuración inversa se agregó después de GDB 7.0. Específicamente, por ejemplo, configuro puntos de interrupción en getbuf() y main(), y cuando se inicia el programa, se detendrá en el punto de interrupción de la función main(). En este momento, después de escribir record, continúe con el siguiente punto de interrupción getbuf(), y GDB registrará la información de tiempo de ejecución desde main() hasta getbuf(). Ahora use rn para depurar inversamente de getbuf() a main(). Al igual que en "X-Men: Días del futuro pasado", ¡es increíble!

Este método es adecuado para encontrar el código que causó el error a la inversa del error, y la practicidad varía según la situación. Por supuesto, también tiene limitaciones. Cuando cambian las condiciones externas, como la salida de E/S del programa, GDB no puede "revertirse".

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.

(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) b main
Breakpoint 2 at 0x80490c6

(gdb) run -t cdai
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/Temp/bufbomb/bufbomb -t cdai

Breakpoint 2, 0x080490c6 in main ()
(gdb) record
(gdb) c
Continuing.
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()

(gdb) rn
Single stepping until exit from function getbuf,
which has no line number information.
0x08048dad in test ()
(gdb) rn
Single stepping until exit from function test,
which has no line number information.
0x08049080 in launch ()
(gdb) rn
Single stepping until exit from function launch,
which has no line number information.
0x08049252 in main ()

4.2 VSCode+GDB+Qemu depurando el núcleo ARM64 de Linux

El kernel de Linux es un sistema muy complicado y es difícil para los principiantes comenzar. Si hay un entorno de depuración conveniente, la eficiencia de aprendizaje se puede mejorar al menos entre 5 y 10 veces.

Para aprender el kernel de Linux, generalmente existen estas dos necesidades:

  1. Puede deshacerse del hardware, compilar y ejecutar Linux convenientemente
  2. Puedes usar herramientas gráficas para depurar Linux

El autor usa VSCode+GDB+Qemu para completar estos dos requisitos:

  • qemu se usa como una máquina virtual para iniciar Linux.
  • VSCode+GDB se utiliza como herramienta de depuración para la DEPURACIÓN gráfica.

El efecto final es más o menos el siguiente:

interfaz de ejecución qemu:

imagen

Interfaz de depuración vscode:

imagen

A continuación se presentará paso a paso cómo construir el entorno anterior. Todas las operaciones de este artículo se realizan en una máquina virtual Vmware Ubuntu16.

Instalar la cadena de herramientas de compilación

Dado que Ubuntu es una arquitectura X86, para compilar archivos arm64, debe instalar una cadena de herramientas de compilación cruzada

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

hacer un sistema de archivos raíz

El inicio de Linux necesita cooperar con el sistema de archivos raíz. Aquí usamos busybox para hacer un sistema de archivos raíz simple

compilar caja ocupada

wget  https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -xjf busybox-1.33.1.tar.bz2
cd busybox-1.33.1

Active la opción de compilación de biblioteca estática

make menuconfig
Settings --->
 [*] Build static binary (no shared libs)

Especificar herramientas de compilación

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

compilar

make
make install

La compilación está completa y el directorio _install se genera en el directorio busybox

sistema de archivos personalizado

Para que el proceso de inicio comience normalmente, se requiere alguna configuración adicional

Agregue los directorios etc, dev y lib al directorio raíz

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ mkdir etc dev lib
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ ls
bin  dev  etc  lib  linuxrc  sbin  usr

Crear archivos por separado en etc:

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13]
$ cat profile
#!/bin/sh
export HOSTNAME=bryant
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16]
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19]
$ cat fstab
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26]
$ ls init.d
rcS

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30]
$ cat init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

Aquí hay una pequeña explicación de estos archivos:

  1. Después de que busybox se inicie como linuxrc, leerá /etc/profile, que establece algunas variables de entorno y propiedades de shell
  2. Monte el sistema de archivos de acuerdo con la información de montaje proporcionada por /etc/fstab
  3. busybox leerá sysinit desde /etc/inittab y lo ejecutará, donde sysinit apunta a /etc/init.d/rcS
  4. En /etc/init.d/rcS, el comando mdev -s es muy importante, escaneará el directorio /sys, encontrará dispositivos de caracteres y dispositivos de bloqueo, y mknod en /dev

directorio de desarrollo:

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1

Este paso es muy importante, sin el archivo de la consola, la salida del modo de usuario no se puede imprimir en el puerto serie

Directorio lib: copie la biblioteca lib y admita aplicaciones compiladas dinámicamente para ejecutar :

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43]
$ cp /usr/aarch64-linux-gnu/lib/*.so*  -a .

Compilar el kernel

Configurar el núcleo

El código fuente del kernel de Linux se puede descargar directamente desde github.

Genere .config de acuerdo con el archivo arch/arm64/configs/defconfig

make defconfig ARCH=arm64

Agregue la siguiente configuración al archivo .config

CONFIG_DEBUG_INFO=y 
CONFIG_INITRAMFS_SOURCE="./root"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0

CONFIG_DEBUG_INFO es para depurar

CONFIG_INITRAMFS_SOURCE es para especificar la ubicación del ramdisk del kernel, de modo que el ramdisk se compile directamente en la imagen del kernel después de especificar.

Llevamos el sistema de archivos raíz creado previamente al directorio raíz:

# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56]
$ cp -r ../busybox-1.33.1/_install root

ejecutar compilar

make ARCH=arm64 Image -j8  CROSS_COMPILE=aarch64-linux-gnu-

Especificar el objetivo como Imagen aquí solo compilará el núcleo, no los módulos, lo que aumentará la velocidad de compilación

empezar qemu

descargar qemu

Cabe señalar que lo mejor es compilar qemu desde el código fuente, la versión de qemu instalada directamente con apt-get puede ser demasiado baja, lo que hace que sea imposible iniciar el kernel arm64. El autor está usando la versión 4.2.1 de qemu

apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
wget https://download.qemu.org/qemu-4.2.1.tar.xz
tar xvJf qemu-4.2.1.tar.xz
cd qemu-4.2.1
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make 
sudo make install

Una vez completada la compilación, qemu está en el directorio /usr/local/bin

$ /usr/local/bin/qemu-system-aarch64 --version
QEMU emulator version 4.2.1
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

iniciar el kernel de linux

/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel

Aquí hay algunas explicaciones para los parámetros:

  • -m 512MLa memoria es 512M
  • -smp 44 nucleares
  • -cpu cortex-a57la cpu es cortex-a57
  • -kernelarchivo de imagen del kernel
  • -appendEl parámetro cmdline pasado al kernel. Entre ellos, rdinit especifica el proceso de inicio; nokaslr prohíbe la aleatorización de la dirección de inicio del kernel, lo cual es muy importante, de lo contrario, la depuración de GDB puede tener problemas; console=ttyAMA0 especifica el puerto serie, sin este paso, no puede ver la salida de linux ;
  • -nographicdeshabilitar la salida de gráficos
  • -sEscuche el puerto gdb y el programa gdb se puede conectar a través del puerto 1234.

Aquí hay una explicación de cómo funciona console=ttyAMA0.

Ver el código fuente de Linux muestra que ttyAMA0 corresponde a AMBA_PL011este controlador:

config SERIAL_AMBA_PL011_CONSOLE
    bool "Support for console on AMBA serial port"
    depends on SERIAL_AMBA_PL011=y
    select SERIAL_CORE_CONSOLE
    select SERIAL_EARLYCON
    help
      Say Y here if you wish to use an AMBA PrimeCell UART as the system
      console (the system console is the device which receives all kernel
      messages and warnings and which allows logins in single user mode).

      Even if you say Y here, the currently visible framebuffer console
      (/dev/tty0) will still be used as the system console by default, but
      you can alter that using a kernel command line option such as
      "console=ttyAMA0". (Try "man bootparam" or see the documentation of
      your boot loader (lilo or loadlin) about how to pass options to the
      kernel at boot time.)

AMBA_PL011 es un dispositivo de puerto serie estándar de arm, y la salida de qemu es el puerto serie simulado.

En el archivo de código fuente de qemu, también puede ver los archivos relacionados de PL011:

# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54]
$ find . -name "*pl011*"
./hw/char/pl011.c

Después de iniciar Linux con éxito, las impresiones del puerto serie son las siguientes:

[    3.401567] usbcore: registered new interface driver usbhid
[    3.404445] usbhid: USB HID core driver
[    3.425030] NET: Registered protocol family 17
[    3.429743] 9pnet: Installing 9P2000 support
[    3.435439] Key type dns_resolver registered
[    3.440299] registered taskstats version 1
[    3.443685] Loading compiled-in X.509 certificates
[    3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[    3.473163] ALSA device list:
[    3.474432]   No soundcards found.
[    3.485283] uart-pl011 9000000.pl011: no DMA platform data
[    3.541376] Freeing unused kernel memory: 10752K
[    3.545897] Run /linuxrc as init process
[    3.548390]   with arguments:
[    3.550279]     /linuxrc
[    3.551073]     nokaslr
[    3.552216]   with environment:
[    3.554396]     HOME=/
[    3.555898]     TERM=linux
[    3.985835] 9pnet_virtio: no channels available for device kmod_mount
mount: mounting kmod_mount on /mnt failed: No such file or directory
/etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory

Please press Enter to activate this console.
[root@bryant ]#
[root@bryant ]#

Código VSC+GDB

La función GDB está integrada en vscode, podemos usarla para depurar gráficamente el kernel de Linux

Primero agregamos el archivo de configuración gdb de vscode (.vscode/launch.json):

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/vmlinux",
            "cwd": "${workspaceFolder}",
            "MIMode": "gdb",
            "miDebuggerPath":"/usr/bin/gdb-multiarch",
            "miDebuggerServerAddress": "localhost:1234"
        }
    ]
}

Aquí hay algunas explicaciones para varios parámetros clave:

  • program: archivo de símbolos para la depuración
  • miDebuggerPath: La ruta de gdb. Cabe señalar aquí que, dado que somos kernel arm64, necesitamos usar gdb-multiarch para depurar
  • miDebuggerServerAddress: Dirección de pares, qemu usará el puerto 1234 por defecto

Una vez completada la configuración, puede iniciar GDB directamente y conectarse al kernel de Linux

imagen

En vscode, puede establecer puntos de interrupción para la depuración de un solo paso

imagen

imagen


Declaración de derechos de autor: este artículo es un artículo original escrito por el bloguero de Zhihu "Jugando con el kernel de Linux". Sigue el acuerdo de derechos de autor CC 4.0 BY-SA. Para reimprimirlo, adjunte el enlace de la fuente original y esta declaración.
Enlace original: https://zhuanlan.zhihu.com/p/639365490

Supongo que te gusta

Origin blog.csdn.net/m0_50662680/article/details/131484284
Recomendado
Clasificación