objdump vs / proc / pid / maps的虚拟内存地址?

时间:2014-02-24 23:43:38

标签: c linux memory virtual-memory

当程序加载/运行时,我试图了解程序的可执行程序集的确切位置。我发现有两个资源在讨论这个问题,但它们有点难以阅读:

所以,这是一个简短的例子;我感兴趣的是tail程序的可执行部分最终在哪里。基本上,objdump告诉我这个:

$ objdump -dj .text /usr/bin/tail | head -10

/usr/bin/tail:     file format elf32-i386
Disassembly of section .text:

08049100 <.text>:
 8049100:   31 ed                   xor    %ebp,%ebp
 8049102:   5e                      pop    %esi
 8049103:   89 e1                   mov    %esp,%ecx
...

我假设我看到了tail&#39; main()&#39;在这里制作,没有剥离符号。无论如何,可执行部分的开头是,0x08049100;我对它最终到达的地方感兴趣。

然后,我在后台运行tail,得到它的pid:

$ /usr/bin/tail -f & echo $!
28803

...我检查了它的/proc/pid/maps

$ cat /proc/28803/maps
00547000-006a8000 r-xp 00000000 08:05 3506       /lib/i386-linux-gnu/libc-2.13.so
...
008c6000-008c7000 r-xp 00000000 00:00 0          [vdso]
08048000-08054000 r-xp 00000000 08:05 131469     /usr/bin/tail
08054000-08055000 r--p 0000b000 08:05 131469     /usr/bin/tail
08055000-08056000 rw-p 0000c000 08:05 131469     /usr/bin/tail
08af1000-08b12000 rw-p 00000000 00:00 0          [heap]
b76de000-b78de000 r--p 00000000 08:05 139793     /usr/lib/locale/locale-archive
...
bf845000-bf866000 rw-p 00000000 00:00 0          [stack]

