Минимальный исполняемый пример
Если концепция не ясна, есть более простой пример, который вы не видели, который объясняет это.
В данном случае этот пример - свободно распространяемая сборка Linux x86_64 (без libc):
hello.S
.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 вверх по течению .
Собрать и запустить:
as -o hello.o hello.S
ld -o hello.out hello.o
./hello.out
Выводит ожидаемое:
hello
Теперь давайте используем strace на этом примере:
env -i ASDF=qwer strace -o strace.log -s999 -v ./hello.out arg0 arg1
cat strace.log
Мы используем:
strace.log
теперь содержит:
execve("./hello.out", ["./hello.out", "arg0", "arg1"], ["ASDF=qwer"]) = 0
write(1, "hello\n", 6) = 6
exit(0) = ?
+++ exited with 0 +++
В таком минимальном примере каждый отдельный символ вывода очевиден:
execve
строка: показывает, как strace
выполняется hello.out
, включая аргументы CLI и среду, как описано вman execve
write
строка: показывает системный вызов write, который мы сделали. 6
это длина строки "hello\n"
.
= 6
это возвращаемое значение системного вызова, которое, как задокументировано, man 2 write
является количеством записанных байтов.
exit
строка: показывает системный вызов выхода, который мы сделали. Нет возвращаемого значения, так как программа вышла!
Более сложные примеры
Применение strace, конечно, позволяет увидеть, какие системные вызовы на самом деле выполняют сложные программы, чтобы помочь отладить / оптимизировать вашу программу.
Примечательно, что большинство системных вызовов, с которыми вы можете столкнуться в Linux, имеют оболочки glibc, многие из них из POSIX .
Внутри оболочки glibc используют встроенную сборку более или менее так: Как вызвать системный вызов через sysenter во встроенной сборке?
Следующий пример, который вы должны изучить, - это write
привет POSIX :
main.c
#define _XOPEN_SOURCE 700
#include <unistd.h>
int main(void) {
char *msg = "hello\n";
write(1, msg, 6);
return 0;
}
Скомпилируйте и запустите:
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
На этот раз вы увидите, что glibc выполняет кучу системных вызовов, main
чтобы настроить приятную среду для main.
Это потому, что мы сейчас не используем отдельно стоящую программу, а скорее более распространенную программу glibc, которая обеспечивает функциональность libc.
Затем, на каждом конце, strace.log
содержит:
write(1, "hello\n", 6) = 6
exit_group(0) = ?
+++ exited with 0 +++
Итак, мы заключаем, что write
функция POSIX использует, удивительно, write
системный вызов Linux .
Мы также наблюдаем, что return 0
приводит к exit_group
звонку вместо exit
. Ха, я не знал об этом! Вот почему strace
так круто. man exit_group
затем объясняет:
Этот системный вызов эквивалентен exit (2) за исключением того, что он завершает не только вызывающий поток, но и все потоки в группе потоков вызывающего процесса.
А вот еще один пример, где я изучал, какой системный вызов dlopen
использует: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
Протестировано в Ubuntu 16.04, GCC 6.4.0, ядре Linux 4.4.0.