Obsah
1. Trasování a ladění nativních aplikací v Linuxu
2. Utilita ltrace – výpis informací o volaných knihovních funkcích
3. Ukázka použití utility ltrace
4. Zjištění doby trvání volané funkce a filtrace výstupu
5. Zjištění statistiky volaných funkcí
6. Utilita strace – výpis informací o volaných systémových funkcích
7. Ukázka použití utility strace
12. Obsah následujícího článku
13. Demonstrační příklady použité v dnešním článku
1. Trasování a ladění nativních aplikací v Linuxu
V operačním systému Linux můžeme pro ladění a trasování nativních aplikací použít hned několik nástrojů, které se od sebe liší jak způsobem použití, tak i svými možnostmi. Dnes se nejprve zmíníme o dvou základních nástrojích určených především pro sledování volání funkcí z knihoven popř. sledování volání systémových funkcí. Tyto nástroje se jmenují ltrace (kapitola číslo 2) a strace (kapitola číslo 6). Pokud možnosti těchto dvou nástrojů nestačí, je možné použít složitější prostředky typu GNU Debuggeru (kapitola číslo 8), prozatím neoficiální verze nástroje DTrace (kapitole 9) nebo sofistikované utility nazvané SystemTap (jedenáctá kapitola).
Typická aplikace běžící v uživatelském prostoru volá funkce různých (nativních) knihoven, zejména pak knihovny glibc. Funkce z glibc následně volají systémové funkce implementované (zjednodušeně řečeno) v kernelu nebo v jeho modulech:
+----------+ | | | aplikace | | | +----------+ | | v +----------+ | | | glibc | | | +----------+ | | v +----------+ | | | jádro | | | +----------+
Pro sledování volání funkcí z nativních knihoven se používá ltrace, pro systémové volání strace, samotný GNU Debugger je možné použít pro ladění aplikace i knihoven a SystemTap je použitelný ve všech úrovních, a to včetně sledování stavů jádra operačního systému (KGDB si prozatím popisovat nebudeme):
+----------+ | |..... gdb | aplikace | | |..... SystemTap +----------+ | |...... ltrace v +----------+ | |..... gdb | glibc | | |..... SystemTap +----------+ | |...... strace v +----------+ | |..... SystemTap | jádro | | |..... KGDB +----------+
2. Utilita ltrace – výpis informací o volaných knihovních funkcích
Prvním nástrojem, s nímž se v dnešním článku ve stručnosti seznámíme, je utilitka nazvaná jednoduše ltrace. Tento nástroj je možné použít ve chvíli, kdy potřebujeme zjistit, které knihovní funkce se volají popř. jak často se tyto funkce volají a kolik času v nich některá aplikace tráví. Takto zjištěné statistické informace lze použít různým způsobem, například pro vyhledání problémových či pomalých částí kódu, zjištění, ve kterých místech dokází k chybě, zjištění způsobů alokace a dealokace paměti (funkce malloc, free a příbuzné), popř. k jednoduchému trasování (což je asi nejběžnější). V tom nejjednodušším případě se ltrace používá následujícím způsobem:
ltrace jméno_binární_aplikace parametry_aplikace
popř. pokud je zapotřebí předat parametry i samotnému nástroji ltrace:
ltrace parametry_ltrace jméno_binární_aplikace parametry_aplikace
3. Ukázka použití utility ltrace
Podívejme se nyní na základní použití nástroje ltrace. Použijeme přitom demonstrační příklady, jejichž zdrojové kódy jsou zmíněny ve třinácté kapitole. Takže začněme:
ltrace ./hello __libc_start_main(0x40053d, 1, 0x7fffd2e1f5f8, 0x400560 <unfinished ...> puts("Hello world!"Hello world! ) = 13 +++ exited (status 0) +++
V tomto jednoduchém příkladu se (přímo) mnoho knihovních funkcí nevolá.
ltrace ./random_bitmap __libc_start_main(0x400bf4, 1, 0x7fff18e8d758, 0x400c70 <unfinished ...> puts("processing:") = 12 malloc(16) = 0xa11010 malloc(921600) = 0x7f45fdb46010 memset(0x7f45fdb46010, '\0', 921600) = 0x7f45fdb46010 memset(0x7f45fdb46010, '\0', 921600) = 0x7f45fdb46010 open("/dev/urandom", 0, 037560470000) = 3 read(3, "\270\325\332\340r8\304\354\306A\001S", 1920) = 1920 ... ... ... vynecháno přibližně 480 řádků ... ... ... close(3) = 0 fopen("random.bmp", "wb") = 0xa11030 fwrite("BMF", 54, 1, 0xa11030) = 1 printf("%d pixels written\n", 307200) = 22 fwrite("\270\325\332\340r8\304\354\306A\001S", 921600, 1, 0xa11030) = 1 fclose(0xa11030) = 0 puts("done!\n") = 7 +++ exited (status 0) +++
Povšimněte si, že u funkcí se vypisují i jejich parametry. Můžeme zde vysledovat základní logiku aplikace, od alokace paměti pro hlavičku bitmapy i pro celou bitmapu:
bitmap *p=(bitmap *)malloc(sizeof(bitmap)); /* alokace struktury bitmapy */ if (!p) return NULL; p->width=width; /* naplneni struktury */ p->height=height; p->pixels=(unsigned char *)malloc(3*width*height);
přes čtení náhodných čísel:
int randomDataDevice = open("/dev/urandom", O_RDONLY); size_t i; unsigned char *pixels_ptr = p->pixels; for (i=0; i < p->height; i++) { /* nacteni celeho radku */ ssize_t result = read(randomDataDevice, pixels_ptr, 3*p->width); if (result >= 0) { /* posun ukazatele na dalsi radek */ pixels_ptr+=result; } else { perror("urandom read failed"); close(randomDataDevice); return; } } close(randomDataDevice);
Až po závěrečnou sekvenci:
fout=fopen(fileName, "wb"); if (!fout) return; fwrite(bmp_header, sizeof(bmp_header), 1, fout); printf("%d pixels written\n", width * height); fwrite(p->pixels, 3 * width * height, 1, fout); fclose(fout); puts("done!\n");
Poslední příklad:
ltrace ./fractal_renderer __libc_start_main(0x400ec2, 1, 0x7fff0293d968, 0x400f60 <unfinished ...> puts("processing:") = 12 malloc(16) = 0xf7f010 malloc(921600) = 0x7fb63dc5a010 memset(0x7fb63dc5a010, '\0', 921600) = 0x7fb63dc5a010 memset(0x7fb63dc5a010, '\0', 921600) = 0x7fb63dc5a010 sin(0xf7f010, 1000, 0, 0xf7f010) = 0x3fa11111 ... ... ... vynechána další volání funkce sin - 614400x !!! ... ... ... fopen("result.bmp", "wb") = 0xf7f030 fwrite("BMF", 54, 1, 0xf7f030) = 1 printf("%d pixels written\n", 307200) = 22 fwrite("z\036zz\036zz\036zz\036zz\036zz\036zz\036zz\036zz\036zz\036zz\036"..., 921600, 1, 0xf7f030) = 1 fclose(0xf7f030) = 0 puts("done!\n") = 7 +++ exited (status 0) +++
Zde můžeme vidět, že jasně dominuje volání funkce sin z knihovny libm.
4. Zjištění doby trvání volané funkce a filtrace výstupu
Pokud potřebujete zjistit čas volání nějaké funkce, je možné použít přepínač -t. Výsledek pak vypadá takto:
ltrace -t ./hello 20:17:06 __libc_start_main(0x40053d, 1, 0x7fff74550708, 0x400560 <unfinished ...> 20:17:06 puts("Hello world!"Hello world! ) = 13 20:17:06 +++ exited (status 0) +++
Pokud se namísto přepínače -t použije přepínač -tt, zvětší se přesnost na milisekundy:
ltrace -tt ./hello 20:49:54.521242 __libc_start_main(0x40053d, 1, 0x7fff78e06dd8, 0x400560 <unfinished ...> 20:49:54.521862 puts("Hello world!"Hello world! ) = 13 20:49:54.522886 +++ exited (status 0) +++
Samozřejmě je možné zjistit nejenom okamžik, kdy se do funkce vstupuje, ale i dobu trvání knihovních funkcí, a to díky přepínači -r:
ltrace -r ./hello 0.000000 __libc_start_main(0x40053d, 1, 0x7fffdd8d7208, 0x400560 <unfinished ...> 0.000479 puts("Hello world!"Hello world! ) = 13 0.000985 +++ exited (status 0) +++
Mnohdy nepotřebujeme zjistit všechny volané knihovní funkce, ale pouze vybranou podmnožinu z nich. Zde přichází ke slovu přepínač -e, kterému můžeme předat seznam funkcí, které nás zajímají. Funkce se zapisují ve formě jednoduchého jazyka, což například znamená, že pro filtraci těch funkcí, jejichž volání nás zajímá, se vkládají znaky +:
ltrace -e malloc+free+open+close ./random_bitmap processing: random_bitmap->malloc(16) = 0x2137010 random_bitmap->malloc(921600) = 0x7f4fb333c010 random_bitmap->open("/dev/urandom", 0, 026320350000) = 3 random_bitmap->close(3) = 0 307200 pixels written done! +++ exited (status 0) +++
Naopak je možné při použití znaku – (minus) odfiltrovat ty knihovní funkce, které nás v daném okamžiku nezajímají. Řekněme, že nepotřebujeme sledovat volání funkce read:
ltrace -e -read ./random_bitmap random_bitmap->__libc_start_main(0x400bf4, 1, 0x7fff7c9456e8, 0x400c70 <unfinished ...> random_bitmap->puts("processing:"processing: ) = 12 random_bitmap->malloc(16 <unfinished ...> libc.so.6->(0x7f7140a97bd0, 0x7fff7c945550, 0x7fff7c945540, 0) = 0x7f7140ffa4c0 <... malloc resumed> ) = 0x1ee3010 random_bitmap->malloc(921600) = 0x7f7140ef3010 random_bitmap->memset(0x7f7140ef3010, '\0', 921600) = 0x7f7140ef3010 random_bitmap->memset(0x7f7140ef3010, '\0', 921600) = 0x7f7140ef3010 random_bitmap->open("/dev/urandom", 0, 010077240000) = 3 random_bitmap->close(3) = 0 random_bitmap->fopen("random.bmp", "wb" <unfinished ...> libc.so.6->memalign(568, 0x400cf4, 1, 0) = 0x1ee3030 <... fopen resumed> ) = 0x1ee3030 random_bitmap->fwrite("BMF", 54, 1, 0x1ee3030) = 1 random_bitmap->printf("%d pixels written\n", 307200307200 pixels written ) = 22 random_bitmap->fwrite("Z\226\314H7\316\301\306\340\367iI\324O\332|\005\035![BL\325u\271\335L", 921600, 1, 0x1ee3030) = 1 random_bitmap->fclose(0x1ee3030 <unfinished ...> libc.so.6->(0x1ee3030, 0, 0x1ee3110, 0xfbad000c) = 1 <... fclose resumed> ) = 0 random_bitmap->puts("done!\n"done! ) = 7 libc.so.6->_dl_find_dso_for_object(0x7f7140dd1d90, 0x7f7140dd26c8, 1, -1) = 0x7f7140fd5690 +++ exited (status 0) +++
5. Zjištění statistiky volaných funkcí
Velmi užitečná je volba -c, po jejímž zadání nástroj ltrace zobrazí statistiku volání jednotlivých funkcí s celkovým i průměrným časem, který program v dané funkci strávil. Opět si uveďme příklady:
ltrace -c ./hello Hello world! % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 100.00 0.000455 455 1 puts ------ ----------- ----------- --------- -------------------- 100.00 0.000455 1 total
Zde asi nedošlo k žádnému většímu překvapení.
ltrace -c ./random_bitmap % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 97.81 0.228395 475 480 read 0.73 0.001701 850 2 fwrite 0.44 0.001032 516 2 memset 0.29 0.000670 335 2 puts 0.24 0.000559 279 2 malloc 0.13 0.000299 299 1 fclose 0.10 0.000245 245 1 fopen 0.10 0.000231 231 1 printf 0.09 0.000214 214 1 open 0.07 0.000174 174 1 close ------ ----------- ----------- --------- -------------------- 100.00 0.233520 493 total
Povšimněte si, že funkce read() se volala přesně 480×, což odpovídá zdrojovému kódu:
/* rozmery bitmapy */ #define BITMAP_WIDTH 640 #define BITMAP_HEIGHT 480 ... ... ... void fillBitmap(const bitmap *p) { int randomDataDevice = open("/dev/urandom", O_RDONLY); size_t i; unsigned char *pixels_ptr = p->pixels; for (i=0; i < p->height; i++) { /* nacteni celeho radku */ ssize_t result = read(randomDataDevice, pixels_ptr, 3*p->width); if (result >= 0) { /* posun ukazatele na dalsi radek */ pixels_ptr+=result; } else { perror("urandom read failed"); close(randomDataDevice); return; } } close(randomDataDevice); }
ltrace -c ./fractal_renderer processing: 307200 pixels written done! % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 99.99 72.237624 117 614400 sin 0.00 0.001307 653 2 fwrite 0.00 0.000695 347 2 puts 0.00 0.000672 336 2 memset 0.00 0.000487 487 1 fclose 0.00 0.000409 409 1 fopen 0.00 0.000338 169 2 malloc 0.00 0.000160 160 1 printf ------ ----------- ----------- --------- -------------------- 100.00 72.241692 614411 total
Podle očekávání (viz čtvrtou kapitolu) nás určitě nepřekvapí, že nejvíce času se celkově (kumulativně) strávilo ve funkci sin, i když tato funkce nepatří mezi nejpomalejší (ve skutečnosti je ze všech volaných funkcí nejrychlejší).
6. Utilita strace – výpis informací o volaných systémových funkcích
Druhým nástrojem, s nímž se dnes seznámíme, je utilita nazvaná příhodně strace. Zatímco výše popsaná ltrace sloužila k výpisu volaných knihovních funkcí (pocházejících například z knihovny glibc), je utilita strace určena ke zjištění systémových volání, a to nezávisle na tom, kde toto volání vzniklo (většinou se jedná opět o knihovnu glibc, ovšem nemusí tomu tak být vždycky. Podobně jako v případě ltrace je možné nástroj strace použít buď pro spuštění laděné/trasované aplikace, nebo je možné se přes přepínač -p{pid}) připojit k již běžící aplikaci (to je velmi užitečné například při sledování běhu „živých“ dlouhoběžících serverových aplikací apod.). Význam některých přepínačů je u strace i ltrace shodný, což je samozřejmě výhodné.
7. Ukázka použití utility strace
Podívejme se nyní na základní způsob použití strace. Pro jednoduchost ji vyzkoušíme pouze oproti programu Hello world, který ve svém kódu volá jedinou funkci puts a nic jiného. Ovšem z pohledu systémových volání je tento program mnohem složitější, o čemž se lze velmi snadno přesvědčit:
strace ./hello execve("./hello", ["./hello"], [/* 53 vars */]) = 0 brk(0) = 0xa52000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1af21d000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=132679, ...}) = 0 mmap(NULL, 132679, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fd1af1fc000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\37\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1840928, ...}) = 0 mmap(NULL, 3949248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fd1aec38000 mprotect(0x7fd1aedf3000, 2093056, PROT_NONE) = 0 mmap(0x7fd1aeff2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7fd1aeff2000 mmap(0x7fd1aeff8000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fd1aeff8000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1af1fb000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1af1f9000 arch_prctl(ARCH_SET_FS, 0x7fd1af1f9740) = 0 mprotect(0x7fd1aeff2000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7fd1af21f000, 4096, PROT_READ) = 0 munmap(0x7fd1af1fc000, 132679) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 7), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fd1af21c000 write(1, "Hello world!\n", 13Hello world! ) = 13 exit_group(0) = ? +++ exited with 0 +++
Povšimněte si způsobu výpisu – u každého systémového volání jsou uvedeny parametry, a to v některých případech inteligentně – namísto číselné konstanty se používá symbolická konstanta PROT_READ atd. Taktéž se vypisuje návratová hodnota volání, a to opět (pokud je to možné) formou symbolické konstanty (ENOENT) popř. zprávy („No such file or directory“.). Na konci můžeme vidět, že volání puts se provede formou write na standardní výstup (vrátí se počet zapsaných/vytištěných bajtů).
8. GNU Debugger
Od relativně jednoduchých a snadno použitelných nástrojů určených pro „pouhé“ trasování se na chvíli přesuňme k debuggerům, což jsou složitější, ovšem také mnohem mocnější nástroje. Pro operační systém Linux existuje větší množství debuggerů, včetně několika komerčních nástrojů (příkladem může být Affinic a TotalView), ovšem nejpoužívanějším debuggerem je pravděpodobně stále GNU Debugger neboli GDB, jehož první verze vznikla již v roce 1986, takže se po třiceti letech kontinuálního vývoje jedná o velmi vyzrálý produkt. GNU Debugger byl v průběhu svého vývoje portován jak na mnoho operačních systémů (většinou na různé komerční i nekomerční varianty Unixu, ovšem nalezneme ho například i v systému DOS), tak i na nepřeberné množství procesorových a mikroprocesorových architektur, z nichž jmenujme především řadu x86, x86_64, ARM (prakticky všechny 32bitové CPU i nová 64bitová jádra), Motorola 68HC11, MIPS či PowerPC.
Tento debugger podporuje všechny překladače z rodiny GNU, což mj. znamená, že dokáže zobrazit a pracovat se zdrojovými kódy v jazycích Ada, C, C++, Go, Objective-C, D, Fortran, Modula-2, Pascal a Java (ovšem jen v případě překladu Javy do nativního kódu). Na základě jazyka, v němž je laděný program napsán, se upravují i zprávy GNU Debuggeru, takže se například používá správný formát hexadecimálních čísel, struktur záznamů atd. Taktéž assemblery používané na Linuxu GNU Debugger přímo podporují (jedná se jak o as, tak i o NASM). Ladicí nástroj GNU Debugger primárně používá ke komunikaci s uživatelem příkazový řádek, alternativně lze použít i protokol pro nepřímé ovládání debuggeru (tuto technologii používají různé nadstavby nad debuggerem) a v případě potřeby je možné k laděné aplikaci přidat relativně krátký „stub“ sloužící pro přímé ladění takové aplikace (touto nepochybně zajímavou problematikou se však dnes nebudeme zabývat).
Většina často používaných příkazů má i svoji zkrácenou podobu (bt=backtrace, c=continue, f=frame) a navíc je možné používat klávesu [Tab] pro automatické doplnění celého jména příkazu. Pokud je správně nastavený terminál, bude fungovat i historie příkazového řádku, a to stejným způsobem, jaký známe ze shellu. Alternativně je možné využít gdbtui s celoobrazovkovým výstupem a přiblížit se tak možnostem debuggerů s plnohodnotným grafickým uživatelským rozhraním. Pokud se GNU Debugger používá pro trasování, lze do kódu vkládat takzvané tracepoints. Ty slouží pro zjištění stavu programu v nějakém specifikovaném bodu, ovšem bez (po)zastavení programu. Samotné pozastavení programu totiž může v některých případech způsobit jeho chybnou činnost či naopak zastínit některé chyby vyplývající ze špatně implementované synchronizace vláken či při přístupu ke sdíleným prostředkům. Podrobnosti o této užitečné technologii si řekneme v navazujícím článku.
9. DTrace
Dalším nástrojem, s nímž se jako vývojáři či administrátoři můžeme na některých systémech setkat, je DTrace. Tento nástroj původně vznikl pro operační systém Solaris (dokonce se mělo jednat o jeden z klíčových nástrojů, kvůli kterému si měli zákazníci koupit Solaris a nikoli přecházet na Linux) a i přes určitou snahu společnosti Oracle o konverzi DTrace pro Linux zatím tento nástroj v Linuxu oficiálně není podporovaný a existuje pouze v beta verzi (dodává přímo Oracle) nebo v neoficiální verzi dostupné na GitHubu. DTrace je založen na použití takzvaných „sond“ (probe), což jsou z hlediska uživatele (většinou) krátké skripty spuštěné ve chvíli, kdy dojde k nějaké předem definované události, a to buď přímo v jádře operačního systému nebo v laděné aplikaci. Samotná sonda (skript) má přístup k zásobníkovému rámci a tedy i k lokálním proměnným a parametrům předaným volané funkci.
Příklad naprogramované sondy:
#pragma D option flowindent syscall::write:entry /pid == $target/ { printf("Written %d bytes\n", arg2); }
Pro zápis sond se sice používá programovací jazyk nazvaný D, ovšem jedná se o „jiné D“, než http://dlang.org/. Jazyk D použitý v DTrace je sice syntakticky taktéž podobný jazyku C, ovšem sémanticky se jedná o zcela odlišné jazyky s různými pravidly i překladači. Podrobnější popis tohoto jazyka lze nalézt na stránce http://docs.oracle.com/cd/E53394_01/html/E53395/gkwpo.html
10. SystemTap
Nástroj SystemTap je v některých ohledech podobný výše zmíněné utilitě DTrace. I v SystemTapu se s využitím programovacího jazyka deklarují takzvané sondy (probe) a určuje se, za jakých okolností se mají tyto sondy provést. SystemTap lze použít jak pro zjišťování procesů probíhajících v kernelu, tak i v uživatelském prostoru (userspace). Sledovat lze například sycally (systémová volání), operace nad soubory, vstupy do funkcí na všech úrovních atd. Poněkud složitější bývala instalace SystemTapu (především kvůli nutnosti operovat s ladicími symboly jádra), to se však zlepšilo přidáním utility stap-prep. Předpokládejme, že balíček SystemTap je nainstalovaný. Potom příkaz stap-prep zjistí, jaké další balíčky je nutné doinstalovat a kde je získat (u Ubuntu se jedná o jiné kanály atd.):
stap-prep Need to install the following packages: kernel-devel-3.15.6-200.fc20.x86_64 kernel-debuginfo-3.15.6-200.fc20.x86_64
V některých případech stap-prep instalaci sám provede. Vezměme si příklad z Fedory:
stap-prep Need to install the following packages: kernel-devel-4.4.8-300.fc23.x86_64 kernel-debuginfo-4.4.8-300.fc23.x86_64 Yum command has been deprecated, redirecting to '/usr/bin/dnf install -y --enablerepo=* kernel-devel-4.4.8-300.fc23.x86_64 kernel-debuginfo-4.4.8-300.fc23.x86_64'. See 'man dnf' and 'man yum2dnf' for more information. To transfer transaction metadata from yum to DNF, run: 'dnf install python-dnf-plugins-extras-migrate && dnf-2 migrate' Fedora 23 - x86_64 - Test Updates 50 MB/s | 2.4 MB 00:00 Fedora 23 - x86_64 - Debug 46 MB/s | 11 MB 00:00 Fedora 23 - Test Updates Source 4.1 MB/s | 330 kB 00:00 Fedora 23 - Updates Source 14 MB/s | 2.5 MB 00:00 Fedora 23 - x86_64 - Updates - Debug 45 MB/s | 6.2 MB 00:00 Fedora 23 - x86_64 52 MB/s | 43 MB 00:00 Fedora 23 - x86_64 - Test Updates Debug 34 MB/s | 885 kB 00:00 Fedora 23 - Source 8.3 MB/s | 5.9 MB 00:00 Last metadata expiration check: 0:00:03 ago on Wed May 11 16:05:54 2016. Dependencies resolved. =================================================================================================================== Package Arch Version Repository Size =================================================================================================================== Installing: kernel-debuginfo x86_64 4.4.8-300.fc23 updates-debuginfo 379 M kernel-debuginfo-common-x86_64 x86_64 4.4.8-300.fc23 updates-debuginfo 53 M kernel-devel x86_64 4.4.8-300.fc23 updates 10 M Transaction Summary =================================================================================================================== Install 3 Packages Total download size: 443 M Installed size: 2.0 G Downloading Packages: (1/3): kernel-devel-4.4.8-300.fc23.x86_64.rpm 26 MB/s | 10 MB 00:00 (2/3): kernel-debuginfo-common-x86_64-4.4.8-300.fc23.x86_64.rpm 26 MB/s | 53 MB 00:02 (3/3): kernel-debuginfo-4.4.8-300.fc23.x86_64.rpm 26 MB/s | 379 MB 00:14 -------------------------------------------------------------------------------------------------------------- Total 27 MB/s | 443 MB 00:16 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Installing : kernel-debuginfo-common-x86_64-4.4.8-300.fc23.x86_64 1/3 Installing : kernel-debuginfo-4.4.8-300.fc23.x86_64 2/3 Installing : kernel-devel-4.4.8-300.fc23.x86_64 3/3 Verifying : kernel-devel-4.4.8-300.fc23.x86_64 1/3 Verifying : kernel-debuginfo-4.4.8-300.fc23.x86_64 2/3 Verifying : kernel-debuginfo-common-x86_64-4.4.8-300.fc23.x86_64 3/3 Installed: kernel-debuginfo.x86_64 4.4.8-300.fc23 kernel-debuginfo-common-x86_64.x86_64 4.4.8-300.fc23 kernel-devel.x86_64 4.4.8-300.fc23 Complete! kernel-devel-4.4.8-300.fc23.x86_64 kernel-debuginfo-4.4.8-300.fc23.x86_64
Po instalaci se může provést kontrola, která má dle oficiální dokumentace vypadat takto:
stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}' Pass 1: parsed user script and 118 library scripts using 233764virt/36948res/7372shr/29452data kb, in 200usr/30sys/471real ms. Pass 2: analyzed script: 1 probe, 1 function, 4 embeds, 0 globals using 372776virt/170852res/8824shr/168464data kb, in 3560usr/400sys/4282real ms. Pass 3: translated to C into "/tmp/stapWy5VWo/stap_5ec9a356f694741098a16e111db111c9_1654_src.c" using 372776virt/171052res/9024shr/168464data kb, in 10usr/10sys/19real ms. Pass 4: compiled C into "stap_5ec9a356f694741098a16e111db111c9_1654.ko" in 16650usr/3190sys/20283real ms. Pass 5: starting run. read performed Pass 5: run completed in 20usr/60sys/440real ms.
11. Ukázky použití SystemTapu
Podrobnější informace o SystemTapu si uvedeme příště, takže si dnes jen pro ilustraci uveďme několik jednoduchých sond.
Sonda, která ihned po svém spuštění (begin) vypíše zprávu na standardní výstup a ukončí se. Povšimněte si, že se syntaxe zápisu podobá standardnímu céčku, což samozřejmě není náhoda:
probe begin { printf ("hello world\n") exit () }
Spuštění této sondy:
stap hello.stp hello world
Poněkud složitější sonda, která pozdraví uživatele jeho ID:
probe begin { printf ("hello %d\n", uid()) exit () }
Spuštění této sondy (spuštěno pod rootem, takže UID by mělo být 0):
stap hello2.stp hello 0
Sonda reagující na časovač – po každé sekundě se vypíše zpráva:
probe timer.ms(1000) { printf ("tiktak\n") }
Spuštění této sondy:
stap clock.stp tiktak tiktak tiktak
Výpis všech typů sond, které je možné použít:
stap --dump-probe-types
Prozatím nejsložitější příklad – reakce na spuštění programu nazvaného hello, reakce na ukončení tohoto programu a taktéž reakce na vstup do funkce main v tomto programu:
probe process("hello").begin { printf ("started\n") } probe process("hello").end { printf ("finished\n") } probe process("hello").function("main") { printf ("main function\n") }
Sondu zaregistrujeme:
stab process.stp
a následně v jiném terminálu spustíme náš program typu Hello world!:
./hello
Ten se spustí, vypíše zprávu a ukončí se, zatímco sonda na prvním terminálu zareaguje následovně:
started main function finished
12. Obsah následujícího článku
V následující části tohoto článku si řekneme podrobnější informace o možnostech GNU Debuggeru (což je jedna z nejpropracovanějších GNU utilit vůbec) a taktéž o SystemTapu. V případě SystemTapu si ukážeme složitější sondy sloužící mj. i pro zjišťování různých statistických informací, pro profilování aplikace atd.
13. Demonstrační příklady použité v dnešním článku
Všechny tři demonstrační příklady, které jsme používali při popisu nástrojů ltrace, strace i SystemTap, byly uloženy do GIT repositáře nazvaného https://github.com/tisnik/presentations:
# | Program | URL do GITu |
---|---|---|
1 | hello.c | https://github.com/tisnik/presentations/blob/master/tracing/hello.c |
2 | random_bitmap.c | https://github.com/tisnik/presentations/blob/master/tracing/random_bitmap.c |
3 | fractal_renderer.c | https://github.com/tisnik/presentations/blob/master/tracing/fractal_renderer.c |
Způsob překladu těchto tří příkladu je ve skutečnosti velmi jednoduchý:
gcc -ansi -pedantic -Wall -o hello hello.c gcc -ansi -pedantic -Wall -o random_bitmap random_bitmap.c gcc -ansi -pedantic -Wall -o fractal_renderer fractal_renderer.c -lm
Poznámka: u posledního příkladu nezapomeňte přilinkovat knihovnu s matematickými funkcemi libm.
Obrázek 1: Výsledek aplikace random_bitmap.
Obrázek 2: Výsledek aplikace fractal_renderer.
14. Odkazy na Internetu
- Tracing (software)
https://en.wikipedia.org/wiki/Tracing_%28software%29 - ltrace(1) – Linux man page
http://linux.die.net/man/1/ltrace - ltrace (Wikipedia)
https://en.wikipedia.org/wiki/Ltrace - strace(1) – Linux man page
http://linux.die.net/man/1/strace - strace (stránka projektu na SourceForge)
https://sourceforge.net/projects/strace/ - strace (Wikipedia)
https://en.wikipedia.org/wiki/Strace - SystemTap (stránka projektu)
https://sourceware.org/systemtap/ - SystemTap (Wiki projektu)
https://sourceware.org/systemtap/wiki - SystemTap (Wikipedia)
https://en.wikipedia.org/wiki/SystemTap - Dynamic Tracing with DTrace & SystemTap
http://myaut.github.io/dtrace-stap-book/ - DTrace (Wikipedia)
https://en.wikipedia.org/wiki/DTrace - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Getting started with ltrace: how does it do that?
https://www.ellexus.com/getting-started-with-ltrace-how-does-it-do-that/ - Reverse Engineering Tools in Linux – strings, nm, ltrace, strace, LD_PRELOAD
http://www.thegeekstuff.com/2012/03/reverse-engineering-tools/ - 7 Strace Examples to Debug the Execution of a Program in Linux
http://www.thegeekstuff.com/2011/11/strace-examples/ - Oracle® Solaris 11.3 DTrace (Dynamic Tracing) Guide
http://docs.oracle.com/cd/E53394_01/html/E53395/gkwpo.html#scrolltoc