[gdb] Solución a la confusión de la pila de funciones [Transferir]
Cerré el núcleo del programa y lo depuré, pero la pila de funciones era un desastre, muy desagradable... Después de algunas búsquedas en Google/wiki, encontré dos soluciones.
x86ManualBacktrace
Este tutorial le muestra cómo reconstruir manualmente un seguimiento inverso usando GDB en x86 usando el puntero del marco de pila y el puntero de instrucción actual.
Considere el siguiente rastreo de gdb:
Es obvio que esto no funciona, por favor pruebe los siguientes campos:
Obtenga la información de registro del proceso:
En x86:
Diseño del marco de pila para x86:
Clave de color: azul
Vuelque la pila de arriba usando la dirección del marco de pila actual en ebp:
Del volcado de pila, sabemos que la dirección del marco de pila en el registro ebp es el marco de pila de la instrucción actual en el registro eip (a menos que se trate de una función hoja que no apila marcos , pero eso es irrelevante para esta discusión). Usando los registros ebp y eip como punto de partida, podemos construir la primera línea de la reconstrucción de retroceso:
Cuando el control del programa se bifurca a una nueva función, los marcos de la pila se apilarán y la última dirección del La función llamada se almacenará en La dirección del nuevo marco está en ebp 4 (es decir, la última dirección de memoria en el marco de pila de la función llamada es 4 diferente de la dirección en ebp del marco de pila actual). Para obtener el marco de pila y el puntero de instrucción de la persona que llama, simplemente mire ebp y ebp 4:
Encuentre la memoria en el marco de pila ebp, la memoria en ebp 4, la memoria en ebp 4 en la pila y la memoria en ebp 4 (que contendrá el puntero de instrucciones del destinatario).
Continúe construyendo nuestra lista usando las direcciones en 0xbf9ef358 y 0xbf9ef35c (es decir, 0xbf9ef388 y 0x00d94cf7):
Continúe con la siguiente dirección del marco de pila 0xbf9ef388:
Mantener haciendo esto hasta que obtengamos un rastreo completo.
Sabes que has llegado al final de la pila cuando el puntero del marco de la pila anterior es 0x00000000.
Aquí hay una reconstrucción manual completa del marco de la pila:
En amd64, simplemente convierta el registro a rbp y duplique el tamaño de la palabra. Por supuesto, elegimos encontrar manualmente la dirección de retorno de la función y luego imprimir el nombre de la función desde el símbolo de información. gdb puede formatear el símbolo e imprimir el nombre de la función directamente:
gdbgt; contenidos rbp
Por lo tanto, el método de reconstrucción manual se vuelve muy simple:
gdbgt;info reg rbp *x86 para información reg ebp
gdbgt;x/128ag the contenido de rbp *x86 para x/128aw el contenido de ebp
De esta manera puedes ver la pila de funciones. Si desea analizar los parámetros, también puede hacerlo, pero es un poco engorroso. ..... Si desea analizar parámetros, necesita conocer el diseño de la pila. Puede consultar este artículo: /liigo/archive/2006/12/23/1456938.aspx
Ayer estudié el. Pila de llamadas de función con Dayang. Por cierto, escribí dos oraciones.
Ayer estaba estudiando la pila de llamadas de funciones con Dahai y, por cierto, ¡escribí algunas frases!
Los dos puntos más importantes para entender la pila de llamadas son: la estructura de la pila y el papel del registro EBP.
En primer lugar, debe tener en cuenta los dos hechos siguientes:
1. Una operación de llamada de función se puede descomponer en: cero a más instrucciones PUSH (utilizadas para colocar parámetros en la pila ), una instrucción CALL. La instrucción CALL en realidad implica una dirección de retorno (es decir, la dirección de la siguiente instrucción de la instrucción CALL) para impulsar la acción de la pila.
2. Casi todos los compiladores nativos insertarán instrucciones similares a las siguientes antes del cuerpo de cada función: PUSH EBP; MOV EBP ESP
En otras palabras, cuando el programa ejecuta un Cuando el; Cuando se alcanza el cuerpo real de la función, se ha ingresado la siguiente secuencia de datos en la pila: parámetros, dirección de retorno, EBP. Tomando como ejemplo el CDECL predeterminado en lenguaje C):
El significado real de "PUSH EBP" y "MOV EBP ESP" es: primero agregue EBP a la pila y luego asigne el puntero superior ESP a PBE. La instrucción MOV EBP ESP " parece sobrescribir el valor original de EBP con ESP, pero en realidad este no es el caso, porque el valor original de EBP se ha insertado en la pila (en la parte superior de la pila) antes de asignar un valor. a EBP, y el nuevo EBP resulta ser Puntos en la parte superior de la pila
En este momento, el registro EBP ya está en una posición muy importante. Este registro almacena una dirección en la pila. El EBP original se encuentra en la parte superior de la pila después de ingresar a la pila). Según esta dirección, obtenga la dirección de retorno y el valor del parámetro hacia arriba (dirección desde la parte inferior de la pila), obtenga el valor de la variable local de la función hacia abajo (dirección). desde la parte superior de la pila) y almacene el valor de la dirección EBP de la última llamada a la función en la capa superior.
¡En general, ss: [ebp 4] es la dirección de retorno, ss: [ebp] 8] es el primer valor del parámetro (el último valor del parámetro en la pila, suponiendo que ocupe 4 bytes de memoria), ss: [ebp-4] es la primera variable local A, ss: [ebp] es el valor EBP anterior.
Dado que la dirección en EBP es siempre el "valor EBP de la última llamada a función", y en cada nivel de llamada a función, puede utilizar el valor EBP para "obtener la dirección de retorno y el valor del parámetro hacia arriba ( hacia la parte inferior de la pila), y obtener el valor de la variable local en la función hacia abajo (hacia la parte superior de la pila)". Esto forma un proceso recursivo hasta llegar al final de la pila. Pila de llamadas a funciones. p>
El uso de EBP por parte del compilador es muy sutil
Es muy fácil encontrar todos los EBP a partir del EBP actual:
De esta manera es simple y fácil. hacerlo, pero la advertencia es que si el contenido de la pila se vacía, ni siquiera verá el cabello en el encabezado (que es), por lo que necesita comenzar a proteger la pila... al menos puede encontrar la función en la parte superior de la pila..
gcc tiene dos parámetros: -fstack-protector y -fstack-protector-all, se recomienda encarecidamente activarlos....
******** ******************************************* ********** ************/
Utilice el método de impresión para depurar
Después de trabajar en proyectos de clientes durante la mitad Al año, descubrí que los clientes principales son mejores que nosotros. Hablemos primero sobre los métodos de depuración.
Los clientes no confían en GDB para la depuración porque pueden sentir que GDB depende de la implementación del sistema y no. propicio para la portabilidad, por lo que el programa del cliente se basa completamente en la depuración de impresión. Admiro sus capacidades de planificación de software y gestión e implementación de proyectos.
Creo que en las empresas chinas, cada uno tiene sus propios métodos de depuración. no es fácil seguir las reglas.
Implementación de un menú de depuración completo No es difícil, es solo una función de impresión y aceptación de caracteres. En este menú, los controles están abiertos para cierta información de impresión.
En cada módulo, además de impresiones cuidadosamente seleccionadas, divididas en diferentes contenidos básicos según las necesidades. Idealmente, todas las ediciones se pueden encontrar en el nivel de impresión más alto. Los niveles de impresión se pueden controlar fácilmente de forma dinámica.
Registro de llamadas de función
Si puede encontrar el módulo donde ocurre el problema, puede imprimir el nombre de la función. Ingrese a la entrada de cada llamada de función en el módulo, imprima el nombre de la función. Salga al regresar y use El interruptor de impresión de cada módulo controla si se imprime la información de seguimiento. El interruptor de impresión está controlado por el menú de depuración.
Si es demasiado vago para agregar declaraciones impresas, puede usar la opción -finstrument-functions de gcc para agregar rápidamente información de depuración.
Estas funciones deben implementarse usando el atributo ((no_instrument_function)); para evitar que el compilador llame a __cyg_profile_func_enter y __cyg_profile_func_exit y haga que el compilador llame a estas funciones.
Puedes usar dladdr() para obtener el nombre del archivo y el nombre de la función de this_fn. El código es el siguiente:
// Dado que dladdr es una extensión GNU y no una función estándar de dl, esta oración debe agregarse al principio del archivo
Acerca de la impresión pila. Esto es posible
Este enfoque requiere soporte del compilador.
Pero se debe usar -rdynamic al compilar; de lo contrario, solo se puede generar la dirección absoluta en la memoria.
Si no se utiliza -rdynamic, queda por estudiar cómo encontrar la dirección de tiempo de ejecución de la biblioteca de enlaces dinámicos.
Puede enviar SIGSEGV a la aplicación mientras el sistema se está ejecutando y generar un Coredump del proceso actual para obtener la dirección de tiempo de ejecución de la función en la biblioteca de enlaces dinámicos.
Cómo usar GDB para obtener el seguimiento (usando la opción -g, no usando -rdynamic):
list lt *addressgt; -g opción ¿qué hacer?