Minimalny możliwy do uruchomienia przykład
Jeśli pojęcie nie jest jasne, istnieje prostszy przykład, którego nie widziałeś, który to wyjaśnia.
W tym przypadku przykładem jest niezależny zestaw Linux x86_64 (bez libc) hello world:
przywitania
.text
.global _start
_start:
/* write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* buffer len */
syscall
/* exit */
mov $60, %rax /* exit status */
mov $0, %rdi /* syscall number */
syscall
msg:
.ascii "hello\n"
len = . - msg
GitHub w górę .
Złóż i uruchom:
as -o hello.o hello.S
ld -o hello.out hello.o
./hello.out
Wyprowadza oczekiwane:
hello
Teraz użyjmy strace na tym przykładzie:
env -i ASDF=qwer strace -o strace.log -s999 -v ./hello.out arg0 arg1
cat strace.log
Używamy:
strace.log
teraz zawiera:
execve("./hello.out", ["./hello.out", "arg0", "arg1"], ["ASDF=qwer"]) = 0
write(1, "hello\n", 6) = 6
exit(0) = ?
+++ exited with 0 +++
Przy tak minimalnym przykładzie każdy znak wyjściowy jest oczywisty:
execve
wiersz: pokazuje sposób strace
wykonania hello.out
, w tym argumenty CLI i środowisko, zgodnie z dokumentacją wman execve
write
linia: pokazuje wykonane przez nas wywołanie systemowe zapisu. 6
jest długością łańcucha "hello\n"
.
= 6
jest zwracaną wartością wywołania systemowego, która zgodnie z dokumentacją man 2 write
jest liczbą zapisanych bajtów.
exit
linia: pokazuje wykonane przez nas wywołanie systemowe wyjścia. Nie ma wartości zwracanej, ponieważ program został zamknięty!
Bardziej złożone przykłady
Zastosowanie strace ma oczywiście na celu sprawdzenie, które wywołania systemowe faktycznie wykonują złożone programy, aby pomóc w debugowaniu / optymalizacji programu.
Warto zauważyć, że większość wywołań systemowych, które możesz napotkać w Linuksie, ma opakowania glibc, wiele z nich z POSIX .
Wewnętrznie owijki glibc używają mniej więcej takiego wbudowania: Jak wywołać wywołanie systemowe za pomocą sysenter w zestawie wbudowanym?
Następnym przykładem, który powinieneś przestudiować, jest POSIX write
hello world:
main.c
#define _XOPEN_SOURCE 700
#include <unistd.h>
int main(void) {
char *msg = "hello\n";
write(1, msg, 6);
return 0;
}
Skompiluj i uruchom:
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
Tym razem zobaczysz, że glibc wykonuje kilka wywołań systemowych, main
aby skonfigurować ładne środowisko dla main.
Wynika to z faktu, że nie używamy teraz programu wolnostojącego, ale bardziej popularnego programu glibc, który umożliwia funkcjonalność libc.
Następnie na każdym końcu strace.log
zawiera:
write(1, "hello\n", 6) = 6
exit_group(0) = ?
+++ exited with 0 +++
Dochodzimy zatem do wniosku, że write
funkcja POSIX używa, niespodzianka ! write
, wywołania systemowego Linux .
Obserwujemy również, że return 0
prowadzi to do exit_group
połączenia zamiast exit
. Ha, nie wiedziałem o tym! Właśnie dlatego strace
jest taki fajny. man exit_group
następnie wyjaśnia:
To wywołanie systemowe jest równoważne z exit (2), z tym wyjątkiem, że kończy nie tylko wątek wywołujący, ale wszystkie wątki w grupie wątków procesu wywołującego.
A oto kolejny przykład, w którym badałem, które dlopen
wykorzystuje wywołanie systemowe : /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
Testowane w Ubuntu 16.04, GCC 6.4.0, jądro Linuksa 4.4.0.