====== Ladění a odvšivování C ====== ===== Obecné nástroje ===== ==== ps ==== * rodič/potomek * PID (Process ID), PPID (Parent), PGID (Process Group ID leader), ps axjf | less PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 12052 12540 12540 12540 ? -1 Ss 0 0:00 \_ sshd: flowmon [priv] 12540 12542 12540 12540 ? -1 S 500 0:03 | \_ sshd: flowmon@pts/0 12542 12543 12543 12543 pts/0 24512 Ss 500 0:00 | \_ -bash 12543 24511 24511 12543 pts/0 24512 S 0 0:00 | \_ su 24511 24512 24512 12543 pts/0 24512 S+ 0 0:00 | \_ bash * vlákna - jeden proces může mít několik vláken. Všechna vlákna mají stejný PID a Parent PID. LWP (Lightweight Processes) je identifikátor vlákna. ps -eFL | grep "/bin/some" UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD 503 26207 10278 26207 11 4 1455062 4857636 6 Nov15 ? 13:28:28 /bin/something 503 26207 10278 26207 11 4 1455062 4857636 6 Nov15 ? 13:28:28 /bin/something 503 26207 10278 26768 0 4 1455062 4857636 1 Nov15 ? 00:00:00 /bin/something 503 26207 10278 1849 0 4 1455062 4857636 1 Nov18 ? 00:00:00 /bin/something 503 26207 10278 1850 0 4 1455062 4857636 3 Nov18 ? 00:00:00 /bin/something * start time - datum/čas spuštění procesu. V kombinaci s time nám říka, jak proces dlouho běží a jak moc využívá procesor. Například následující výpis říká, že procesy postgress byly spuštěny 1.12.2013 a na procesoru běžely necelé dvě minuty (přestože teď je už 2.12 9:30). Procesy tedy skoro nic nedělají. ps -ef | grep postgress UID PID PPID C STIME TTY TIME CMD postgres 7534 4669 0 Dec01 ? 00:01:04 postgres: somebody somebody 127.0.0.1(37573) VACUUM postgres 13828 4669 0 Dec01 ? 00:01:28 postgres: somebody somebody 127.0.0.1(35066) VACUUM postgres 14167 4669 0 Dec01 ? 00:01:11 postgres: somebody somebody 127.0.0.1(60953) VACUUM postgres 16436 4669 0 Dec01 ? 00:01:19 postgres: somebody somebody 127.0.0.1(58098) VACUUM * proces může mít různé stavy: * **D** - Uninterruptible sleep (usually IO). Tyto procesy většinou způsobují I/O waiting. * **S** - Interruptible sleep (waiting for an event to complete). Proces není na CPU. * **R** - Running or runnable (on run queue). * existují ještě modifikátory například **l** (multi-thread) - tedy Rl znamená spuštěný multi-thread process ps ax | grep postgress PID TTY STAT TIME COMMAND 7534 ? D 1:04 postgres: somebody somebody 127.0.0.1(37573) VACUUM 13828 ? D 1:28 postgres: somebody somebody 127.0.0.1(35066) VACUUM 14167 ? D 1:12 postgres: somebody somebody 127.0.0.1(60953) VACUUM 16436 ? D 1:19 postgres: somebody somebody 127.0.0.1(58098) VACUUM 17723 ? D 1:07 postgres: somebody somebody 127.0.0.1(47033) VACUUM ==== top ==== * spotřeba CPU, paměti, ... * pomoci klavesy **f** lze zapnout/vypnout ruzne sloupecky s info o procesu, napriklad swap * pomoci **< >** lze provest trideni podle sloupcu (reverzni trideni **R**) * pomoci **H** lze zapnout zobrazovani threadu * pomoci **1** lze zapnout vypis vsech CPU jader: * sloupec %wa je velice dulezity, jde o I/O waiting. Pokud je I/O waiting skoro 100%, tak je system zatizen (nepouzitelny), i kdyz ma CPU %us velice male. top - 18:20:11 up 83 days, 10:59, 1 user, load average: 0.06, 0.10, 0.09 Tasks: 137 total, 2 running, 135 sleeping, 0 stopped, 0 zombie Cpu0 : 5.1%us, 1.0%sy, 0.0%ni, 92.5%id, 1.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu1 : 5.2%us, 0.9%sy, 0.0%ni, 93.1%id, 0.7%wa, 0.0%hi, 0.0%si, 0.0%st * CPU load je specialni hodnota, ktera rika, jak jsou CPU procesory/jadra vyuzivany [[http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages|info]]. * u jedno jadroveho CPU: * CPU load 0.5 znamana, ze procesor 50% casu nic nedela * CPU load 1.0 znamena, ze procesor je vytizen na 100% * CPU load vetsi nez 1.0 rika, ze existujici procesy cekajici na procesor. System je pretizen. * u vice jadrovych je to analogicky, CPU load 4.0 u 4 jadroveho CPU zneman 100% load * vypis pameti rika: * pamet celkem (mem total), kolik je ji pouzito (Mem used), kolik je volne (Mem free), * jak velky je swap (Swap total), kolik ze swapu se pouziva (Swap used), kolik je swapu volneho (Swap free) - swap se casto pouziva i pro uvolneni bloku hlavni pameti, ktere se dlouho nepouzivaly. Nemusi jit tedy o nedostatek hlavni pameti. * kolik se pouziva jako file system diskova cache (cached) - to je pamet, kterou lze v pripade potreby uvolnit. Je zahrnuta take v Mem used. Mem: 2059524k total, 1823764k used, 235760k free, 212016k buffers Swap: 1028312k total, 90556k used, 937756k free, 1218500k cached * vypis procesu dale rika: * VIRT - veskera pamet adresovana procesem. Kod, data, sdilene knihovny, swap, ... . Muze byt vetsi nez je fyzicka pamet. Zapocitava se sem totiz i napriklad adresovani velkych souboru ulozenych ve skutecnosti pouze na disku. * RES - skutecne spotrebovana pamet. RES = CODE + DATA. %MEM = RES/TOTAL. * SHR - sdilena pamet, procesy casto sdili pamet. * TIME - kolik procesoroveho casu proces spotreboval. Vetsinou je nizsi nez doba behu programu (program nema pridelen CPU porad) PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 16069 somebody 20 0 1385m 95m 60m S 10,0 1,2 2:08.56 vlc 22567 somebody 20 0 1889m 274m 97m S 8,3 3,5 131:34.02 gnome-shell 22291 somebody 20 0 324m 129m 58m S 5,7 1,6 71:22.25 Xorg 5685 somebody 20 0 642m 9m 5764 S 3,7 0,1 107:27.06 pulseaudio ==== lsof ==== * zobrazí file deskriptory (soubory, sockety, pipe), které má proces aktuálně otevřené * seznam obsahuje rovněž spuštěnou binárku a všechny knihovny * dále zde lze zjistit pracovní adresář * u souborů lze zjistit, jaký je aktuální seek * příklad: * pracovní adresář je /home/flowmon/commleak * předchozí adresář je / * binárka je /bin/bash * stdin (0), stdout (1) i stderr (2) je navěšen na terminál /dev/pts0 lsof -p 16901 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME bash 16901 root cwd DIR 8,3 4096 464983 /home/flowmon/commleak bash 16901 root rtd DIR 8,3 4096 2 / bash 16901 root txt REG 8,3 801816 48121 /bin/bash bash 16901 root mem REG 8,3 144776 547197 /lib64/ld-2.5.so .. bash 16901 root 0u CHR 136,0 3 /dev/pts/0 bash 16901 root 1u CHR 136,0 3 /dev/pts/0 bash 16901 root 2u CHR 136,0 3 /dev/pts/0 ==== strace ==== * Nástroj na monitorování systémových volání * Umožňuje zobrazit, co program dělá (pouze systémová volání). Typicky zde jde vidět zda čeká na nějaké I/O, zda na nečem necyklí, zda nečeká na nějký zámek, ... * Vypisuje vždy systémové volání s argumenty a návratovou hodnotou. Například u operace write je prvním argumentem file deskriptor. File deskriptory je pak možné dohledat přes lsof -p (fd 1 je typicky stdinout, fd2 je typicky stderr) * výpis strace je zkrácený na 32 znaků, pokud je potřeba vidět víc, je třeba nastavit větší délku přes strace -s * užitečné volby * -tt detailed timestamps * -f also strace any child processes * -v be very verbose, even with common structures * -o write output to file * -s N capture up to N characters of strings, rather than abbreviating with ... ==== gdb ==== * umoňuje se navěsit i na běžící proces * vypíše aktuální instrukci, hodnoty paměti, .. * výpis backtrace, krokování * [[gdb|více zde]] ==== gcore ==== * kompletní dump procesu včetně celé paměti, k pozdější analýze * alternativně lze nastavit * v environment přes ''ulimit -c unlimited'' * globálně, pro uživatele, přes ''/etc/security/limits.conf'' ==== pmap ==== * výpis alokované paměti ==== time ==== * měření času spušteného procesu * ukazuje časy * real - jak dlouho proces skutečně běžel * user - kolik strávil execution time v user space * sys - kolik strávil execution time v user space * příklad real 2m4.884s user 0m52.677s sys 0m2.528 * pokud real < user, tak proces využívá paralelismus * (real - (user+sys)) odpovídá I/O waiting (u single thread procesu) ====== Diagnostika neoprávněného přístupu do paměti ====== V céčku jsou nejoblibenější chyby typu segmentation fault. Objevují se totiž náhodně, hlavně když si programátor myslí, že už to má určitě dobře ===== Nástroje ===== ==== valgrind ==== * program pro hledání chyb v programu - špatná práce s pamětí. * dokáže dobře detekovat chyby v přístupu mimo alokovanou paměť * je pomalý, u komplikovanějšího kódu mrzne. * má několik rozšíření, například callgrind (vypisuje volání... grafické rozšíření kcachegrind) ==== address sanitizer ==== * zpomaluje program méně a umí hledat víc chyb než valgrind * podpora v gcc od verze 4.8 * podpora v clang od verze 3.3. * alternativní překladač C/C++. * měl by překládat rychleji než gcc a obsahuje kontrolu paměti. ==== Google's tcmalloc heapprofiler ==== * starý: http://goog-perftools.sourceforge.net/doc/heap_profiler.html * nový: https://github.com/gperftools/gperftools * kompilace * spustit ./autogen.sh * na x86_64 vyžaduje knihovnu libunwind, bez toho nefunguje trasování volání, více info v INSTALL souboru * zkoušel jsem s LD_PRELOAD i přímo s -ltcmalloc při kompilaci binárky. Výsledek byl asi stejný. * zkoušel jsem i ten auto leaks checker PPROF_PATH=/usr/local/bin/pprof HEAPCHECK=normal HEAPPROFILE=/tmp/heapprof7 /data/xx_memtest Starting tracking the heap WARNING: Perftools heap leak checker is active -- Performance may suffer Dumping heap profile to /tmp/heapprof7.0001.heap (476 MB currently in use) Dumping heap profile to /tmp/heapprof7.0002.heap (667 MB currently in use) Have memory regions w/o callers: might report false leaks Leak check _main_ detected leaks of 600000000 bytes in 2 objects The 2 largest leaks: Using local file /data/xx_memtest. Leak of 500000000 bytes in 1 objects allocated from: @ 4005ea main @ 3a0001d9f4 __libc_start_main @ 400529 _start Leak of 100000000 bytes in 1 objects allocated from: @ 40061c main @ 3a0001d9f4 __libc_start_main @ 400529 _start * profilování paměti * proces je zpomalen, naměřil jsem cca 14x (dříve to zvládlo zapsat 2.4M flow, při profilování 0.17M) * snapshoty se ukládají v pravidelných intervalech (default, po alokaci zhruba 1 GB) do zvoleného adresáře * snapshoty jsou v interním formátu, programem pprof lze převést do svg, postscript, callgrind formátu * export do postscript nebo svg vyžaduje nějaké desktopové knihovny/binárky, nezkoušel jsem * export do callgrind je super, lze otevřít v kcachegrind * jde zde vidět kdo jakou funkci zavolal a kolik má daná funkce aktuálně alokováno paměti ve snapshotu * například: když vyberu funkci main, tak vidím jaké jiné funkce (alokující nějakou paměť) se z main zavolaly (callee) a kolik každá alokovala. Postup lze opakovat vybráním nějaké ze zavolaných funkcí. Krásně se to analyzuje. * pprof vyžaduje cestu k binárce a snaphostu * je žádoucí, aby binárka byla zkompilována s ladícími symboly (přepínač -g) * místo binárky lze použít i samotné debug symboly ze souboru binárka.debug * ukázka spuštění programu s profilováním paměti $ HEAPPROFILE=/data/ads/tmp/ads-nf-loader.memprof LD_PRELOAD=/data/ads/tmp/bari-memprof/libtcmalloc.so.4.2.6 ads-nf-loader * ukázka jednoduchého textového výpisu snaphostu $ pprof --text /usr/lib/debug/usr/local/bin/ads-nf-loader.debug /data/ads/tmp/ads-nf-loader.memprof.0085.heap Using local file /usr/lib/debug/usr/local/bin/ads-nf-loader.debug. Using local file /data/ads/tmp/ads-nf-loader.memprof.0085.heap. Total: 673.8 MB 572.2 84.9% 84.9% 572.2 84.9% InitCacheBuff 48.5 7.2% 92.1% 48.5 7.2% mainsub 26.4 3.9% 96.0% 26.4 3.9% tpxmit 20.0 3.0% 99.0% 20.0 3.0% NewFile 5.0 0.7% 99.8% 5.0 0.7% LZO_initialize 0.9 0.1% 99.9% 0.9 0.1% SQLAllocEnv 0.4 0.1% 100.0% 0.4 0.1% Insert_Extension_Map .. ==== Heaptrack ==== * http://milianw.de/blog/heaptrack-a-heap-memory-profiler-for-linux ==== mtrace ==== * součástí GNU C knihovny * vyžaduje úpravu zdrojového kódu * umí detekovat neuvolněnou paměť Memory not freed: ----------------- Address Size Caller 0x0000000001b75460 0x4 at /home/bari/Plocha/heaptest/mtrace/badcode-mtrace.c:10 * popis: https://en.wikipedia.org/wiki/Mtrace ==== memprof ==== http://www.secretlabs.de/projects/memprof/ ==== gdb-heap ==== * zní to pěkně, ale nikdy se mi to nepodařilo úplně rozběhnout * věčně nějaké nepochopitelné python syntax errors * https://fedorahosted.org/gdb-heap/wiki ==== Dmalloc ==== http://dmalloc.com/ ==== vlastní nástroj ==== * vlastní implementace malloc, free, ... * http://danluu.com/malloc-tutorial/ * [[http://code.google.com/p/address-sanitizer/wiki/ComparisonOfMemoryTools|srovnání nástrojů pro ověřování správné práce s paměti]] ===== LD_PRELOAD ====== * umožňuje přepsat funkce poskytované dynamickými knihovnami v programu pomocí načtení jiné knihovny * z bezpečnostních důvodů * u programů s nastaveným setuid bitem musí mít i alternativní knihovna nastaven setuid bit * pěkná ukázka: http://samanbarghi.com/blog/2014/09/05/how-to-wrap-a-system-call-libc-function-in-linux/ ===== Backtrace ====== * pro ladění segfault se hodí výpis backtrace, ten můžeme získat [[http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes|zdroj]] * úpravou zdrojových kódů, voláním funkce backtrace_symbols_fd pri obsluze signalu sigfault * spuštěním binárky přes wrapper catchsegv (lze použít i LD_PRELOAD=/lib/libSegFault.so) * vygenerováním kompletního dumpu paměti nastavením ulimit v terminálu před spuštěním programu * aby nám výpis vůbec něco řekl, je nutné mít k dispozici debug symboly (překlad adres na proměnné/funkce)