quarta-feira, 4 de maio de 2011

Re-post Analisando aplicações Linux com strace e ltrace

Neste artigo vamos estu­dar o uso das fer­ra­men­tas strace e ltrace para anal­isar apli­cações Linux.

Alguma vez você já ten­tou exe­cu­tar uma apli­cação de linha de comando em Linux que sim­ples­mente retor­nava sem exibir nen­huma men­sagem de erro? Ou então um erro de seg­men­ta­tion fault que não fazia sen­tido? Já pre­cisou enten­der porque uma apli­cação estava demor­ando demais para exe­cu­tar? Ou tra­vando sem nen­huma explicação?

Estas são situ­ações bas­tante comuns em Linux, seja no uni­verso desk­top ou embar­cado. Mas você não pre­cisa se deses­perar! Você esta em um ambi­ente ideal para debugar apli­cações.

Olhe só este exem­plo. O net­cat (ou nc) é uma pop­u­lar fer­ra­menta de rede para tra­bal­har com o pro­to­colo TCP/IP. O comando abaixo visa se conec­tar em um servi­dor na máquina local e na porta 1234.

$ nc localhost 1234
$

Veja que o comando sim­ples­mente retornou sem exibir nen­huma men­sagem de erro. O que acon­te­ceu? Qual a mel­hor forma de anal­isar este tipo de situ­ação?

É aí que entram as fer­ra­men­tas strace e ltrace.

STRACE

O strace é uma fer­ra­menta que mon­i­tora as chamadas de sis­tema (sys­tem calls) e os sinais rece­bidos pela apli­cação. A maneira mais comum de executá-la é pas­sando a apli­cação a ser mon­i­torada como parâmetro.

Voltando ao nosso exem­plo, veja como ela fun­ciona:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22



