446 lines
16 KiB
Org Mode
446 lines
16 KiB
Org Mode
#+title: Curso prático de introdução ao GDB
|
|
#+author: Blau Araujo
|
|
#+email: blau@debxp.org
|
|
|
|
* 3. Binários executáveis
|
|
|
|
** Objetivos
|
|
|
|
- Entender o que é um programa para o sistema operacional;
|
|
- Conhecer os elementos do formato de binários executáveis ELF;
|
|
- Investigar os símbolos de depuração;
|
|
- Descobrir como programas são executados.
|
|
|
|
** O que são programas
|
|
|
|
Do ponto de vista do sistema operacional GNU/Linux (e de sistemas UNIX-like em
|
|
geral), um programa é sempre um arquivo binário contendo um conjunto de instruções
|
|
que a CPU do computador é capaz de executar. Isso não significa que todo programa
|
|
é escrito para ser transformado em um arquivo binário e, só então, ser executado!
|
|
É preciso entender que, o que nós escrevemos e chamamos de "programa", pode
|
|
não ser exatamente o que o sistema operacional vai executar.
|
|
|
|
Para nós, programas podem ser...
|
|
|
|
- Escritos diretamente na linguagem que a CPU é capaz de interpretar e executar
|
|
(código de máquina, com baixo nível de abstração), e apenas armazenados como
|
|
arquivos binários;
|
|
- Escritos usando mnemônicos e pseudoinstruções relativamente simples (linguagem
|
|
de montagem, ou assembly), e então montados em código de máquina para gerar
|
|
arquivos binários executáveis;
|
|
- Escritos em linguagens com expressões e regras de sintaxe mais próximas da
|
|
escrita humana (alto nível de abstração), e depois traduzidos para código de
|
|
máquina por meio de um processo de compilação;
|
|
- Escritos em linguagens de alto nível, projetadas para serem lidas e
|
|
interpretadas em tempo de execução por outro programa (o interpretador), que é
|
|
um binário executável.
|
|
|
|
Mas o sistema operacional só executa arquivos binários que seguem uma convenção
|
|
específica de formato.
|
|
|
|
*** O que significa executar
|
|
|
|
Em sistemas GNU/Linux e UNIX-like, executar um programa significa:
|
|
|
|
- Criar uma estrutura de dados para gerenciar a execução do programa (processo);
|
|
- Carregar o conteúdo do binário executável na memória;
|
|
- Carregar na memória o conteúdo de outros arquivos necessários para o
|
|
funcionamento do programa, como bibliotecas compartilhadas;
|
|
- Disponibilizar um espaço de endereços de memória para uso exclusivo do
|
|
programa;
|
|
- Dar acesso a dados do ambiente de execução, como variáveis exportadas e
|
|
argumentos passados na invocação do programa;
|
|
- Conceder acesso a recursos diversos do sistema, como dispositivos de terminal
|
|
e descritores de arquivos;
|
|
- Administrar a passagem do controle da CPU para o programa;
|
|
- Monitorar o término do programa, mantendo sua estrutura de processo até que o
|
|
término seja completamente tratado.
|
|
|
|
Para que o sistema possa realizar todas essas etapas corretamente, é necessário
|
|
que o conteúdo do binário executável siga uma convenção específica de organização
|
|
de dados. Essa convenção define, por exemplo, onde se encontram as instruções do
|
|
programa, os dados que ele utiliza e as informações sobre bibliotecas externas.
|
|
No sistema GNU/Linux, o formato padrão utilizado para isso é o ELF (/Executable
|
|
and Linkable Format/).
|
|
|
|
** O formato ELF
|
|
|
|
Um arquivo no formato ELF possui uma estrutura interna bem definida que informa
|
|
ao sistema onde estão -- e onde devem ser carregados na memória -- os dados e os
|
|
elementos essenciais para a execução do programa, entre eles:
|
|
|
|
- As instruções do programa em código de máquina (seção =.text=);
|
|
- Dados constantes (=.rodata=);
|
|
- Dados de variáveis globais e estáticas (seções =.data= e =.bss=);
|
|
- Tabelas de símbolos do programa e de seções;
|
|
- Tabelas de símbolos de depuração (se incluídas).
|
|
|
|
*** Passagem do arquivo para a memória
|
|
|
|
O conteúdo do binário no formato ELF é organizado em seções descritas em várias
|
|
tabelas. Quando um processo é iniciado para executar o programa, essas seções são
|
|
mapeadas para segmentos de memória correspondentes em seu espaço de endereços.
|
|
Isso é feito por uma biblioteca do sistema chamada de /loader/ (carregador).
|
|
|
|
No GNU/Linux, o /loader/ é a biblioteca compartilhada =ld-linux= e, entre suas várias
|
|
atribuições, nós podemos destacar:
|
|
|
|
- Carregar o programa na memória, mapeando as seções do arquivo ELF para os
|
|
segmentos apropriados no espaço de endereços do processo.
|
|
- Configurar o ambiente de execução, incluindo a configuração de uma estrutura
|
|
de pilha (/stack/) no espaço de endereços do processo.
|
|
- Resolver dependências de bibliotecas compartilhadas, carregando-as no espaço
|
|
de endereços do processo, se for necessário.
|
|
|
|
*** Layout de memória
|
|
|
|
Após o carregamento do binário e a configuração do ambiente de execução, o espaço
|
|
de endereços do processo estará organizado desta forma:
|
|
|
|
#+begin_example
|
|
ENDEREÇO MAIS ALTO +-----------------------+ - Dados de ambiente
|
|
| | - Argumentos de linha de comando
|
|
| PILHA | - Endereços de retorno
|
|
| | - Dados locais de chamadas de funções
|
|
+-----------------------+
|
|
| ↓ |
|
|
| |
|
|
| |
|
|
+-----------------------+
|
|
| MMAP | - Mapeamento de bibliotecas compartilhadas
|
|
+-----------------------+
|
|
| |
|
|
| HEAP | - Dados dinâmicos do programa
|
|
| |
|
|
+-----------------------+
|
|
| SEGMENTO .BSS | - Dados globais e estáticos não inicializados
|
|
+-----------------------+
|
|
| SEGMENTO .DATA | - Dados globais e estáticos inicializados
|
|
+-----------------------+
|
|
| SEGMENTO .RODATA | - Dados constantes
|
|
+-----------------------+
|
|
| SEGMENTO .TEXT | - Instruções e funções do programa
|
|
ENDEREÇO MAIS BAIXO +-----------------------+
|
|
#+end_example
|
|
|
|
*** Utilitários para examinar binários ELF
|
|
|
|
**** Utilitário =readelf=
|
|
|
|
Imprime cabeçalhos e seções de binários ELF.
|
|
|
|
Exemplos de uso:
|
|
|
|
#+begin_example
|
|
readelf -h PROGRAMA Informações do cabeçalho ELF
|
|
readelf -l PROGRAMA Lista de cabeçalhos do programa
|
|
readelf -S PROGRAMA Lista dos cabeçalhos de seções do arquivo
|
|
readelf -x SEÇÂO PROGRAMA Exibe o conteúdo de uma seção em hexadecimal
|
|
#+end_example
|
|
|
|
**** Utilitário =ldd=
|
|
|
|
Lista dependências de bibliotecas compartilhadas.
|
|
|
|
Exemplo de uso:
|
|
|
|
#+begin_example
|
|
ldd PROGRAMA Lista as dependências dinâmicas do programa
|
|
#+end_example
|
|
|
|
**** Utilitário =nm=
|
|
|
|
Lista símbolos declarados no binário, como funções e variáveis globais.
|
|
|
|
Exemplo de uso:
|
|
|
|
#+begin_example
|
|
nm PROGRAMA Lista todos os símbolos no programa.
|
|
#+end_example
|
|
|
|
**** Utilitário =objdump=
|
|
|
|
Analisa, desmonta e imprime informações detalhadas sobre as seções de um
|
|
programa.
|
|
|
|
Exemplo de uso:
|
|
|
|
#+begin_example
|
|
objdump -d PROGRAMA Desmonta e exibe o conteúdo das seções executáveis do programa.
|
|
#+end_example
|
|
|
|
** Símbolos de depuração
|
|
|
|
Símbolos são nomes associados a elementos do programa, como funções e
|
|
variáveis. Quando os símbolos de depuração são incluídos no binário
|
|
(compilando com =-g=, por exemplo), o GDB é capaz de:
|
|
|
|
- Exibir nomes legíveis, em vez de endereços de memória;
|
|
- Acompanhar o fluxo do programa com o código-fonte;
|
|
- Receber definições de /breakpoints/ por nomes de funções;
|
|
- Fazer a associação de códigos binários com as linhas do código-fonte;
|
|
- Acessar nomes de variáveis, tipos, estruturas, etc.
|
|
|
|
Nós podemos verificar se o programa foi compilado com os símbolos de
|
|
depuração com o utilitário =file=...
|
|
|
|
Sem a opção =-g=:
|
|
|
|
#+begin_example
|
|
:~$ gcc -o demo demo.c
|
|
:~$ file demo
|
|
demo: ELF 64-bit LSB pie executable [...] not stripped
|
|
#+end_example
|
|
|
|
Com a opção =-g=:
|
|
|
|
#+begin_example
|
|
:~$ gcc -g -o demo demo.c
|
|
:~$ file demo
|
|
demo: ELF 64-bit LSB pie executable [...] with debug_info, not stripped
|
|
#+end_example
|
|
|
|
Repare que, desta vez, nós temos a informação =with debug_info= no final
|
|
da linha.
|
|
|
|
Nós também podemos verificar se há símbolos de depuração a partir das
|
|
informações no formato ELF:
|
|
|
|
#+begin_example
|
|
:~$ readelf -S demo | grep debug
|
|
[28] .debug_aranges PROGBITS 0000000000000000 00003037
|
|
[29] .debug_info PROGBITS 0000000000000000 00003067
|
|
[30] .debug_abbrev PROGBITS 0000000000000000 00003180
|
|
[31] .debug_line PROGBITS 0000000000000000 0000324a
|
|
[32] .debug_str PROGBITS 0000000000000000 000032ba
|
|
[33] .debug_line_str PROGBITS 0000000000000000 00003367
|
|
#+end_example
|
|
|
|
Mas o próprio GDB informa se há símbolos de depuração ao iniciar...
|
|
|
|
Sem a opção =-g=:
|
|
|
|
#+begin_example
|
|
:~$ gcc -o demo demo.c
|
|
:~$ gdb demo
|
|
Reading symbols from demo...
|
|
(No debugging symbols found in demo)
|
|
#+end_example
|
|
|
|
Com a opção =-g=:
|
|
|
|
#+begin_example
|
|
:~$ gcc -g -o demo demo.c
|
|
:~$ gdb demo
|
|
Reading symbols from demo...
|
|
#+end_example
|
|
|
|
** Inspecionando a execução de programas
|
|
|
|
Com o GDB, nós podemos obter várias informações relacionadas à execução de
|
|
programas.
|
|
|
|
Utilizando o programa =demo.c= como exemplo:
|
|
|
|
#+begin_example
|
|
:~$ gcc -g -o demo demo.c
|
|
:~$ gdb ./demo
|
|
Reading symbols from ./demo...
|
|
(gdb)
|
|
#+end_example
|
|
|
|
*** Listagem de símbolos
|
|
|
|
#+begin_example
|
|
(gdb) info files
|
|
Symbols from "/home/blau/git/gdb-pratico/mods/01/demo".
|
|
Local exec file:
|
|
`/home/blau/git/gdb-pratico/mods/01/demo', file type elf64-x86-64.
|
|
Entry point: 0x1050
|
|
0x0000000000000350 - 0x0000000000000370 is .note.gnu.property
|
|
0x0000000000000370 - 0x0000000000000394 is .note.gnu.build-id
|
|
0x0000000000000394 - 0x00000000000003b0 is .interp
|
|
0x00000000000003b0 - 0x00000000000003d4 is .gnu.hash
|
|
0x00000000000003d8 - 0x0000000000000480 is .dynsym
|
|
0x0000000000000480 - 0x000000000000050f is .dynstr
|
|
0x0000000000000510 - 0x000000000000051e is .gnu.version
|
|
0x0000000000000520 - 0x0000000000000550 is .gnu.version_r
|
|
0x0000000000000550 - 0x0000000000000610 is .rela.dyn
|
|
0x0000000000000610 - 0x0000000000000628 is .rela.plt
|
|
0x0000000000001000 - 0x0000000000001017 is .init
|
|
0x0000000000001020 - 0x0000000000001040 is .plt
|
|
0x0000000000001040 - 0x0000000000001048 is .plt.got
|
|
0x0000000000001050 - 0x000000000000119b is .text
|
|
0x000000000000119c - 0x00000000000011a5 is .fini
|
|
0x0000000000002000 - 0x0000000000002013 is .rodata
|
|
0x0000000000002014 - 0x0000000000002048 is .eh_frame_hdr
|
|
0x0000000000002048 - 0x0000000000002114 is .eh_frame
|
|
0x0000000000002114 - 0x0000000000002134 is .note.ABI-tag
|
|
0x0000000000003dd0 - 0x0000000000003dd8 is .init_array
|
|
0x0000000000003dd8 - 0x0000000000003de0 is .fini_array
|
|
0x0000000000003de0 - 0x0000000000003fc0 is .dynamic
|
|
0x0000000000003fc0 - 0x0000000000003fe8 is .got
|
|
0x0000000000003fe8 - 0x0000000000004008 is .got.plt
|
|
0x0000000000004008 - 0x0000000000004018 is .data
|
|
0x0000000000004018 - 0x0000000000004020 is .bss
|
|
#+end_example
|
|
|
|
*** Listagem de símbolos conhecidos
|
|
|
|
Funções:
|
|
|
|
#+begin_example
|
|
(gdb) info functions
|
|
All defined functions:
|
|
|
|
File demo.c:
|
|
8: int main();
|
|
3: int soma(int, int);
|
|
|
|
Non-debugging symbols:
|
|
0x0000000000001000 _init
|
|
0x0000000000001030 printf@plt
|
|
0x0000000000001040 __cxa_finalize@plt
|
|
0x0000000000001050 _start
|
|
0x0000000000001080 deregister_tm_clones
|
|
0x00000000000010b0 register_tm_clones
|
|
0x00000000000010f0 __do_global_dtors_aux
|
|
0x0000000000001130 frame_dummy
|
|
0x000000000000119c _fini
|
|
#+end_example
|
|
|
|
Variáveis:
|
|
|
|
#+begin_example
|
|
(gdb) info variables
|
|
All defined variables:
|
|
|
|
Non-debugging symbols:
|
|
0x0000000000002000 _IO_stdin_used
|
|
0x0000000000002014 __GNU_EH_FRAME_HDR
|
|
0x0000000000002110 __FRAME_END__
|
|
0x0000000000002114 __abi_tag
|
|
0x0000000000003dd0 __frame_dummy_init_array_entry
|
|
0x0000000000003dd8 __do_global_dtors_aux_fini_array_entry
|
|
0x0000000000003de0 _DYNAMIC
|
|
0x0000000000003fe8 _GLOBAL_OFFSET_TABLE_
|
|
0x0000000000004008 __data_start
|
|
0x0000000000004008 data_start
|
|
0x0000000000004010 __dso_handle
|
|
0x0000000000004018 __TMC_END__
|
|
0x0000000000004018 __bss_start
|
|
0x0000000000004018 _edata
|
|
0x0000000000004018 completed
|
|
0x0000000000004020 _end
|
|
#+end_example
|
|
|
|
*** Examinando o estado da memória
|
|
|
|
A inspeção da memória só pode ser feita com o início de um processo associado
|
|
à execução do programa, portanto...
|
|
|
|
#+begin_example
|
|
(gdb) b main
|
|
Breakpoint 1 at 0x115b: file demo.c, line 9.
|
|
(gdb) r
|
|
Starting program: /home/blau/git/gdb-pratico/mods/01/demo
|
|
[Thread debugging using libthread_db enabled]
|
|
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
|
|
|
|
Breakpoint 1, main () at demo.c:9
|
|
9 int x = 10;
|
|
#+end_example
|
|
|
|
Para exibir o layout do mapeamento de memória do processo:
|
|
|
|
#+begin_example
|
|
(gdb) info proc mappings
|
|
process 1029871
|
|
Mapped address spaces:
|
|
|
|
Start Addr End Addr Size Offset Perms File
|
|
0x0000555555554000 0x0000555555555000 0x1000 0x0 r--p /home/blau/git/gdb-pratico/mods/01/demo
|
|
0x0000555555555000 0x0000555555556000 0x1000 0x1000 r-xp /home/blau/git/gdb-pratico/mods/01/demo
|
|
0x0000555555556000 0x0000555555557000 0x1000 0x2000 r--p /home/blau/git/gdb-pratico/mods/01/demo
|
|
0x0000555555557000 0x0000555555558000 0x1000 0x2000 r--p /home/blau/git/gdb-pratico/mods/01/demo
|
|
0x0000555555558000 0x0000555555559000 0x1000 0x3000 rw-p /home/blau/git/gdb-pratico/mods/01/demo
|
|
0x00007ffff7da3000 0x00007ffff7da6000 0x3000 0x0 rw-p
|
|
0x00007ffff7da6000 0x00007ffff7dce000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
|
|
0x00007ffff7dce000 0x00007ffff7f33000 0x165000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
|
|
0x00007ffff7f33000 0x00007ffff7f89000 0x56000 0x18d000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
|
|
0x00007ffff7f89000 0x00007ffff7f8d000 0x4000 0x1e2000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
|
|
0x00007ffff7f8d000 0x00007ffff7f8f000 0x2000 0x1e6000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
|
|
0x00007ffff7f8f000 0x00007ffff7f9c000 0xd000 0x0 rw-p
|
|
0x00007ffff7fbf000 0x00007ffff7fc1000 0x2000 0x0 rw-p
|
|
0x00007ffff7fc1000 0x00007ffff7fc5000 0x4000 0x0 r--p [vvar]
|
|
0x00007ffff7fc5000 0x00007ffff7fc7000 0x2000 0x0 r-xp [vdso]
|
|
0x00007ffff7fc7000 0x00007ffff7fc8000 0x1000 0x0 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
0x00007ffff7fc8000 0x00007ffff7ff0000 0x28000 0x1000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
0x00007ffff7ff0000 0x00007ffff7ffb000 0xb000 0x29000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
0x00007ffff7ffb000 0x00007ffff7ffd000 0x2000 0x34000 r--p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
0x00007ffff7ffd000 0x00007ffff7ffe000 0x1000 0x36000 rw-p /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
|
|
0x00007ffff7ffe000 0x00007ffff7fff000 0x1000 0x0 rw-p
|
|
0x00007ffffffde000 0x00007ffffffff000 0x21000 0x0 rw-p [stack]
|
|
#+end_example
|
|
|
|
*** Observando o conteúdo da pilha
|
|
|
|
Terminando a execução anterior (=kill=):
|
|
|
|
#+begin_example
|
|
(gdb) k
|
|
[Inferior 1 (process 1030168) killed]
|
|
#+end_example
|
|
|
|
Executando novamente:
|
|
|
|
#+begin_example
|
|
(gdb) r
|
|
Starting program: /home/blau/git/gdb-pratico/mods/01/demo a b c
|
|
[Thread debugging using libthread_db enabled]
|
|
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
|
|
|
|
Breakpoint 1, main () at demo.c:9
|
|
9 int x = 10;
|
|
#+end_example
|
|
|
|
Avançar a execução até a chamada da função =soma=:
|
|
|
|
#+begin_example
|
|
(gdb) n
|
|
10 int y = 20;
|
|
(gdb) n
|
|
11 int z = soma(x, y);
|
|
#+end_example
|
|
|
|
Inspecionar o conteúdo da pilha neste momento:
|
|
|
|
#+begin_example
|
|
(gdb) bt
|
|
#0 main () at demo.c:11
|
|
#+end_example
|
|
|
|
Avançar a execução entrando na função =soma=:
|
|
|
|
#+begin_example
|
|
(gdb) s
|
|
soma (a=10, b=20) at demo.c:4
|
|
4 int resultado = a + b;
|
|
#+end_example
|
|
|
|
Inspecionar o conteúdo da pilha:
|
|
|
|
#+begin_example
|
|
(gdb) bt
|
|
#0 soma (a=10, b=20) at demo.c:4
|
|
#1 0x0000555555555178 in main () at demo.c:11
|
|
#+end_example
|
|
|
|
*** Listagem de bibliotecas compartilhadas carregadas
|
|
|
|
#+begin_example
|
|
(gdb) info sharedlibrary
|
|
From To Syms Read Shared Object Library
|
|
0x00007ffff7fc8000 0x00007ffff7fef2d1 Yes /lib64/ld-linux-x86-64.so.2
|
|
0x00007ffff7dce400 0x00007ffff7f3217d Yes /lib/x86_64-linux-gnu/libc.so.6
|
|
#+end_example
|