diff --git a/mods/01/README.org b/mods/01/README.org index 381e551..a9138c4 100644 --- a/mods/01/README.org +++ b/mods/01/README.org @@ -8,7 +8,8 @@ - Entender o que é o GDB; - 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 diff --git a/mods/02/README.org b/mods/02/README.org index 0a4e7f7..e78d02f 100644 --- a/mods/02/README.org +++ b/mods/02/README.org @@ -4,6 +4,11 @@ * 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 *** Debian e derivados diff --git a/mods/03/README.org b/mods/03/README.org index 5f3f749..9d7f069 100644 --- a/mods/03/README.org +++ b/mods/03/README.org @@ -2,47 +2,174 @@ #+author: Blau Araujo #+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 -formato padrão. No GNU/Linux, esse formato é o *ELF* (/Executable and Linkable Format/), -que inclui no binário: +** Objetivos -- O código de máquina do programa; -- Dados de variáveis globais e estáticas; -- Dados constantes; -- Tabelas de símbolos; -- Informações de depuração (se incluídas). +- 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. -Os primeiros bytes de um arquivo ELF contém um cabeçalho com diversas -informações sobre o binário, o que nós podemos listar com o utilitário -=readelf=: +** 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 -:~$ readelf -h demo -Cabeçalho ELF: - Magia: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 - Classe: ELF64 - 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 - Versão: 0x1 - Endereço do ponto de entrada: 0x1050 - Início dos cabeçalhos do programa: 64 (bytes no ficheiro) - Start of section headers: 14944 (bytes no ficheiro) - Bandeiras: 0x0 - Tamanho deste cabeçalho: 64 (bytes) - Tamanho dos cabeçalhos do programa:56 (bytes) - Nº de cabeçalhos do programa: 14 - Tamanho dos cabeçalhos de secção: 64 (bytes) - Nº dos cabeçalhos de secção: 37 - Índice de tabela de cadeias da secção: 36 +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 -** 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 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 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