Минимальный исполняемый пример
Если концепция не ясна, есть более простой пример, который вы не видели, который объясняет это.
В данном случае этот пример - свободно распространяемая сборка 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.