$ strace nc localhost 1234
execve("/bin/nc", ["nc", "localhost", "2000"], [/* 37 vars */]) = 0
brk(0) = 0x9864000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7835000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=127096, ...}) = 0
mmap2(NULL, 127096, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7815000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
..........
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl64(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl64(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
select(4, NULL, [3], NULL, NULL) = 1 (out [3])
getsockopt(3, SOL_SOCKET, SO_ERROR, [111], [4]) = 0
fcntl64(3, F_SETFL, O_RDWR) = 0
close(3) = 0
close(-1) = -1 EBADF (Bad file descriptor)
exit_group(1) = ?

Cada linha é uma chamada de sis­tema com os parâmet­ros e o código de retorno. Foram 262 chamadas ao sis­tema no total, e por questões de espaço, estou exibindo ape­nas as 10 primeiras e as 10 últi­mas lin­has. Mas estas lin­has são sufi­cientes para enten­der o que esta acon­te­cendo ao exe­cu­tar o comando nc.

Na linha 16, a chamada à função con­nect() esta retor­nando erro (-1) ao se conec­tar na minha máquina (127.0.0.1) na porta 1234 (não existe nen­hum servi­dor na minha máquina escu­tando a porta 1234).

O exem­plo foi bem sim­ples, mas nos dá uma noção do poder desta fer­ra­menta. A grande van­tagem é que não pre­cisamos do código-fonte da apli­cação, nem de sím­bo­los no arquivo binário. Tudo isso fun­ciona através de uma fun­cional­i­dade fornecida pelo ker­nel, chamada de ptrace, que pos­si­bilita que um processo possa con­tro­lar outro processo, manip­u­lando seus descritores de arquivo, memória, reg­istradores, etc. É isso que faz o strace.

É claro que exis­tem muitas out­ras apli­cações para o strace. Basta usar a imag­i­nação.

Você já instalou alguma apli­cação mas não sabia onde ela bus­cava o arquivo de con­fig­u­ração? O comando abaixo pode te respon­der:

$ strace app_name 2>&1 | grep "open" | grep "\/etc"

Com este comando, bus­camos todas as chamadas open() em arquivos den­tro de “/etc”.

Perceba tam­bém que o strace não serve ape­nas para debug. É tam­bém a fer­ra­menta per­feita para você enten­der o fun­ciona­mento de uma apli­cação e até fazer engen­haria reversa quando o que você tem é ape­nas o binário.

Além disso, com o strace podemos fazer análise de per­for­mance através do parâmetro “–c”.

$ strace -c du /home/sprado
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
70.16 0.273238 12 23144 getdents64
28.63 0.111506 1 104334 fstatat64
0.43 0.001680 0 11559 write
0.29 0.001130 0 23734 close
0.24 0.000927 0 34716 fcntl64
0.23 0.000881 0 12148 openat
0.02 0.000096 0 12166 fstat64
0.00 0.000000 0 3 read
0.00 0.000000 0 30 13 open
0.00 0.000000 0 1 execve
0.00 0.000000 0 3 3 access
0.00 0.000000 0 14 brk
0.00 0.000000 0 2 munmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 21 mmap2
0.00 0.000000 0 1 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.389458 221880 16 total

Temos algu­mas infor­mações valiosas na saída deste comando. Os con­ta­dores de cada chamada do sis­tema (calls) e tempo de proces­sa­mento (sec­onds) são extrema­mente úteis quando quer­e­mos saber onde esta o gar­galo na exe­cução da nossa aplicação.

Exis­tem ainda muitas out­ras fun­cional­i­dades. Você pode mon­i­torar ape­nas as chamadas de sis­tema rela­cionadas à rede usando “trace=network” como parâmetro, ou então a comu­ni­cação entre proces­sos usando “trace=ipc”. Uma descrição com­pleta das fun­cional­i­dades do strace podem ser encon­tradas no man­ual da fer­ra­menta.

$ man strace

LTRACE

O ltrace tem as mes­mas car­ac­terís­ti­cas do strace, mas ao invés de mon­i­torar as chamadas do sis­tema, ele mon­i­tora as chamadas às funções das bib­liote­cas car­regadas dinami­ca­mente.

Veja como ficaria o nosso exem­plo do “nc” com o ltrace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22



$ ltrace nc localhost 1234
__libc_start_main(0x804a700, 3, 0xbf868d94, 0x804caa0, 0x804ca90
getopt(3, 0xbf868d94, "46Ddhi:jklnP:p:q:rSs:tT:Uuvw:X:x"...) = -1
getservbyname("1234", "tcp") = NULL
strchr("1234", '-') = NULL
strtoul(0xbf86a695, 0xbf866b4c, 10, 0xbf868d94, 0x804d0f8) = 1234
calloc(1, 6) = 0x091c0520
getaddrinfo("localhost", "1234", 0xbf866b78, 0xbf866b4c) = 0
socket(2, 1, 6) = 3
fcntl(3, 3, 0, 0xbf866b4c, 0x8e49ae) = 2
fcntl(3, 4, 2050, 0xbf866b4c, 0x8e49ae) = 0
connect(3, 0x91c0cf0, 16, 0xbf866b4c, 0x8e49ae) = -1
__errno_location() = 0xb77eb688
select(4, 0, 0xbf866a98, 0, 0) = 1
getsockopt(3, 1, 4, 0xbf866b44, 0xbf866b40) = 0
fcntl(3, 4, 2, 0xbf866b44, 0xbf866b40) = 0
close(3) = 0
freeaddrinfo(0x091c0cd0) =
close(-1) = -1
exit(1
+++ exited (status 1) +++


Veja que, da mesma forma, con­seguimos iden­ti­ficar o prob­lema na chamada a con­nect() na linha 12.

O detalhe aqui é que esta fer­ra­menta mon­i­tora ape­nas a chamada às funções de bib­lioteca linkadas dinami­ca­mente com a apli­cação, e por isso você não con­seguirá usá-la se a apli­cação for linkada esta­ti­ca­mente com as bib­liote­cas do sistema.

NO UNIVERSO EMBEDDED

A uti­liza­ção destas fer­ra­men­tas em Linux embar­cado é idên­tica. A única difer­ença é que você irá pre­cisar cross-compilar o strace e o ltrace para exe­cu­tar na sua arquitetura-alvo.

O con­hec­i­mento e o uso deste tipo de fer­ra­menta é essen­cial para o desen­volve­dor Linux. Seja para debugar um prob­lema em deter­mi­nada apli­cação, mon­i­torar sua per­for­mance ou apren­der sobre seu fun­ciona­mento, inve­stir um tempo para conhecê-la mais pro­fun­da­mente é extrema­mente válido. Mas não existe con­hec­i­mento sem prática. Por­tanto, mãos à obra!

Um abraço,

Ser­gio Prado

Nenhum comentário:

Postar um comentário

Observação: somente um membro deste blog pode postar um comentário.