tipos simbologia proposito lenguaje estructura ejemplos caracteristicas basica aplicaciones c windows backtrace

proposito - simbologia lenguaje c



Win32-Backtrace de código C (3)

Actualmente estoy buscando una manera de obtener información de seguimiento en Windows, desde el código C (sin C ++).

Estoy construyendo una biblioteca C multiplataforma, con gestión de memoria de conteo de referencias. También tiene un depurador de memoria integrado que proporciona información sobre errores de memoria ( XEOS C Foundation Library ).

Cuando se produce un fallo, se inicia el depurador, que proporciona información sobre el fallo y el registro de memoria involucrado.

En Linux o Mac OS X, puedo buscar execinfo.h para usar la función de backtrace , por lo que puedo mostrar información adicional sobre la falla de memoria.

Estoy buscando lo mismo en Windows.

He visto ¿Cómo se puede agarrar un rastro de pila en C? en desbordamiento de pila. No quiero usar una biblioteca de terceros, por lo que las funciones de CaptureStackBackTrace o StackWalk ven bien.

El único problema es que simplemente no entiendo cómo usarlos, incluso con la documentación de Microsoft.

No estoy acostumbrado a la programación de Windows, ya que normalmente trabajo en sistemas compatibles con POSIX.

¿Cuáles son algunas explicaciones de esas funciones y quizás algunos ejemplos?

EDITAR

Ahora estoy considerando usar la función CaptureStackBackTrace de DbgHelp.lib , ya que parece que hay un poco menos de sobrecarga ...

Esto es lo que he intentado hasta ahora:

unsigned int i; void * stack[ 100 ]; unsigned short frames; SYMBOL_INFO symbol; HANDLE process; process = GetCurrentProcess(); SymInitialize( process, NULL, TRUE ); frames = CaptureStackBackTrace( 0, 100, stack, NULL ); for( i = 0; i < frames; i++ ) { SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, &symbol ); printf( "%s/n", symbol.Name ); }

Solo estoy recibiendo chatarra. Supongo que debería usar algo más que SymFromAddr .


@Jon Bright: Dices "quién sabe si la pila es válida ...": Bueno, hay una forma de averiguarlo, ya que las direcciones de la pila son conocidas. Suponiendo que necesita un rastreo en el hilo actual, por supuesto:

NT_TIB* pTEB = GetTEB(); UINT_PTR ebp = GetEBPForStackTrace(); HANDLE hCurProc = ::GetCurrentProcess(); while ( ((ebp & 3) == 0) && ebp + 2*sizeof(VOID*) < (UINT_PTR)pTEB->StackBase && ebp >= (UINT_PTR)pTEB->StackLimit && nAddresses < nTraceBuffers) { pTraces[nAddresses++]._EIP = ((UINT_PTR*)ebp)[1]; ebp = ((UINT_PTR*)ebp)[0]; }

Mi "GetTEB ()" es NtCurrentTeb () de NTDLL.DLL, y no solo es Windows 7 y superior como se indica en el MSDN actual. MS junks hasta la documentación. Estuvo ahí por mucho tiempo. Usando el bloque ThreadEnvironment (TEB), no necesita ReadProcessMemory () ya que conoce los límites inferior y superior de la pila. Supongo que esta es la forma más rápida de hacerlo.

Usando el compilador de MS, GetEBPForStackTrace () puede ser

inline __declspec(naked) UINT_PTR GetEBPForStackTrace() { __asm { mov eax, ebp ret } }

como forma fácil de obtener EBP del subproceso actual (pero puede pasar cualquier EBP válido a este bucle siempre que sea para el subproceso actual).

Limitación: Esto es válido para x86 en Windows.


Aquí está mi alternativa de super-low-fi, como se usa para leer pilas de una aplicación C ++ Builder. Este código se ejecuta dentro del propio proceso cuando se bloquea y obtiene una pila en la matriz cs.

int cslev = 0; void* cs[300]; void* it = <ebp at time of crash>; void* rm[2]; while(it && cslev<300) { /* Could just memcpy instead of ReadProcessMemory, but who knows if the stack''s valid? If it''s invalid, memcpy could cause an AV, which is pretty much exactly what we don''t want */ err=ReadProcessMemory(GetCurrentProcess(),it,(LPVOID)rm,sizeof(rm),NULL); if(!err) break; it=rm[0]; cs[cslev++]=(void*)rm[1]; }

ACTUALIZAR

Una vez que tengo la pila, luego voy a traducirla en nombres. Hago esto mediante referencias cruzadas con el archivo .map que genera C ++ Builder. Lo mismo podría hacerse con un archivo de mapa de otro compilador, aunque el formato sería algo diferente. El siguiente código funciona para los mapas de C ++ Builder. De nuevo, esto es bastante bajo y probablemente no sea la forma canónica de MS de hacer las cosas, pero funciona en mi situación. El código a continuación no se entrega a los usuarios finales.

