GNU/Linux >> Znalost Linux >  >> Linux

Pochopení systémových volání na Linuxu pomocí strace

Systémové volání je programový způsob, jakým program požaduje službu od jádra a strace je výkonný nástroj, který vám umožňuje sledovat tenkou vrstvu mezi uživatelskými procesy a linuxovým jádrem.

Abyste pochopili, jak operační systém funguje, musíte nejprve pochopit, jak fungují systémová volání. Jednou z hlavních funkcí operačního systému je poskytovat abstrakce uživatelským programům.

Operační systém lze zhruba rozdělit do dvou režimů:

  • Režim jádra: Privilegovaný a výkonný režim používaný jádrem operačního systému
  • Uživatelský režim: Kde běží většina uživatelských aplikací

Uživatelé většinou pracují s nástroji příkazového řádku a grafickými uživatelskými rozhraními (GUI) při provádění každodenních úkolů. Systémová volání pracují tiše na pozadí a propojují se s jádrem, aby mohla pracovat.

Další zdroje pro Linux

  • Cheat pro příkazy Linuxu
  • Cheat sheet pro pokročilé příkazy systému Linux
  • Bezplatný online kurz:Technický přehled RHEL
  • Síťový cheat pro Linux
  • Cheat sheet SELinux
  • Cheat pro běžné příkazy pro Linux
  • Co jsou kontejnery systému Linux?
  • Naše nejnovější články o Linuxu

Systémová volání jsou velmi podobná volání funkcí, což znamená, že přijímají a pracují s argumenty a návratovými hodnotami. Jediný rozdíl je v tom, že systémová volání vstupují do jádra, zatímco volání funkcí nikoli. Přechod z uživatelského prostoru do prostoru jádra se provádí pomocí speciálního mechanismu pasti.

Většina z toho je před uživateli skryta pomocí systémových knihoven (aka glibc na systémech Linux). I když jsou systémová volání obecná, mechanismus vydávání systémových volání je do značné míry závislý na počítači.

Tento článek zkoumá některé praktické příklady pomocí některých obecných příkazů a analyzuje systémová volání prováděná každým příkazem pomocí strace . Tyto příklady používají Red Hat Enterprise Linux, ale příkazy by měly fungovat stejně na jiných distribucích Linuxu:

[root@sandbox ~]# cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#

Nejprve se ujistěte, že jsou ve vašem systému nainstalovány požadované nástroje. Můžete ověřit, zda strace se instaluje pomocí příkazu RPM níže; pokud ano, můžete zkontrolovat strace číslo verze nástroje pomocí -V možnost:

