当程序加载/运行时,我试图了解程序的可执行程序集的确切位置。我发现有两个资源在讨论这个问题,但它们有点难以阅读:
所以,这是一个简短的例子;我感兴趣的是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
脚本,我得到了这张图片:
首先(最上面)的情节显示&#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
答案 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
)