char linbuf[300]; char *pars; unsigned long coff,lngth,csect; unsigned long thisa,sect; char *fns[300]; unsigned int maxs[300]; FILE *map; map = fopen(mapname, "r"); if (!map) { ...Add error handling for missing map... } do { fgets(linbuf,300,map); } while (!strstr(linbuf,"CODE")); csect=strtoul(linbuf,&pars,16); /* Find out code segment number */ pars++; /* Skip colon */ coff=strtoul(pars,&pars,16); /* Find out code offset */ lngth=strtoul(pars,NULL,16); /* Find out code length */ do { fgets(linbuf,300,map); } while (!strstr(linbuf,"Publics by Name")); for(lop=0;lop!=cslev;lop++) { fns[lop] = NULL; maxs[lop] = 0; } do { fgets(linbuf,300,map); sect=strtoul(linbuf,&pars,16); if(sect!=csect) continue; pars++; thisa=strtoul(pars,&pars,16); for(lop=0;lop!=cslev;lop++) { if(cs[lop]<coff || cs[lop]>coff+lngth) continue; if(thisa<cs[lop]-coff && thisa>maxs[lop]) { maxs[lop]=thisa; while(*pars=='' '') pars++; fns[lop] = fnsbuf+(100*lop); fnlen = strlen(pars); if (fnlen>100) fnlen = 100; strncpy(fns[lop], pars, 99); fns[lop][fnlen-1]=''/0''; } } } while (!feof(map)); fclose(map);

Después de ejecutar este código, la matriz fns contiene la función de mejor coincidencia del archivo .map.

En mi situación, en realidad tengo la pila de llamadas producida por el primer fragmento de código que se envía a un script PHP: hago el equivalente al código C de arriba con un fragmento de PHP. Este primer bit analiza el archivo de mapa (de nuevo, esto funciona con los mapas de C ++ Builder pero podría adaptarse fácilmente a otros formatos de archivo de mapa):

$file = fopen($mapdir.$app."-".$appversion.".map","r"); if (!$file) ... Error handling for missing map ... do { $mapline = fgets($file); } while (!strstr($mapline,"CODE")); $tokens = split("[[:space:]/:]", $mapline); $codeseg = $tokens[1]; $codestart = intval($tokens[2],16); $codelen = intval($tokens[3],16); do { $mapline = fgets($file); } while (!strstr($mapline,"Publics by Value")); fgets($file); // Blank $addrnum = 0; $lastaddr = 0; while (1) { if (feof($file)) break; $mapline = fgets($file); $tokens = split("[[:space:]/:]", $mapline); $thisseg = $tokens[1]; if ($thisseg!=$codeseg) break; $addrs[$addrnum] = intval($tokens[2],16); if ($addrs[$addrnum]==$lastaddr) continue; $lastaddr = $addrs[$addrnum]; $funcs[$addrnum] = trim(substr($mapline, 16)); $addrnum++; } fclose($file);

Entonces este bit traduce una dirección (en $rowaddr ) a una función dada (así como el desplazamiento después de la función):

$thisaddr = intval($rowaddr,16); $thisaddr -= $codestart; if ($thisaddr>=0 && $thisaddr<=$codelen) { for ($lop=0; $lop!=$addrnum; $lop++) if ($thisaddr<$addrs[$lop]) break; } else $lop = $addrnum; if ($lop!=$addrnum) { $lop--; $lines[$ix] = substr($line,0,13).$rowaddr." : ".$funcs[$lop]." (+".sprintf("%04X",$thisaddr-$addrs[$lop]).")"; $stack .= $rowaddr; } else { $lines[$ix] = substr($line,0,13).$rowaddr." : external"; }


Bien, ahora lo tengo. :)

El problema estaba en la estructura SYMBOL_INFO. Debe asignarse en el montón, reservar espacio para el nombre del símbolo e inicializarse correctamente.

Aquí está el código final:

void printStack( void ); void printStack( void ) { unsigned int i; void * stack[ 100 ]; unsigned short frames; SYMBOL_INFO * symbol; HANDLE process; process = GetCurrentProcess(); SymInitialize( process, NULL, TRUE ); frames = CaptureStackBackTrace( 0, 100, stack, NULL ); symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 ); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof( SYMBOL_INFO ); for( i = 0; i < frames; i++ ) { SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol ); printf( "%i: %s - 0x%0X/n", frames - i - 1, symbol->Name, symbol->Address ); } free( symbol ); }

La salida es:

6: printStack - 0xD2430 5: wmain - 0xD28F0 4: __tmainCRTStartup - 0xE5010 3: wmainCRTStartup - 0xE4FF0 2: BaseThreadInitThunk - 0x75BE3665 1: RtlInitializeExceptionChain - 0x770F9D0F 0: RtlInitializeExceptionChain - 0x770F9D0F