Exemplo mínimo executável
Se um conceito não estiver claro, há um exemplo mais simples que você ainda não viu que o explica.
Nesse caso, esse exemplo é o hello world independente do assembly Linux x86_64:
ola.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 upstream .
Montar e executar:
as -o hello.o hello.S
ld -o hello.out hello.o
./hello.out
Produz o esperado:
hello
Agora vamos usar strace nesse exemplo:
env -i ASDF=qwer strace -o strace.log -s999 -v ./hello.out arg0 arg1
cat strace.log
Nós usamos:
strace.log
agora contém:
execve("./hello.out", ["./hello.out", "arg0", "arg1"], ["ASDF=qwer"]) = 0
write(1, "hello\n", 6) = 6
exit(0) = ?
+++ exited with 0 +++
Com um exemplo tão minimalista, todos os caracteres da saída são evidentes:
execve
line: mostra como é strace
executado hello.out
, incluindo argumentos e ambiente da CLI, conforme documentado emman execve
write
linha: mostra a chamada do sistema de gravação que fizemos. 6
é o comprimento da string "hello\n"
.
= 6
é o valor de retorno da chamada do sistema, que conforme documentado man 2 write
é o número de bytes gravados.
exit
line: mostra a chamada do sistema de saída que fizemos. Não há valor de retorno, pois o programa foi encerrado!
Exemplos mais complexos
A aplicação do strace é, obviamente, para ver quais chamadas do sistema os programas complexos estão realmente fazendo para ajudar a depurar / otimizar seu programa.
Notavelmente, a maioria das chamadas de sistema que você provavelmente encontrará no Linux possui wrappers glibc, muitas delas do POSIX .
Internamente, os wrappers glibc usam montagem inline mais ou menos assim: Como chamar uma chamada de sistema via sysenter na montagem inline?
O próximo exemplo que você deve estudar é um write
hello world POSIX :
main.c
#define _XOPEN_SOURCE 700
#include <unistd.h>
int main(void) {
char *msg = "hello\n";
write(1, msg, 6);
return 0;
}
Compile e execute:
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
Desta vez, você verá que várias chamadas de sistema estão sendo feitas pela glibc antes main
para configurar um ambiente agradável para main.
Isso ocorre porque agora não estamos usando um programa independente, mas um programa glibc mais comum, que permite a funcionalidade libc.
Então, em cada extremidade, strace.log
contém:
write(1, "hello\n", 6) = 6
exit_group(0) = ?
+++ exited with 0 +++
Portanto, concluímos que a write
função POSIX usa, surpresa !, a write
chamada de sistema Linux .
Também observamos que return 0
leva a uma exit_group
chamada em vez de exit
. Ha, eu não sabia sobre este! É por strace
isso que é tão legal. man exit_group
então explica:
Essa chamada do sistema é equivalente à exit (2), exceto que ela finaliza não apenas o encadeamento de chamada, mas todos os encadeamentos no grupo de encadeamentos do processo de chamada.
E aqui está outro exemplo em que estudei qual chamada de sistema dlopen
usa: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
Testado no Ubuntu 16.04, GCC 6.4.0, kernel do Linux 4.4.0.