
Andre strategier til debugging af C/C++ kode på Linux?
Jeg sidder og debugger en store klump C/C++ kode på en Linux-maskine - ca. 500.000 linier vel. Jeg er ved at være lidt muggen fordi mine normale debugging-værktøjer ikke giver hjælp om problemet. Lad mig lige forklare lidt mere om hvad jeg normalt laver af ulykker og hvordan jeg finder problemets rod. Jeg laver en del kode i C/C++ med malloc/new-kode og jeg har en sjat modul-tests, som kører fint. Der er "styr på" at al hukommelse frigives fint efter brug i de tilfælde.
Jeg har nu en segmenteringsfejl, hvor der er nul hjælp til hvor det sker. gdb/ddd har ingen "backtrace" information (kør "bt" efter fejlen sker) og ddd melder endda intern fejl. Jeg har prøvet at indkredse fejlen med
- valgrind/valkyrie - som køre programmet uden ændringer. Langsomt med normalt super godt til at vise allokeringsfejl.
- purify - kommercielt program, som ændrer link-fasen og instrumenterer koden.
- insure++ - kommercielt program, som reelt omskriver koden og så oversætter den. For hver array-adgang indsættes kode som checker at index er lovligt eller ej. Og tilsvarende laves tonsvis af andre godt checks.
Her står jeg med fejl, som ikke findes med nogen af ovenstående. Er der nogle tools, som jeg har overset til "heavy"-debugging? Jeg er nok ikke interesseret i statisk kode-analyse her - kun dynamisk kodeanalyse.
Jeg er på vej til at rulle tilbage til gode gamle "printf" 
Alle kommentarer modtages med stor interesse!
/pto
Peter Toft er senior specialist hos Renesas Mobile og har blogget om open source og Linux siden Version2's begyndelse. Blogger også jævnligt om andre sjove teknologi-områder.
Follow @petertoftKommentarer (28)
Bruger du fork eller lignende som kan forvirre gdb?
gdb burde altså kunne fortælle dig hvor i memory segfault'en sker.
Nej - der er ikke forks i min kode.
Jeg har nu fået gdb til at fortælle om en fejl i 0x090c140e in colon_greater ()
at ...build-gcc-4.2.4/i686-pc-linux-gnu/libstdc++-v3/include/bits/stl_iterator.h:652
... og ingen oplagt kobling til min egen kode.
Hvor i alverden får man 500000 linjer kode, samt det utaknemmelige job at debugge skidtet fra?
Det er faktisk 750.000 linier - det meste er autogenereret fra et udviklingsværktøj vi bruger
Debugge autogenereret kode er en lidet missundelsværdig aktivitet:-) Held og lykke (du behøver det!)
Hvilken type segmenteringsfejl - adresse 0x0 eller noget mere obskurt?
Af det du har kopieret kunne det se ud til at fejlen udløses i C standardbiblioteket. Dog undrer referencen til colon_greater mig, da der kun er ganske få hits på dette navn via Google (ingen i Google code) - så ville tro det kommer andetsteds fra. Måske dette er et clue?
Viser gdb virkelig ikke et trace af call stacken? Det undrer mig meget - men i så fald kan du blive nødt til at gå stakken igennem manuelt og 'bladre tilbage' til du finder en instruktionspointer der ligger indenfor din egen kode.
Selvom fejlen udløses i C standardbiblioteket er den jo nok i den kaldende kode. Evt. i parametrene til selve kaldet eller også via memory corruption i omkringliggende kode. Nu siger du godt nok du bruger en del værktøjer til at checke for dette. Men i hvert fald valgrind (har ikke erfaring med de andre programmer) laver ikke boundschecking på arrays allokeret på stakken ligesom en del andre typer fejl der kan lede til memory corruption heller ikke altid fanges.
Hej Morten - der kom kun det ud du ser ovenfor. Hvad mener du konkret med bladre tilbage?
Prøv at køre programmet med en syscall-tracer og se hvad det/de sidste syscalls før crashet er, det giver ofte et ret godt indtryk af hvor i koden du var da det væltede.
Poul-Henning
Har du prøvet en anden version af GCC? Måske anvender det autogenererede kode funktionalitet, der har været deprecated længe, og som helt er forsvundet i din version af GCC, så derfor: Kan programmet køre, hvis det er oversat med GCC-3.4?
Hej Morten - der kom kun det ud du ser ovenfor. Hvad mener du konkret med bladre tilbage?
Nogle gange er stacken corrupted/overskrevet (fx ved at skrive udover et lokalt array), så kan gdb ikke vise nogen backtrace.
Du kunne så dumpe stackindholdet manuelt og lede efter begyndelsen på en tidligere stack frame, fx ved at kikke efter en kodeadresse, og sætte gdb til at anvende denne.
Ellers er der kun én udvej: http://www.mcdcareers.co.uk/html/apply.htm
Har du prøvet at kigge i koden og indkredse rent logisk hvor du antager fejlen opstår. Når du har gjort det kan du med fordel steppe igennem via debuggeren :)
Det lugter af et STL problem. Har du prøvet at benytte checked STL? Det kunne være du kunne fange fejlen på den måde.
Jeg udvikler normalt på x86 men target-platformen er noget andet.
Derfor har jeg altid sat signal handlers op for seg-faults etc.
Jeg har så implementeret stack rewind til de forskellige platforme skrevet til log-fil.
Det kan jeg så holde op imod mapfilen uanset platform og uanset om det er i test/udviklings-sammenhæng eller i released kode (I sidstnævnte opstår der jo aldrig fejl :-)).
Men... efter som GDB ikke kan hjælpe er du jo nok ude i en vild-faret pointer eller noget stack-korruption.
Du kunne prøve at skifte compiler. Man kan få gratis trial af ICC til Linux.
Bruger du STL helpers/extensions til GDB?
Hvis ikke så kunne du kigge på GCC 4.5 som har fået indbygget support for python prettyprinters til debugging af STL kode. Se GCC 4.5 changelog på http://gcc.gnu.org/gcc-4.5/changes.html
Du bygger ikke med optimering, vel?
Prøv evt. gdb og/eller gcc i nyere/ældre/anderledes version(er)...
Iøvrigt er det jo slet ikke sikkert at fejlen er i "din" kode... Det ville ikke være første gang at en klump autogenereret kode sender gcc på eventyr...
Jeg har oplevet et lignende symptom med segfaults/debugger problemer.
.. i mit tilfælde var der uoverensstemmelse mellem header for shared objects og den '.so' der blev loaded.
.. check library path.
.. kør ldconfig ved ændringer.
( undskyld hvis det var trivielt, men symptomerne lignede meget )
Dog ville jeg sandsynligvis prøve at debugge med et gui-værktøj som kdevelop - og sætte breakpoints og se hvor langt koden når.
(Umiddelbart ville jeg forestille mig, at det gik hurtigere end printf metoden, hvor man hele tiden skal rette og genkompilere ....)
Eller også hvis programmet er multitrådet kan man feks. bruger http://i1.dk/mta/. Det har jeg brugt et par gange
/Kim
Hvor meget erfaring har du med det tool, der har genereret koden? Modenhed?
Måske duer det bare ikke? ;-)
Std. disclaimer^Wexcuse: jeg sidder til daglig med PowerPC så det der x86 er lidt rustent. Men altså. Hvis nu vi har
[code=c]
bool evil()
{
intx=(int)alloca(1);
*(x+11)=0;
__asm("ud2");
}
void no()
{
evil();
}
void _do()
{
no();
}
void google()
{
_do();
}
int main()
{
google();
}
[/code]
så ser det sådan her ud hos mig:
[code=text]
petera-segarally:~/txy$ g++ -o t -g t.c
petera-segarally:~/txy$ gdb ./t
GNU gdb 6.4.90-debian
Copyright (C) 2006 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 "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) run
Starting program: /home/petera/txy/t
Program received signal SIGILL, Illegal instruction.
evil__Fv () at t.c:5
5 __asm("ud2");
(gdb) bt
#0 evil__Fv () at t.c:5
#1 0x00000000 in ?? ()
(gdb)
[/code]
Og så er gode dyr som bekendt rådne. Men hvis vi gør som Lasse og Morten foreslår:
[code=text]
(gdb) x/16x $ebp
0xbfaf8fa8: 0xbfaf8fb8 0x00000000 0xb7ff8ff4 0x080497fc
0xbfaf8fb8: 0xbfaf8fc8 0x0804851b 0xbfaf9064 0xb7ff8ff4
0xbfaf8fc8: 0xbfaf8fd8 0x0804853b 0xb7e41c8c 0xb7f63ff4
0xbfaf8fd8: 0xbfaf8fe8 0x0804855b 0x00000000 0xb7ff8cc0
(gdb) info symbol 0x0804851b
_do(void) + 11 in section .text
(gdb) info symbol 0x0804853b
google(void) + 11 in section .text
(gdb) info symbol 0x0804855b
main + 11 in section .text
(gdb)
[/code]
.. kan vi spadsere igennem stakken. Selv går vi glip af den stakframe som blev overskrevet, men vi har da "main", "google", "_do", XXXX og "evil". Så måske vi kan regne XXXX ud fra konteksten :-).
Bemærk at på $ebp begynder en kæde af links ned igennem stakken. Lige efter links'ne i kæden ligger den tilhørende kode adresse (kan nemt kendes da den hedder 0x080.... som synes at være der min kode ligger i dag).
Selvfølgelig kan der være sket mange andre ting i dit tilfælde. Men ovenstående illustrerer forhåbentlig hvad der menes med at gå stakken igennem manuelt.
Åhh. Nu er jeg spændt på hvad Version2's kommentar-system gør ved mine fine kode snippets :-)....
kgdb og kdevelop er ligesom ddd frontends til gdb - dvs. man kan ca få lige så meget stack og kalde info i dem alle. Jeg ved godt at der er lidt forskelle, men ca...
Optimering er altid giftigt - nogle gange forsvinder der kode og variable, som bare ikke må forsvinde. Det er jeg med på og har smidt det ud
Det lyder simpelt, men prøv at static compile programmet og uden optimeringer.
strace (tracing af systemkald) med Ubuntu:
http://manpages.ubuntu.com/manpages/karmic/man1/strace.1.html
Nu er du er ikke lige newbie ud i programmering, men der er god hjælp her til andre som måtte læse med og vil bruge strace:
https://wiki.ubuntu.com/Strace
Kig i i log filer: syslog, messages, og user.log i /var/log.
Bruger dit program for meget RAM lukker kernen dit program ned. 'for meget' er relativt til mængden af fri hukommelse.
Tror du at fejlen har noget med kernen at gøre, så kør (som root):
echo N > /proc/sys/kernel/printk
for at få mere debugging information ud af kernen.
N er et tal er mellem 0 til 8 incl. ( er flest debugging beskeder. 0 er kun de mest kritiske beskeder.
Lasse og Peter's beskrivelser er gode forklaringer af hvad jeg mente med 'bladre tilbage' :)
Jeg studser stadigvæk over det der colon_greater(), har du fået kigget mere på det?
Jeg har iøvrigt før været ude for at debuggeren har oplyst et forkert funktionsnavn som synderen (i.e. maskinkodeinstruktionen der udløste fejlen befandt sig ikke kodemæssigt i den oplyste funktion). Hvordan det kan ske er mig en gåde, men har aldrig haft tid til at undersøge det nærmere. Antageligt er der tale om fejl i debuggeren eller om en meget uheldig korruption af stack der fører til forkert fortolkning fra debuggerens side. Alternativt korruption af debuggerens memory-strukturer.
Jeg har nu vundet. I mit tilfælde havde jeg en stabil kodebase - hvorefter jeg havde lavet en solid mængde ændringer i noget plain C/C++ kode OG en lille ændring i noget "andet" som skal autogenereres og linkes ind i min kode.
Efter 10 timers debug-øvelse, der kørte efter melodien;
* Hvad melder valgrind/valkyrie?
* Melder purify noget andet?
* Hvad melder Insure++?
* Flere unit tests
* Fuld clean up i memory space etc.
* Forsøg med at allokere mindre hukommelse og lignende
* Reducere mine ændringer ned mod den kendte valide tilstande af koden.
Desværre brugte jeg det meste af tiden i min kode, men i sidste del - hvor jeg trækker mine ændringer ud en for en, pludselig stod jeg med nul ændringer i min kode og kun lille ændring i noget "andet".
Det "andet" var ude af min kontrol og det har været stabilt i de sidste 5+ år. Men her endte jeg med en testcase, som skal riples ud til nogle andre glade kodedrenge som sikkert bliver glade for at få en fejltest i deres kode i noget de helt sikkert har glemt alt om :)
Jeg vil gerne takke jer alle - og en masse offline mails fra mange spændende personer - angående emnet.
/pto
Godt det er løst. Mon ikke colon_greater befandt sig i "det andet" (som sikkert er parser-relateret).

