Ich suche derzeit nach einer Möglichkeit, Backtrace-Informationen unter Windows aus C-Code (kein C++) abzurufen.
Ich baue eine plattformübergreifende C-Bibliothek mit Speicherverwaltung mit Referenzzählung. Es hat auch einen integrierten Speicherdebugger, der Informationen über Speicherfehler liefert (XEOS C Foundation-Bibliothek).
Wenn ein Fehler auftritt, wird der Debugger gestartet und liefert Informationen über den Fehler und den betroffenen Speicherdatensatz.
Unter Linux oder Mac OS X kann ich suchen execinfo.h um die zu verwenden backtrace Funktion, damit ich mir zusätzliche Infos zum Speicherfehler anzeigen lassen kann.
Ich suche das gleiche unter Windows.
Ich habe gesehen Wie kann man in C einen Stack-Trace abrufen? auf Stapelüberlauf. Ich möchte keine Bibliothek eines Drittanbieters verwenden, also die CaptureStackBackTrace oder StackWalk Funktionen sieht gut aus.
Das einzige Problem ist, dass ich einfach nicht verstehe, wie man sie benutzt, selbst mit der Microsoft-Dokumentation.
Ich bin nicht an die Windows-Programmierung gewöhnt, da ich normalerweise auf POSIX-kompatiblen Systemen arbeite.
Was sind einige Erklärungen für diese Funktionen und vielleicht einige Beispiele?
BEARBEITEN
Ich überlege jetzt, die zu verwenden CaptureStackBackTrace Funktion ab DbgHelp.libwie es scheint, gibt es ein bisschen weniger Overhead …
Folgendes habe ich bisher versucht:
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 );
}
Ich bekomme nur Schrott. Ich denke, ich sollte etwas anderes als verwenden SymFromAddr.
Eigentlich der Artikel, der in Ihrem verlinkten Beitrag als Antwort gegeben wird (codeproject.com/KB/threads/StackWalker.aspx) kann auch als Leitfaden zum Erfassen des Stapels eines Threads verwendet werden. Es gibt Antworten auf alle Ihre Fragen. Außerdem gibt es einen Quellcode, den Sie verwenden können, um zu verstehen, wie man es selbst macht. Versuchen Sie, den Artikel nach unten zum Abschnitt “Sehenswürdigkeiten” zu scrollen.
– bezmax
17. April 2011 um 19:20 Uhr
Danke für den Kommentar 🙂 Immer noch kein Glück … stackoverflow.com/questions/5705650/…
– Macmade
19. April 2011 um 9:31 Uhr
Macmade
Okay, jetzt habe ich es. 🙂
Das Problem lag in der SYMBOL_INFO-Struktur. Es muss auf dem Heap zugewiesen werden, Platz für den Symbolnamen reservieren und ordnungsgemäß initialisiert werden.
Haben Sie eine Idee, wie Sie dieselbe Methode verwenden können, um die tatsächliche Zeilennummer zu erhalten? Wenn ja, antworten Sie bitte hier auf meine Frage: stackoverflow.com/questions/22465253/…
– Alexandru
17. März 2014 um 21:08 Uhr
Danke für den Code! Beachten Sie, dass SymFromAddr kann fehlschlagen (z. B. wenn die .pdb-Datei fehlt), während die Adresse von stack[i] könnte immer noch nützlich sein (und Sie drucken es nicht).
– Paulus
21. Februar 2015 um 16:21 Uhr
Es gibt nichts, was erfordert, dass die SYMBOL_INFO Struktur auf dem Haufen sein. Auf dem Stack funktioniert es einwandfrei.
– legalisieren
19. Juni 2016 um 17:11 Uhr
@legalize, es muss dynamisch zugewiesen werden, um dem Namensfeld zusätzliche Größe zuzuweisen (andernfalls wäre die maximale Länge des Namens 1).
– Mark Ingram
23. August 2017 um 10:59 Uhr
@AvivCohn #include und fügen Sie -lDbgHelp zu den Linker-Flags hinzu. Und ja, ich weiß, ich bin spät dran. 🙂
– Refugnisches Eternium
10. Januar 2020 um 11:11 Uhr
Jon Hell
Hier ist meine Super-Low-Fi-Alternative, wie sie zum Lesen von Stacks aus einer C++ Builder-App verwendet wird. Dieser Code wird innerhalb des Prozesses selbst ausgeführt, wenn er abstürzt und einen Stack in das cs-Array erhält.
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];
}
AKTUALISIEREN
Sobald ich den Stack habe, mache ich mich daran, ihn in Namen zu übersetzen. Ich tue dies durch Querverweise mit dem .map Datei, die C++Builder ausgibt. Das Gleiche könnte mit einer Map-Datei von einem anderen Compiler gemacht werden, obwohl die Formatierung etwas anders wäre. Der folgende Code funktioniert für C++Builder-Maps. Dies ist wieder ziemlich Low-Fi und wahrscheinlich nicht die kanonische MS-Methode, aber es funktioniert in meiner Situation. Der folgende Code wird nicht an Endbenutzer geliefert.
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);
Nachdem Sie diesen Code ausgeführt haben, wird die fns Das Array enthält die am besten passende Funktion aus der .map-Datei.
In meiner Situation habe ich tatsächlich den Call-Stack, wie er durch das erste Stück Code erzeugt wird, das an ein PHP-Skript gesendet wird – ich mache das Äquivalent des obigen C-Codes mit einem Stück PHP. Dieses erste Bit analysiert die Kartendatei (Auch dies funktioniert mit C++Builder-Karten, könnte aber leicht an andere Kartendateiformate angepasst werden):
$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);
Dann übersetzt dieses Bit eine Adresse (in $rowaddr) in eine gegebene Funktion (sowie den Offset nach der Funktion):
Wie bekomme ich dann die Funktionsnamen aus diesen Infos? Soll ich die verwenden SymGetSym() Funktion?
– Macmade
18. April 2011 um 10:15 Uhr
Danke für die sehr schöne Bearbeitung 🙂 Aber was ist, wenn es keine Kartendatei gibt?
– Macmade
18. April 2011 um 15:19 Uhr
Macmade, grob gesagt, mit meiner Methode sind Sie am Arsch. Ich hätte vielleicht erklären sollen, dass ich dies für Dinge mache, die ich eigentlich verteile, in die ich keine Debug-Informationen aufnehmen möchte. Da die ausführbare Datei keine Debug-Informationen enthält, benötigen Sie eine Art externe Referenz. Dafür ist die Map-Datei da. Alles in allem gibt es keinen Grund, keine Map-Datei zu verwenden, wenn Sie bereits Ihre eigenen Backtraces decodieren. Jeder Compiler, den ich kenne, kann einen erzeugen. In C++Builder gibt es ein Kontrollkästchen in den Projektoptionen. Sorgen Sie mit gcc dafür, dass –print-map an ld übergeben wird. VC, es ist unter Linker/Debugging.
– Jon Bright
18. April 2011 um 15:35 Uhr
Danke für die Erklärung. Das Problem ist, dass ich eine statische Bibliothek verteile, die einen integrierten Speicher-Debugger enthält. Alles funktioniert gut, außer dem Backtrace unter Windows.
– Macmade
18. April 2011 um 15:52 Uhr
Weg gefunden mit CaptureStackBackTrace. Danke für die Hilfe : )
– Macmade
19. April 2011 um 12:55 Uhr
chksr
@ Jon Bright: Sie sagen “wer wusste, ob der Stapel gültig ist …”: Nun, es gibt eine Möglichkeit, dies herauszufinden, da die Stapeladressen bekannt sind. Angenommen, Sie benötigen natürlich einen Trace im aktuellen Thread:
Mein “GetTEB()” ist NtCurrentTeb() von NTDLL.DLL – und es ist nicht nur Windows 7 und höher, wie in der aktuellen MSDN angegeben. MS verschrottet die Dokumentation. Es war lange da. Wenn Sie den ThreadEnvironment Block (TEB) verwenden, benötigen Sie ReadProcessMemory() nicht, da Sie die Unter- und Obergrenze des Stacks kennen. Ich nehme an, das ist der schnellste Weg, es zu tun.
Mit dem MS-Compiler kann GetEBPForStackTrace() sein
als einfache Möglichkeit, EBP des aktuellen Threads zu erhalten (aber Sie können jedes gültige EBP an diese Schleife übergeben, solange es für den aktuellen Thread gilt).
Einschränkung: Dies gilt für x86 unter Windows.
14087600cookie-checkWin32 – Rückverfolgung von C-Codeyes
Eigentlich der Artikel, der in Ihrem verlinkten Beitrag als Antwort gegeben wird (codeproject.com/KB/threads/StackWalker.aspx) kann auch als Leitfaden zum Erfassen des Stapels eines Threads verwendet werden. Es gibt Antworten auf alle Ihre Fragen. Außerdem gibt es einen Quellcode, den Sie verwenden können, um zu verstehen, wie man es selbst macht. Versuchen Sie, den Artikel nach unten zum Abschnitt “Sehenswürdigkeiten” zu scrollen.
– bezmax
17. April 2011 um 19:20 Uhr
Danke für den Kommentar 🙂 Immer noch kein Glück … stackoverflow.com/questions/5705650/…
– Macmade
19. April 2011 um 9:31 Uhr