Há uma informação importante faltando na saída de gdb
: as permissões das páginas. (Eles são mostrados no Solaris e FreeBSD , mas não no Linux.) Você pode ver esses olhando para /proc/<pid>/maps
; os mapas para o seu exemplo de Protostar mostram
$ cat /proc/.../maps
08048000-08049000 r-xp 00000000 00:0f 2925 /opt/protostar/bin/stack6
08049000-0804a000 rwxp 00000000 00:0f 2925 /opt/protostar/bin/stack6
b7e96000-b7e97000 rwxp 00000000 00:00 0
b7e97000-b7fd5000 r-xp 00000000 00:0f 759 /lib/libc-2.11.2.so
b7fd5000-b7fd6000 ---p 0013e000 00:0f 759 /lib/libc-2.11.2.so
b7fd6000-b7fd8000 r-xp 0013e000 00:0f 759 /lib/libc-2.11.2.so
b7fd8000-b7fd9000 rwxp 00140000 00:0f 759 /lib/libc-2.11.2.so
b7fd9000-b7fdc000 rwxp 00000000 00:00 0
b7fe0000-b7fe2000 rwxp 00000000 00:00 0
b7fe2000-b7fe3000 r-xp 00000000 00:00 0 [vdso]
b7fe3000-b7ffe000 r-xp 00000000 00:0f 741 /lib/ld-2.11.2.so
b7ffe000-b7fff000 r-xp 0001a000 00:0f 741 /lib/ld-2.11.2.so
b7fff000-b8000000 rwxp 0001b000 00:0f 741 /lib/ld-2.11.2.so
bffeb000-c0000000 rwxp 00000000 00:0f 0 [stack]
(O exemplo Protostar é executado em uma VM que é fácil de hackear, presumivelmente para tornar os exercícios controláveis: não há proteção NX nem ASLR.)
Você verá acima que o que parece ser um mapeamento repetido em gdb
corresponde a diferentes mapeamentos com diferentes permissões. O segmento de texto é mapeado como somente leitura e executável; o segmento de dados é mapeado somente leitura; O BSS e o heap são mapeados como leitura-gravação. Idealmente, o segmento de dados, o BSS e o heap não são executáveis, mas este exemplo não possui suporte ao NX, portanto, eles são executáveis. Cada biblioteca compartilhada obtém seu próprio mapeamento para seu segmento de texto, segmento de dados e BSS. O quarto mapeamento é um segmento não legível, não gravável e não executável, normalmente usado para proteger contra estouros de buffer (embora, dada a idade do kernel e da biblioteca C usada aqui, isso possa ser algo diferente).
O deslocamento, quando fornecido, indica o deslocamento dos dados no arquivo, o que não necessariamente tem muito a ver com sua posição no espaço de endereço. Quando carregado, isso está sujeito a restrições de alinhamento; Por exemplo, os cabeçalhos do programa libc-2.11.2.so
especificam dois cabeçalhos "LOAD":
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x13d2f4 0x13d2f4 R E 0x1000
LOAD 0x13e1cc 0x0013f1cc 0x0013f1cc 0x027b0 0x0577c RW 0x1000
(Use readelf -l
para ver isso.)
Isso pode resultar em vários mapeamentos no mesmo deslocamento, com diferentes endereços virtuais, se as seções mapeadas para os segmentos tiverem diferentes sinalizadores de proteção. No caso de stack6
:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x00604 0x00604 R E 0x1000
LOAD 0x000604 0x08049604 0x08049604 0x00114 0x00128 RW 0x1000
(Isso também explica o tamanho pequeno mostrado por proc info mappings
para stack6
: cada cabeçalho solicita menos que 4KiB, com um alinhamento 4KiB, então obtém dois mapeamentos 4KiB com o mesmo deslocamento em endereços diferentes.)
Os mapeamentos em branco correspondem aos mapeamentos anônimos; veja man 5 proc
para detalhes. Você precisa dividir mmap
em gdb
para determinar o que eles correspondem.
Você não pode ver os mapeamentos do kernel (exceto o legado vsyscall
em algumas arquiteturas) porque eles não importam da perspectiva do processo (eles são inacessíveis).
Eu não sei de uma melhor opção gdb
, eu sempre uso /proc/$$/maps
.
Veja Como os programas são executados: binários ELF para detalhes sobre o formato ELF como lido pelo kernel, e como ele mapeia para alocações de memória; tem ponteiros para muito mais material de referência.