tools proteus net for development cross compiler compilador c++ c gcc arm newlib

c++ - proteus - https launchpad net gcc arm embedded



¿Cómo obtener un backtrace de pila de llamadas?(profundamente integrado, sin soporte de biblioteca) (6)

¿Su ejecutable contiene información de depuración, desde la compilación con la opción -g ? Creo que esto es necesario para obtener una traza de pila completa sin un puntero de marco.

Es posible que necesite -gdwarf-2 para asegurarse de que utiliza un formato que incluye información de desenrollado.

Quiero que mis controladores de excepciones y funciones de depuración puedan imprimir backtraces de pila de llamadas, básicamente como la función de biblioteca backtrace () en glibc. Desafortunadamente, mi biblioteca C (Newlib) no proporciona tal llamada.

Tengo algo como esto:

#include <unwind.h> // GCC''s internal unwinder, part of libgcc _Unwind_Reason_Code trace_fcn(_Unwind_Context *ctx, void *d) { int *depth = (int*)d; printf("/t#%d: program counter at %08x/n", *depth, _Unwind_GetIP(ctx)); (*depth)++; return _URC_NO_REASON; } void print_backtrace_here() { int depth = 0; _Unwind_Backtrace(&trace_fcn, &depth); }

que básicamente funciona pero las trazas resultantes no siempre están completas. Por ejemplo, si lo hago

int func3() { print_backtrace_here(); return 0; } int func2() { return func3(); } int func1() { return func2(); } int main() { return func1(); }

el retroceso solo muestra func3 () y main (). (Esto es, obviamente, un ejemplo de juguete, pero he comprobado el desmontaje y confirmado que todas estas funciones están completas aquí y no están optimizadas ni en línea).

Actualización: probé este código de backtrace en el antiguo sistema ARM7 pero con las mismas (o al menos, equivalente opciones de compilador y script de vinculador) e imprime un backtrace completo correcto (es decir, func1 y func2 no faltan) y de hecho, incluso retrocede pasado principal en el código de inicialización de arranque. Entonces, presumiblemente, el problema no es con el script de vinculador o las opciones del compilador. (Además, se confirmó a partir del desmontaje que tampoco se usa un puntero de cuadro en esta prueba ARM7).

El código se compila con -fomit-frame-puntero, pero mi plataforma (simple ARM Cortex M3) define un ABI que no usa un puntero de marco de todos modos. (Una versión anterior de este sistema usaba el APCS ABI antiguo en ARM7 con marcos de apilamiento forzado y puntero de marco, y un retroceso como el de here , que funcionó perfectamente).

Todo el sistema se compila con -fexception, lo que garantiza que los metadatos necesarios que utiliza _Unwind se incluyen en el archivo ELF. (_Unwind está diseñado para el manejo de excepciones, creo).

Entonces, mi pregunta es: ¿Existe una forma "estándar" y aceptada de obtener backtraces confiables en sistemas integrados utilizando GCC?

No me importa tener que perder el tiempo con los scripts del vinculador y el código crt0 si es necesario, pero no quiero tener que arriesgarme a la cadena de herramientas en sí.

¡Gracias!


Algunos compiladores, como GCC, optimizan las llamadas a funciones como mencionó en el ejemplo. Para la operación del fragmento de código, no es necesario almacenar los punteros de retorno intermedios en la cadena de llamadas. Está perfectamente bien volver de func3() a main() , ya que las funciones intermedias no hacen nada adicional además de llamar a otra función.

No es lo mismo que la eliminación de código (en realidad, las funciones intermedias podrían optimizarse completamente), y un parámetro de compilador separado podría controlar este tipo de optimización.

Si usa GCC, intente -fno-optimize-sibling-calls

Otra opción práctica de GCC es -mno-sched-prolog , que evita la reordenación de la instrucción en la función prólogo, que es vital, si desea analizar el código byte por byte, como se hace aquí: http://www.kegel.com/stackcheck/checkstack-pl.txt


Dado que las plataformas ARM no utilizan un puntero de cuadro, nunca se sabe muy bien qué tan grande es el cuadro de pila y no se puede simplemente lanzar la pila más allá del valor de retorno único en R14.