现在我有tail三次 - 但可执行网段r-xp.text?)显然位于0x08048000(显然{{3}的地址另请参阅was standardized back with SYSV for x86了解图片)

使用下面的gnuplot脚本,我得到了这张图片:

mem-gp.png

首先(最上面)的情节显示&#34;文件偏移&#34;来自objdump的部分(从0x0开始);中间的情节显示&#34; VMA&#34; ({1}}和底部图表中的部分(虚拟内存地址)显示objdump的布局 - 这两个部分均来自/proc/pid/maps;所有三个图都显示相同的范围。

比较最上层和中间的情节,似乎这些部分的翻译更少,而且#34;&#34;从可执行文件到VMA地址(除了结尾);这样整个可执行文件(不仅仅是.text部分)从0x08048000开始。

但是比较中间和底部情节,似乎当程序在内存中运行时, .text被&#34;推回&#34;至0x08048000 - 不仅如此,它现在看起来更大了!

我到目前为止唯一的解释是我在某处读到的内容(但丢失了链接):内存中的图像必须分配整数页(大小为4096字节),并从页面边界。整个页数解释了更大的尺寸 - 但是,鉴于所有这些都是虚拟地址,为什么需要&#34;捕捉&#34;它们到页面边界(也可能不是,也就是将虚拟地址映射到物理页面边界?)

那么 - 有人可以提供一个解释,以便为什么0x08048000/proc/pid/maps的不同虚拟地址区域中看到.text部分?


objdump gnuplot脚本:

mem.gp

2 个答案:

答案 0 :(得分:7)

简短的回答是可加载段根据类型为PT_LOAD的ELF程序头映射到内存中。

  

PT_LOAD - 数组元素指定可加载的段,如下所述   p_filesz和p_memsz。文件中的字节映射到   内存段的开头。如果段的内存大小   (p_memsz)大于文件大小(p_filesz),即“额外”字节   被定义为保持值0并跟随该段   初始化区域。文件大小可能不大于内存   尺寸。程序头表中的可加载段条目出现在   升序,在p_vaddr成员上排序。

例如,在我的CentOS 6.4上:

objdump -x `which tail`

Program Header:
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x0000e4d4 memsz 0x0000e4d4 flags r-x
    LOAD off    0x0000e4d4 vaddr 0x080574d4 paddr 0x080574d4 align 2**12
         filesz 0x000003b8 memsz 0x0000054c flags rw-

来自/ proc / pid / maps:

cat /proc/2671/maps | grep `which tail`
08048000-08057000 r-xp 00000000 fd:00 133669     /usr/bin/tail
08057000-08058000 rw-p 0000e000 fd:00 133669     /usr/bin/tail

你会注意到map和objdump对后续部分的加载地址有什么区别,但这与加载器会计部分占用的内存量以及对齐字段有关。第一个可加载段映射到0x08048000,大小为0x0000e4d4,因此您希望它从0x08048000变为0x080564d4,但对齐表示在2 ^ 12字节页上对齐。如果你做数学运算,你最终得到0x8057000,匹配/ proc / pid / maps。所以第二个段映射到0x8057000,大小为0x0000054c(结束于0x805754c),对齐为0x8058000,匹配/ proc / pid / maps。

答案 1 :(得分:4)

感谢@KerrekSB的评论,我重新阅读了Understanding ELF using readelf and objdump - Linux article,我想我现在已经得到了它(虽然对其他人来说确认它是否合适会很好)。

基本上,错误是来自08048000-08054000 r-xp 00000000 08:05 131469 /usr/bin/tail的{​​{1}}区域/proc/pid/maps部分开头;知道这个的缺失链接是.text报告的程序标题表(PHT)。以下是我对readelf所说的内容:

tail

我手动在“程序标题:”部分添加了$ readelf -l /usr/bin/tail Elf file type is EXEC (Executable file) Entry point 0x8049100 There are 9 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align [00] PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 [01] INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] [02] LOAD 0x000000 0x08048000 0x08048000 0x0b9e8 0x0b9e8 R E 0x1000 [03] LOAD 0x00bf10 0x08054f10 0x08054f10 0x00220 0x003f0 RW 0x1000 [04] DYNAMIC 0x00bf24 0x08054f24 0x08054f24 0x000c8 0x000c8 RW 0x4 [05] NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 [06] GNU_EH_FRAME 0x00b918 0x08053918 0x08053918 0x00024 0x00024 R 0x4 [07] GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 [08] GNU_RELRO 0x00bf10 0x08054f10 0x08054f10 0x000f0 0x000f0 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .ctors .dtors .jcr .dynamic .got 行编号;否则很难将其链接到下面的[0x]。这里还要注意:“ Segment有很多类型,... LOAD:段的内容是从可执行文件加载的。”Offset“表示内核应该开始读取文件内容的文件的偏移量。”FileSiz“告诉我们必须从文件中读取多少字节。Understanding ELF...)“

所以,Section to Segment mapping:告诉我们:

objdump

... 08049100 <.text>: 部分从.text开始。

然后,0x08049100告诉我们:

readelf

...将头文件/段[02]从偏移零处的可执行文件加载到[02] LOAD 0x000000 0x08048000 0x08048000 0x0b9e8 0x0b9e8 R E 0x1000 ;并且标记为0x08048000 - 读取并执行内存区域。

此外,R E告诉我们:

readelf

...意味着标题/段[02]包含许多部分 - 之间,还有02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame ;现在,这与.text高于objdump的{​​{1}}视图匹配。

最后,正在运行的程序的.text告诉我们:

0x08048000

...可执行文件的可执行文件(/proc/pid/maps)“部分”在08048000-08054000 r-xp 00000000 08:05 131469 /usr/bin/tail 加载 - 现在很容易看到这个“部分”,正如我所说的那样,被称为错误 - 它不是一个部分(根据r-xp命名法);但它实际上是一个“标题/段”,因为0x08048000看到它(特别是我们之前看到的标题/段[02])。

嗯,希望我做对了(希望有人可以确认我是否这样做了objdump