Der Syscall ist der Mechanismus, mit dem Benutzerprogramme mit dem Linux-Kernel interagieren, und strace ist ein leistungsstarkes Tool, um diese zu verfolgen. Um besser zu verstehen, wie das Betriebssystem funktioniert, ist es hilfreich zu verstehen, wie sie funktionieren.
Das Betriebssystem kann in zwei Betriebsarten unterteilt werden:
- Der Kernel-Modus ist der privilegierte Modus, der vom Betriebssystem-Kernel verwendet wird.
- Der Benutzermodus ist der Modus, in dem die meisten Benutzeranwendungen ausgeführt werden.
Benutzer verwenden normalerweise Befehlszeilenprogramme und eine grafische Oberfläche (GUI) für ihre tägliche Arbeit. Gleichzeitig arbeiten Systemaufrufe unsichtbar im Hintergrund und beziehen sich auf den Kernel, der die Arbeit erledigt.
Systemaufrufe sind Funktionsaufrufen in dem Sinne sehr ähnlich, dass Argumente übergeben werden und Werte zurückgeben. Der einzige Unterschied besteht darin, dass Systemaufrufe auf Kernelebene funktionieren, Funktionen jedoch nicht. Das Umschalten vom Benutzermodus in den Kernelmodus erfolgt über einen speziellen Interrupt- Mechanismus .
Die meisten dieser Details sind dem Benutzer in den Systembibliotheken verborgen (glibc auf Linux-Systemen). Systemaufrufe sind allgemeiner Natur, aber trotzdem sind die Mechanismen ihrer Ausführung weitgehend hardwareabhängig.
In diesem Artikel werden einige praktische Beispiele für das Parsen von Systemaufrufen mit verwendet
strace. Die Beispiele verwenden Red Hat Enterprise Linux, aber alle Befehle sollten auf anderen Linux-Distributionen funktionieren:
[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 ~]#
Stellen Sie zunächst sicher, dass Sie die erforderlichen Tools auf Ihrem System installiert haben. Sie
stracekönnen mit dem folgenden Befehl überprüfen, ob es installiert ist. straceFühren Sie die Version mit dem Parameter -V aus , um sie anzuzeigen :
[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 ~]#
Wenn
stracenicht installiert, installieren Sie durch Ausführen von:
yum install strace
Erstellen Sie beispielsweise ein Testverzeichnis in
/tmpund zwei Dateien mit dem folgenden Befehl touch:
[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]#
(Ich verwende ein Verzeichnis nur,
/tmpweil jeder Zugriff darauf hat, aber Sie können jedes andere Verzeichnis verwenden .) Stellen Sie
mit dem folgenden Befehl sicher,
lsdass testdirDateien im Verzeichnis erstellt wurden:
[root@sandbox tmp]# ls testdir/
file1 file2
[root@sandbox tmp]#
Sie verwenden den Befehl wahrscheinlich
lsjeden Tag, ohne zu bemerken, dass Systemaufrufe unter der Haube ausgeführt werden. Hier kommt die Abstraktion ins Spiel. So funktioniert dieser Befehl:
-> (glibc) ->
Der Befehl
lsruft Funktionen aus den Linux-Systembibliotheken (glibc) auf. Diese Bibliotheken rufen wiederum Systemaufrufe auf, die den größten Teil der Arbeit erledigen.
Wenn Sie wissen möchten, welche Funktionen aus der glibc-Bibliothek aufgerufen wurden, verwenden Sie den Befehl
ltracegefolgt vom Befehl ls testdir/:
ltrace ls testdir/
Wenn
ltracenicht installiert, installieren Sie:
yum install ltrace
Es werden viele Informationen auf dem Bildschirm angezeigt, aber keine Sorge - wir werden das später behandeln. Hier sind einige der wichtigen Bibliotheksfunktionen aus der Ausgabe
ltrace:
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 })
Wenn Sie diese Ausgabe untersuchen, können Sie wahrscheinlich verstehen, was los ist. Das benannte Verzeichnis wird
testdirmithilfe einer Bibliotheksfunktion geöffnet opendir, gefolgt von Aufrufen von Funktionen readdir, die den Inhalt des Verzeichnisses lesen. Schließlich wird eine Funktion aufgerufen closedir, die das zuvor geöffnete Verzeichnis schließt. Ignorieren Sie vorerst andere Funktionen wie strlenund memcpy.
Wie Sie sehen können, können Sie leicht sehen, welche Bibliotheksfunktionen aufgerufen werden. In diesem Artikel konzentrieren wir uns jedoch auf die Systemaufrufe, die von den Systembibliotheksfunktionen aufgerufen werden.
Verwenden Sie zum Anzeigen von Systemaufrufen
straceden folgenden Befehl ls testdir. Und wieder erhalten Sie eine Reihe inkohärenter Informationen:
[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]#
Als Ergebnis der Ausführung
straceerhalten Sie eine Liste der Systemaufrufe, die während des Befehls ausgeführt werden ls. Alle Systemaufrufe können in folgende Kategorien unterteilt werden:
- Prozessmanagement
- Dokumentenverwaltung
- Verzeichnis- und Dateisystemverwaltung
- Andere
Es gibt eine bequeme Möglichkeit, die empfangenen Informationen zu analysieren - schreiben Sie die Ausgabe mit der Option in eine Datei
-o.
[root@sandbox tmp]# strace -o trace.log ls testdir/
file1 file2
[root@sandbox tmp]#
Dieses Mal werden keine Daten auf dem Bildschirm angezeigt. Der Befehl
lsfunktioniert wie erwartet, zeigt eine Liste der Dateien an und schreibt alle Ausgaben stracein eine Datei trace.log. Für einen einfachen Befehl lsenthält die Datei fast 100 Zeilen:
[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]#
Schauen Sie sich die erste Zeile in der Datei an
trace.log:
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
- Am Anfang der Zeile steht der Name des ausgeführten Systemaufrufs - execve.
- Der Text in Klammern ist das Argument, das an den Systemaufruf übergeben wird.
- Die Zahl nach dem Zeichen = (in diesem Fall 0) ist der vom Systemaufruf zurückgegebene Wert.
Jetzt scheint das Ergebnis nicht allzu beängstigend zu sein, oder? Sie können dieselbe Logik auch für andere Zeilen anwenden.
Achten Sie auf den einzigen Befehl, den Sie aufgerufen haben -
ls testdir. Sie kennen den Namen des Verzeichnisses durch den Befehl verwendet ls, also warum nicht verwenden grepfür testdirin der Datei trace.logund sehen , was es findet? Schauen Sie sich das Ergebnis genau an:
[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]#
Können
execveSie anhand der obigen Analyse feststellen, was der nächste Systemaufruf bewirkt?
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
Sie müssen sich nicht alle Systemaufrufe merken und wissen, was sie tun: Alles ist in der Dokumentation enthalten. Manpages eilen zur Rettung! Stellen Sie sicher, dass das Paket installiert ist, bevor Sie den Befehl man ausführen
man-pages:
[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#
Denken Sie daran, dass Sie zwischen dem Befehl
manund dem Syscall-Namen "2" hinzufügen müssen . Wenn Sie manabout man( man man) lesen , werden Sie sehen, dass Abschnitt 2 für Systemaufrufe reserviert ist. Wenn Sie Informationen zu Bibliotheksfunktionen wünschen, müssen Sie ebenfalls 3 zwischen manund den Namen der Bibliotheksfunktion hinzufügen .
Unten sind die Abschnittsnummern
man:
1. .
2. (, ).
3. ( ).
4. ( /dev).
Führen Sie man mit dem Namen dieses Systemaufrufs aus, um die Dokumentation für einen Systemaufruf anzuzeigen.
man 2 execve
Gemäß der Dokumentation führt ein Systemaufruf
execveein Programm aus, das in Parametern an ihn übergeben wird (in diesem Fall ls). Zusätzliche Parameter für ls werden ebenfalls an ls übergeben. In diesem Beispiel ist es testdir. Daher läuft das Systemaufruf einfach lsmit testdirals Parameter:
'execve - execute program'
'DESCRIPTION
execve() executes the program pointed to by filename'
Dem nächsten Systemaufruf
statwird ein Parameter übergeben testdir:
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
Verwenden Sie zum Anzeigen der Dokumentation
man 2 stat. Der Systemaufruf stat gibt Informationen zur angegebenen Datei zurück. Denken Sie daran, dass alles in Linux eine Datei ist, einschließlich Verzeichnisse.
Als nächstes wird der Systemaufruf
openatgeöffnet testdir. Beachten Sie, dass der Rückgabewert 3 ist. Dies ist der Dateideskriptor, der in nachfolgenden Systemaufrufen verwendet wird:
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
Öffnen Sie nun die Datei
trace.logund beachten Sie die Zeile nach dem Systemaufruf openat. Sie sehen einen Systemaufruf getdents, der den größten Teil der zur Ausführung des Befehls erforderlichen Arbeit erledigt ls testdir. Lassen Sie uns nun grep getdentsfür die Datei ausführen trace.log:
[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768) = 112
getdents(3, /* 0 entries */, 32768) = 0
[root@sandbox tmp]#
Die Dokumentation (
man getdents) besagt, dass getdentsVerzeichniseinträge gelesen werden, was wir tatsächlich benötigen. Beachten Sie, dass das Argument für getdent3 der Dateideskriptor ist, der zuvor vom Systemaufruf erhalten wurde openat.
Nachdem der Inhalt des Verzeichnisses empfangen wurde, benötigen wir eine Möglichkeit, die Informationen im Terminal anzuzeigen. Wir machen also einen
grepanderen Systemaufruf write, der zur Ausgabe an das Terminal verwendet wird:
[root@sandbox tmp]# grep write trace.log
write(1, "file1 file2\n", 13) = 13
[root@sandbox tmp]#
In den Argumenten sehen Sie die Namen der auszugebenden Dateien:
file1und file2. Denken Sie beim ersten Argument (1) daran, dass unter Linux standardmäßig drei Dateideskriptoren für jeden Prozess geöffnet sind:
- 0 - Standardeingabestream
- 1 - Standardausgabestream
- 2 - Standardfehlerstrom
Somit
writedauert der Systemaufruf file1und file2die Standardausgabe, bei der es sich um ein Terminal handelt, bezeichnet die Nummer 1.
Nachdem Sie nun wissen, welche Systemaufrufe den größten Teil der Arbeit für das Team erledigt haben
ls testdir/. Aber was ist mit den anderen über 100 Systemaufrufen in der Datei trace.log?
Das Betriebssystem führt viele unterstützende Aktionen aus, um den Prozess zu starten. In der Datei
trace.logwird also häufig der Prozess initialisiert und bereinigt. Sehen Sie sich die Datei trace.log vollständig an und versuchen Sie zu verstehen, was passiert, wenn der Befehl ausgeführt wird ls.
Jetzt können Sie Systemaufrufe für jedes Programm analysieren. Das Dienstprogramm strace bietet auch viele nützliche Befehlszeilenoptionen, von denen einige im Folgenden beschrieben werden.
Standardmäßig
stracewerden nicht alle Informationen zu Systemaufrufen angezeigt. Es gibt jedoch eine Option -v verbose, die zusätzliche Informationen zu jedem Systemaufruf anzeigt:
strace -v ls testdir
Es wird empfohlen, einen Parameter
-fzu verwenden, um untergeordnete Prozesse zu verfolgen, die von einem laufenden Prozess erstellt wurden:
strace -f ls testdir
Was ist, wenn Sie nur die Namen der Systemaufrufe, die Häufigkeit ihrer Ausführung und den Prozentsatz der Ausführungszeit angeben möchten? Sie können die Option verwenden
-c, um diese Statistiken abzurufen:
strace -c ls testdir/
Wenn Sie beispielsweise einen bestimmten Systemaufruf aufspüren
openund andere ignorieren möchten, können Sie die Option -emit dem Namen des Systemaufrufs verwenden:
[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]#
Was ist, wenn Sie nach mehreren Systemaufrufen filtern müssen? Keine Sorge, Sie können dieselbe Option verwenden
-eund die erforderlichen Systemaufrufe durch ein Komma trennen. Zum Beispiel für writeund getdent:
[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]#
Bisher haben wir nur explizite Befehlsläufe verfolgt. Aber was ist mit den Befehlen, die früher ausgeführt wurden? Was ist, wenn du Dämonen verfolgen willst? Dazu haben Sie
straceeine spezielle Option, -pan die Sie die Prozess-ID übergeben können.
Wir werden den Dämon nicht starten, sondern einen Befehl verwenden
cat, der den Inhalt der an ihn übergebenen Datei als Argument anzeigt. Wenn Sie jedoch kein Argument angeben, catwartet der Befehl nur auf die Eingabe durch den Benutzer. Nach der Texteingabe wird der eingegebene Text auf dem Bildschirm angezeigt. Und so weiter, bis der Benutzer Ctrl+Czum Beenden klickt .
Führen Sie den Befehl
catauf einem Terminal aus.
[root@sandbox tmp]# cat
Suchen Sie auf einem anderen Terminal die Prozess-ID (PID) mit dem folgenden Befehl
ps:
[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 ~]#
Beginnen Sie nun
stracemit der Option -pund PID, mit der Sie gefunden haben ps. Nach dem Start stracewerden Informationen zu dem Prozess, an den es angeschlossen wurde, sowie seine PID angezeigt. Nun straceüberwacht die Systemaufrufe durch den Befehl aus cat. Der erste Systemaufruf, den Sie sehen, wird gelesen und wartet auf die Eingabe von Thread 0, dh von der Standardeingabe, die jetzt das Terminal ist, auf dem der Befehl ausgeführt wird cat:
[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,
Gehen Sie nun zurück zu dem Terminal, an dem Sie den Befehl ausgeführt haben,
catund geben Sie Text ein. Zur Demonstration trat ich ein x0x0. Bitte beachten Sie, dass ich cateinfach meine Eingabe wiederholt habe und x0x0der Bildschirm zweimal angezeigt wird.
[root@sandbox tmp]# cat
x0x0
x0x0
Gehen Sie zurück zu dem Terminal, an dem Sie eine
straceVerbindung zum Prozess hergestellt haben cat. Jetzt sehen Sie zwei neue Systemaufrufe: den vorherigen read, der jetzt gelesen hat x0x0, und einen weiteren zum Schreiben write, der x0x0zurück in das Terminal schreibt , und erneut einen neuen read, der auf einen Lesevorgang vom Terminal wartet. Beachten Sie, dass sich der Standardeingang (0) und der Standardausgang (1) am selben Anschluss befinden:
[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536) = 5
write(1, "x0x0\n", 5) = 5
read(0,
Stellen Sie sich die Vorteile des Starts
stracefür Dämonen vor: Sie können alles sehen, was im Hintergrund geschieht. Führen Sie den Befehl auscatBeim Klicken Ctrl+C... Dadurch wird auch die Sitzung beendetstraceda der überwachte Prozess beendet wurde.
Verwenden Sie die Option, um die Zeitstempel von Systemaufrufen anzuzeigen
-t:
[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
Was ist, wenn Sie die Zeit zwischen Systemaufrufen wissen möchten? Es gibt eine praktische Option
-r, die die Zeit anzeigt, die zum Ausführen jedes Systemaufrufs benötigt wird. Ziemlich nützlich, nicht wahr?
[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
Fazit
Das Dienstprogramm ist
stracesehr praktisch zum Lernen von Systemaufrufen unter Linux. Weitere Befehlszeilenoptionen finden Sie in der Man- und Online-Dokumentation.