Cuando investigamos una falla para la cual no tenemos símbolos de depuración, simplemente volcamos toda la pila y buscamos el símbolo más cercano a cada elemento en el rango de instrucciones. Genera una carga de falsos positivos, pero aún puede ser muy útil para investigar bloqueos.

Si está ejecutando ejecutables ELF puros, puede separar los símbolos de depuración de su ejecutable de lanzamiento. gdb puede ayudarlo a descubrir lo que está pasando desde su volcado de núcleo estándar de Unix


Esto es intrépido, pero he encontrado que funciona lo suficientemente bien teniendo en cuenta la cantidad de espacio de código / RAM requerido:

Suponiendo que esté utilizando el modo ARM THUMB, compile con las siguientes opciones:

-mtpcs-frame -mtpcs-leaf-frame -fno-omit-frame-pointer

La siguiente función se utiliza para recuperar la pila de llamadas. Consulte los comentarios para más información:

/* * This should be compiled with: * -mtpcs-frame -mtpcs-leaf-frame -fno-omit-frame-pointer * * With these options, the Stack pointer is automatically pushed to the stack * at the beginning of each function. * * This function basically iterates through the current stack finding the following combination of values: * - <Frame Address> * - <Link Address> * * This combination will occur for each function in the call stack */ static void backtrace(uint32_t *caller_list, const uint32_t *caller_list_end, const uint32_t *stack_pointer) { uint32_t previous_frame_address = (uint32_t)stack_pointer; uint32_t stack_entry_counter = 0; // be sure to clear the caller_list buffer memset(caller_list, 0, caller_list_end-caller_list); // loop until the buffer is full while(caller_list < caller_list_end) { // Attempt to obtain next stack pointer // The link address should come immediately after const uint32_t possible_frame_address = *stack_pointer; const uint32_t possible_link_address = *(stack_pointer+1); // Have we searched past the allowable size of a given stack? if(stack_entry_counter > PLATFORM_MAX_STACK_SIZE/4) { // yes, so just quite break; } // Next check that the frame addresss (i.e. stack pointer for the function) // and Link address are within an acceptable range else if((possible_frame_address > previous_frame_address) && ((possible_frame_address < previous_frame_address + PLATFORM_MAX_STACK_SIZE)) && ((possible_link_address & 0x01) != 0) && // in THUMB mode the address will be odd (possible_link_address > PLATFORM_CODE_SPACE_START_ADDRESS && possible_link_address < PLATFORM_CODE_SPACE_END_ADDRESS)) { // We found two acceptable values // Store the link address *caller_list++ = possible_link_address; // Update the book-keeping registers for the next search previous_frame_address = possible_frame_address; stack_pointer = (uint32_t*)(possible_frame_address + 4); stack_entry_counter = 0; } else { // Keep iterating through the stack until be find an acceptable combination ++stack_pointer; ++stack_entry_counter; } } }

Tendrás que actualizar #defines para tu plataforma.

Luego, llame a lo siguiente para llenar un búfer con la pila de llamadas actual:

uint32_t callers[8]; uint32_t sp_reg; __ASM volatile ("mov %0, sp" : "=r" (sp_reg) ); backtrace(callers, &callers[8], (uint32_t*)sp_reg);

Nuevamente, esto es bastante intrépido, pero he encontrado que funciona bastante bien. El búfer se rellenará con las direcciones de enlace de cada llamada de función en la pila de llamadas.


Para esto, necesita -funwind-tables o -fasynchronous-unwind-tables En algunos destinos, esto es necesario para que _Unwind_Backtrace funcione correctamente.


gcc devuelve la optimización. En func1 () y func2 () no llama func2 () / func3 () - en lugar de esto, salta a func2 () / func3 (), por lo que func3 () puede regresar inmediatamente a main ().

En su caso, func1 () y func2 () no necesitan configurar un marco de pila, pero si lo harían (por ejemplo, para variables locales), gcc todavía puede hacer la optimización si la llamada a la función es la última instrucción, luego limpia. arriba de la pila antes del salto a func3 ().

Echa un vistazo al código ensamblador generado para verlo.

Editar / Actualizar:

Para verificar que esta es la razón, haga algo después de la llamada a la función, que el compilador no puede reordenar (por ejemplo, usando un valor de retorno). O simplemente intente compilar con -O0.