atualização das partes 1 2 e 3

This commit is contained in:
Blau Araujo 2025-04-28 10:51:02 -03:00
parent 7085ead45c
commit 325a77ae6c
3 changed files with 378 additions and 35 deletions

View file

@ -8,7 +8,8 @@
- Entender o que é o GDB; - Entender o que é o GDB;
- Conhecer o conceito de /depuração/; - Conhecer o conceito de /depuração/;
- Demonstrar o GDB em ação. - Demonstrar o GDB em ação;
- Apresentar uma lista de comandos essenciais.
** O que é o GDB ** O que é o GDB

View file

@ -4,6 +4,11 @@
* 2. Instalação e configurações de início * 2. Instalação e configurações de início
** Objetivos
- Como instalar o GDB nas principais distribuições GNU/Linux;
- Conhecer algumas possibilidades de customização.
** Instalação ** Instalação
*** Debian e derivados *** Debian e derivados

View file

@ -2,47 +2,174 @@
#+author: Blau Araujo #+author: Blau Araujo
#+email: blau@debxp.org #+email: blau@debxp.org
* 3. Formato ELF * 3. Binários executáveis
Todo arquivo executável pelo sistema é construído em um binário segundo um ** Objetivos
formato padrão. No GNU/Linux, esse formato é o *ELF* (/Executable and Linkable Format/),
que inclui no binário:
- O código de máquina do programa; - Entender o que é um programa para o sistema operacional;
- Dados de variáveis globais e estáticas; - Conhecer os elementos do formato de binários executáveis ELF;
- Dados constantes; - Investigar os símbolos de depuração;
- Tabelas de símbolos; - Descobrir como programas são executados.
- Informações de depuração (se incluídas).
Os primeiros bytes de um arquivo ELF contém um cabeçalho com diversas ** O que são programas
informações sobre o binário, o que nós podemos listar com o utilitário
=readelf=: 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 #+begin_example
:~$ readelf -h demo ENDEREÇO MAIS ALTO +-----------------------+ - Dados de ambiente
Cabeçalho ELF: | | - Argumentos de linha de comando
Magia: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 | PILHA | - Endereços de retorno
Classe: ELF64 | | - Dados locais de chamadas de funções
Dados: complemento 2, little endian +-----------------------+
Versão: 1 (actual) | ↓ |
OS/ABI: UNIX - System V | |
Versão ABI: 0 | |
Tipo: DYN (Position-Independent Executable file) +-----------------------+
Máquina: Advanced Micro Devices X86-64 | MMAP | - Mapeamento de bibliotecas compartilhadas
Versão: 0x1 +-----------------------+
Endereço do ponto de entrada: 0x1050 | |
Início dos cabeçalhos do programa: 64 (bytes no ficheiro) | HEAP | - Dados dinâmicos do programa
Start of section headers: 14944 (bytes no ficheiro) | |
Bandeiras: 0x0 +-----------------------+
Tamanho deste cabeçalho: 64 (bytes) | SEGMENTO .BSS | - Dados globais e estáticos não inicializados
Tamanho dos cabeçalhos do programa:56 (bytes) +-----------------------+
Nº de cabeçalhos do programa: 14 | SEGMENTO .DATA | - Dados globais e estáticos inicializados
Tamanho dos cabeçalhos de secção: 64 (bytes) +-----------------------+
Nº dos cabeçalhos de secção: 37 | SEGMENTO .RODATA | - Dados constantes
Índice de tabela de cadeias da secção: 36 +-----------------------+
| SEGMENTO .TEXT | - Instruções e funções do programa
ENDEREÇO MAIS BAIXO +-----------------------+
#+end_example #+end_example
** Símbolos no binário *** 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 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 variáveis. Quando os símbolos de depuração são incluídos no binário
@ -107,3 +234,213 @@ Com a opção =-g=:
:~$ gdb demo :~$ gdb demo
Reading symbols from demo... Reading symbols from demo...
#+end_example #+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