[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#

Pokud to nepomůže, nainstalujte strace spuštěním:

yum install strace

Pro účely tohoto příkladu vytvořte testovací adresář v /tmp a pomocí dotyku vytvořte dva soubory pomocí příkazu:

[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#

(Použil jsem soubor /tmp adresář, protože k němu má přístup každý, ale pokud chcete, můžete si vybrat jiný adresář.)

Ověřte, zda byly soubory vytvořeny pomocí ls příkaz na testdir adresář:

[root@sandbox tmp]# ls testdir/
file1  file2
[root@sandbox tmp]#

Pravděpodobně používáte ls příkaz každý den, aniž byste si uvědomili, že pod ním pracují systémová volání. Zde hraje roli abstrakce; tento příkaz funguje následovně:

Command-line utility -> Invokes functions from system libraries (glibc) -> Invokes system calls

ls příkaz interně volá funkce ze systémových knihoven (aka glibc ) na Linuxu. Tyto knihovny vyvolávají systémová volání, která dělají většinu práce.

Pokud chcete vědět, které funkce byly volány z glibc knihovny, použijte ltrace příkaz následovaný běžným ls testdir/ příkaz:

ltrace ls testdir/

Pokud ltrace není nainstalován, nainstalujte jej zadáním:

yum install ltrace

Na obrazovku se zobrazí spousta výstupů; nedělejte si s tím starosti – jen následujte. Některé z důležitých funkcí knihovny z výstupu ltrace příkazy, které jsou relevantní pro tento příklad, zahrnují:

opendir("testdir/")                                  = { 3 }
readdir({ 3 })                                       = { 101879119, "." }
readdir({ 3 })                                       = { 134, ".." }
readdir({ 3 })                                       = { 101879120, "file1" }
strlen("file1")                                      = 5
memcpy(0x1665be0, "file1\0", 6)                      = 0x1665be0
readdir({ 3 })                                       = { 101879122, "file2" }
strlen("file2")                                      = 5
memcpy(0x166dcb0, "file2\0", 6)                      = 0x166dcb0
readdir({ 3 })                                       = nil
closedir({ 3 })                      

Když se podíváte na výstup výše, pravděpodobně pochopíte, co se děje. Adresář s názvem testdir se otevírá pomocí opendir funkce knihovny, následovaná voláním readdir funkce, která čte obsah adresáře. Na konci je volání na zavřeno funkce, která zavře adresář, který byl otevřen dříve. Ostatní strlen ignorujte a memcpy funkce prozatím.

Můžete vidět, které funkce knihovny jsou volány, ale tento článek se zaměří na systémová volání, která jsou vyvolána funkcemi systémové knihovny.

Podobně jako výše, abyste pochopili, jaká systémová volání jsou vyvolána, stačí zadat strace před ls testdir příkaz, jak je znázorněno níže. Opět se vám na obrazovku vysype hromada nesmyslů, které můžete sledovat zde:

[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL)                               = 0x1f12000
<<< truncated strace output >>>
write(1, "file1  file2\n", 13file1  file2
)          = 13
close(1)                                = 0
munmap(0x7fd002c8d000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
[root@sandbox tmp]#

Výstup na obrazovce po spuštění strace příkaz byl jednoduše proveden systémová volání ke spuštění ls příkaz. Každé systémové volání slouží konkrétnímu účelu pro operační systém a lze je obecně rozdělit do následujících sekcí:

  • Volání systému řízení procesů
  • Volání systému správy souborů
  • Volání systému správy adresářů a souborového systému
  • Další systémová volání

Jednodušší způsob, jak analyzovat informace uložené na obrazovce, je protokolovat výstup do souboru pomocí strace 's šikovný -o vlajka. Za -o přidejte vhodný název souboru flag a spusťte příkaz znovu:

[root@sandbox tmp]# strace -o trace.log ls testdir/
file1  file2
[root@sandbox tmp]#

Tentokrát se na obrazovku nevypíše žádný výstup – ls příkaz fungoval podle očekávání, zobrazoval názvy souborů a zaznamenával veškerý výstup do souboru trace.log . Soubor má téměř 100 řádků obsahu jen pro jednoduché ls příkaz:

[root@sandbox tmp]# ls -l trace.log 
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#

Podívejte se na první řádek v příkladu trace.log:

execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
  • První slovo na řádku, execve , je název právě prováděného systémového volání.
  • Text v závorkách představuje argumenty poskytnuté systémovému volání.
  • Číslo za = znak (což je 0 v tomto případě) je hodnota vrácená execve systémové volání.

Výstup se teď nezdá příliš zastrašující, že? A stejnou logiku můžete použít k pochopení dalších řádků.

Nyní se zaměřte na jediný příkaz, který jste vyvolali, tj. ls testdir . Znáte název adresáře používaný příkazem ls , tak proč ne grep pro testdir ve vašem trace.log soubor a uvidíte, co získáte? Podívejte se podrobně na každý řádek výsledků:

[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#

Vraťme se k analýze execve výše, můžete říci, co toto systémové volání dělá?

execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0

Nemusíte si pamatovat všechna systémová volání nebo to, co dělají, protože když potřebujete, můžete se obrátit na dokumentaci. Manpages na záchranu! Před spuštěním man se ujistěte, že je nainstalován následující balíček příkaz:

[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#

Nezapomeňte, že musíte přidat 2 mezi mužem příkaz a název systémového volání. Pokud čtete člověk manuálová stránka uživatele pomocí man man , můžete vidět, že sekce 2 je vyhrazena pro systémová volání. Podobně, pokud potřebujete informace o funkcích knihovny, musíte přidat 3 mezi mužem a název funkce knihovny.

Následují čísla částí příručky a typy stránek, které obsahují:

1. Executable programs or shell commands
2. System calls (functions provided by the kernel)
3. Library calls (functions within program libraries)
4. Special files (usually found in /dev)

Spusťte následujícího man příkaz s názvem systémového volání, abyste viděli dokumentaci k tomuto systémovému volání:

man 2 execve

Podle execve manuálová stránka, toto spustí program, který je předán v argumentech (v tomto případě to je ls ). Existují další argumenty, které lze poskytnout ls , například testdir v tomto příkladu. Proto toto systémové volání spustí pouze ls s testdir jako argument:

'execve - execute program'

'DESCRIPTION
       execve()  executes  the  program  pointed to by filename'

Další systémové volání s názvem stat , používá testdir argument:

stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0

Použijte statistiku muže 2 pro přístup k dokumentaci. stat je systémové volání, které získává stav souboru – nezapomeňte, že vše v Linuxu je soubor, včetně adresáře.

Další, openat systémové volání otevře testdir. Sledujte 3 která se vrací. Toto je popis souboru, který bude použit při pozdějších systémových voláních:

openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3

Zatím je vše dobré. Nyní otevřete trace.log a přejděte na řádek za openat systémové volání. Zobrazí se getdents je vyvoláno systémové volání, které udělá většinu toho, co je potřeba k provedení ls testdir příkaz. Nyní grep getdents z trace.log soubor:

[root@sandbox tmp]# grep getdents trace.log 
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
[root@sandbox tmp]#

The getdents manuálová stránka to popisuje jako získat položky adresáře , což je to, co chcete dělat. Všimněte si, že argument pro getdents je 3 , což je deskriptor souboru z openat systémové volání výše.

Nyní, když máte výpis adresáře, potřebujete způsob, jak jej zobrazit ve vašem terminálu. Takže grep pro další systémové volání pište , který se používá k zápisu do terminálu, v protokolech:

[root@sandbox tmp]# grep write trace.log
write(1, "file1  file2\n", 13)          = 13
[root@sandbox tmp]#

V těchto argumentech můžete vidět názvy souborů, které se zobrazí:soubor1 a soubor2 . Pokud jde o první argument (1 ), v Linuxu si pamatujte, že když je spuštěn jakýkoli proces, ve výchozím nastavení se pro něj otevřou tři deskriptory souborů. Následují výchozí deskriptory souborů:

  • 0 – Standardní vstup
  • 1 – Standardní
  • 2 – Standardní chyba

Takže pište systémové volání zobrazuje soubor1 a soubor2 na standardním displeji, což je terminál, označený 1 .

Nyní víte, která systémová volání udělala většinu práce pro ls testdir/ příkaz. Ale co dalších 100+ systémových volání v trace.log soubor? Operační systém musí pro spuštění procesu udělat hodně úklidu, takže hodně z toho, co vidíte v souboru protokolu, je inicializace a vyčištění procesu. Přečtěte si celý trace.log a pokuste se pochopit, co se děje, aby se vytvořil ls příkazová práce.

Nyní, když víte, jak analyzovat systémová volání pro daný příkaz, můžete tyto znalosti použít pro další příkazy, abyste pochopili, jaká systémová volání se provádějí. strace poskytuje mnoho užitečných příznaků příkazového řádku, které vám to usnadní, a některé z nich jsou popsány níže.

Ve výchozím nastavení strace nezahrnuje všechny informace o systémovém volání. Má však praktické -v verbose možnost, která může poskytnout další informace o každém systémovém volání:

strace -v ls testdir

Je dobrým zvykem vždy používat -f možnost při spuštění strace příkaz. Umožňuje strace ke sledování všech podřízených procesů vytvořených aktuálně sledovaným procesem:

strace -f ls testdir

Řekněme, že chcete pouze názvy systémových volání, počet jejich spuštění a procento času stráveného v každém systémovém volání. Můžete použít -c flag pro získání těchto statistik:

strace -c ls testdir/

Předpokládejme, že se chcete soustředit na konkrétní systémové volání, například na otevřené systémová volání a ignorování zbytku. Můžete použít -e příznak následovaný názvem systémového volání:

[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1  file2
+++ exited with 0 +++
[root@sandbox tmp]#

Co když se chcete soustředit na více než jedno systémové volání? Žádný strach, můžete použít stejné -e příznak příkazového řádku s čárkou mezi dvěma systémovými voláními. Chcete-li například zobrazit zápis a getdents systémová volání:

[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
write(1, "file1  file2\n", 13file1  file2
)          = 13
+++ exited with 0 +++
[root@sandbox tmp]#

Dosavadní příklady sledovaly explicitně spuštěné příkazy. Ale co příkazy, které již byly spuštěny a jsou prováděny? Co například, když chcete sledovat démony, kteří jsou pouze dlouhotrvajícími procesy? Za tímto účelem strace poskytuje speciální -p příznak, kterému můžete poskytnout ID procesu.

Místo spuštění strace u démona si vezměte příklad z kočky příkaz, který obvykle zobrazí obsah souboru, pokud zadáte název souboru jako argument. Pokud není uveden žádný argument, kočka příkaz jednoduše čeká na terminálu, až uživatel zadá text. Jakmile je text zadán, opakuje daný text, dokud uživatel nestiskne Ctrl+C pro ukončení.

Spusťte kočku příkaz z jednoho terminálu; zobrazí se vám výzva a jednoduše tam počkáte (pamatujte na kočku stále běží a neukončil):

[root@sandbox tmp]# cat

Na jiném terminálu najděte identifikátor procesu (PID) pomocí ps příkaz:

[root@sandbox ~]# ps -ef | grep cat
root      22443  20164  0 14:19 pts/0    00:00:00 cat
root      22482  20300  0 14:20 pts/1    00:00:00 grep --color=auto cat
[root@sandbox ~]#

Nyní spusťte strace na běžícím procesu pomocí -p příznak a PID (které jste našli výše pomocí ps ). Po spuštění strace , výstup uvádí, k čemu byl proces připojen, spolu s číslem PID. Nyní strace sleduje systémová volání provedená kočkou příkaz. První systémové volání, které uvidíte, je přečteno , který čeká na vstup od 0, nebo standardní vstup, což je terminál, kde je cat příkaz běžel:

[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,

Nyní se vraťte k terminálu, kde jste nechali kočku spuštěný příkaz a zadejte nějaký text. Zadal jsem x0x0 pro demo účely. Všimněte si, jak kočka jednoduše zopakoval, co jsem zadal; tedy x0x0 se objeví dvakrát. Vložil jsem první a druhý byl výstup, který opakovala kočka příkaz:

[root@sandbox tmp]# cat
x0x0
x0x0

Přejděte zpět na terminál, kde strace byl připojen ke kočce proces. Nyní vidíte dvě další systémová volání:dřívější přečtené systémové volání, které nyní zní x0x0 v terminálu a další pro zápis , který napsal x0x0 zpět do terminálu a znovu nové přečtení , který čeká na čtení z terminálu. Všimněte si, že standardní vstup (0 ) a Standardní výstup (1 ) jsou oba ve stejném terminálu:

[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536)                = 5
write(1, "x0x0\n", 5)                   = 5
read(0,

Představte si, jak užitečné to je při spouštění strace proti démonům, abyste viděli vše, co dělá na pozadí. Zabijte kočku příkaz stisknutím Ctrl+C; toto také zabije váš strace relaci, protože proces již není spuštěn.

Pokud chcete vidět časové razítko všech vašich systémových volání, jednoduše použijte -t možnost s strace :

[root@sandbox ~]#strace -t ls testdir/

14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL)                      = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

Co když chcete znát čas strávený mezi systémovými voláními? strace má praktické -r příkaz, který ukazuje čas strávený prováděním každého systémového volání. Docela užitečné, ne?

[root@sandbox ~]#strace -r ls testdir/

0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL)                 = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

Závěr

čára nástroj je velmi užitečný pro pochopení systémových volání na Linuxu. Chcete-li se dozvědět o dalších příznacích příkazového řádku, podívejte se na manuálové stránky a online dokumentaci.


Linux
  1. Plánování systémových úloh pomocí Cronu na Linuxu

  2. Vyvažování bezpečnosti Linuxu a použitelnosti

  3. Proč je moje funkce cat se systémovými voláními pomalejší ve srovnání s kočkou v Linuxu?

  1. Monitorujte svůj systém Linux ve svém terminálu pomocí procps-ng

  2. Zlepšete výkon systému Linux pomocí noatime

  3. Linux Shutdown Command (s příklady)

  1. Vyzkoušejte Linux na jakémkoli operačním systému s VirtualBoxem

  2. Testování pera pomocí bezpečnostních nástrojů Linuxu

  3. Jak na to:Programování v C s adresáři na Linuxu