我试图了解程序的可执行程序集在程序加载/运行时的确切位置。 我发现有两个资源在谈论这个问题,但他们有些难以阅读:
所以,这是一个简单的例子。 我感兴趣的是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
的' main()
'在这里,有符号没有被剥离。 无论如何,可执行部分的开始是根据这个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
(这个地址很明显是用x86的 0x08048000
标准化的 ;同样参见内存中的程序剖析:Gustavo Duarte为一个图像)
使用下面的gnuplot
脚本,我到达了这个图像:
首先(最上面的)图显示objdump
(从0x0
开始)部分的“文件偏移”; 中间的图表显示来自objdump
的部分的“VMA”(虚拟内存地址),底部的图表显示来自/proc/pid/maps
布局 – 两者都从0x08048000
开始; 所有三个地块显示相同的范围。
比较最上面的和中间的图,似乎这些部分从可执行文件到VMA地址(除了结尾)“按原样”被更多地翻译; 这样整个可执行文件(不只是.text段)从0x08048000
开始。
但比较中间和底部的情节,似乎当一个程序在内存中运行时, 只有 .text被“推回”到0x08048000
– 不仅如此,它现在看起来更大!
我到目前为止的唯一解释是我在某处读到的东西(但是丢失了链接):内存中的图像必须分配一定数量的页面(大小例如4096字节),并从页面边界开始。 整个页面数量解释了更大的大小 – 但是,由于所有这些都是虚拟地址,为什么需要将它们“捕捉”到页面边界(也可能不是,将虚拟地址映射到物理页面边界?)
所以 – 有人可以提供一个解释,以便为什么/proc/pid/maps
在objdump
的不同虚拟地址区域中看到.text节?
mem.gp
gnuplot脚本:
#!/usr/bin/env gnuplot set term wxt size 800,500 exec = "/usr/bin/tail" ; # cannot do - apparently gnuplot waits for children to exit, so locks here: #runcmd = "bash -c '" . exec . " -f & echo $!'" #print runcmd #pid = system(runcmd) ; #print runcmd, "pid", pid # run tail -f & echo $! in another shell; then enter pid here: pid = 28803 # $1 Idx $2 Name $3 Size $4 VMA $5 LMA $6 File off cmdvma = "<objdump -h ".exec." | awk '$1 ~ \"^[0-9]+$\" && $2 !~ \".gnu_debuglink\" {print $1, $2, \"0X\"$3, \"0X\"$4;}'" ; cmdfo = "<objdump -h ".exec." | awk '$1 ~ \"^[0-9]+$\" && $2 !~ \".gnu_debuglink\" {print $1, $2, \"0X\"$3, \"0X\"$6;}'" ; cmdmaps = "<cat /proc/".pid."/maps | awk '{split($1,a,\"-\");b1=strtonum(\"0x\"a[1]);b2=strtonum(\"0x\"a[2]);printf(\"%d \\\"%s\\\" 0x%08X 0x%08X\\n\", NR,$6,b2-b1,b1);}'" print cmdvma print cmdfo print cmdmaps set format x "0x%08X" # "%016X"; set xtics rotate by -45 font ",7"; unset ytics unset colorbox set cbrange [0:25] set yrange [0.5:1.5] set macros set multiplot layout 3,1 columnsfirst # 0x08056000-0x08048000 = 0xe000 set xrange [0:0xe000] set tmargin at screen 1 set bmargin at screen 0.667+0.1 plot \ cmdfo using 4:(1+$0*0.01):4:($4+$3):0 with xerrorbars lc palette t "File off", \ cmdfo using 4:(1):2 with labels font ",6" left rotate by -45 t "" set xrange [0x08048000:0x08056000] set tmargin at screen 0.667 set bmargin at screen 0.333+0.1 plot \ cmdvma using 4:(1+$0*0.01):4:($4+$3):0 with xerrorbars lc palette t "VMA", \ cmdvma using 4:(1):2 with labels font ",6" left rotate by -45 t "" set tmargin at screen 0.333 set bmargin at screen 0+0.1 plot \ cmdmaps using 4:(1+$0*0.01):4:($4+$3):0 with xerrorbars lc palette t "/proc/pid/maps" , \ cmdmaps using 4:(1):2 with labels font ",6" left rotate by -45 t "" unset multiplot #system("killall -9 " . pid) ;
简单的答案是可加载段根据类型为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 rx 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
您会注意到映射和objdump为后续部分的加载地址所说的区别,但是这与加载程序记录部分占用的内存以及对齐字段有关。 第一个可加载的段映射在0x08048000处,大小为0x0000e4d4,所以你希望它从0x08048000到0x080564d4,但是对齐说在2 ^ 12字节的页面上对齐。 如果你做数学,你最终在0x8057000匹配/ proc / pid / maps。 所以第二个段映射为0x8057000,大小为0x0000054c(结尾为0x805754c),与0x8058000匹配,匹配/ proc / pid / maps。
感谢来自@KerrekSB的评论,我重读了使用readelf和objdump – Linux文章理解ELF ,我想我现在有点儿了(虽然对于其他人来说确实是好的)。
基本上,错误是从/proc/pid/maps
的区域08048000-08054000 r-xp 00000000 08:05 131469 /usr/bin/tail
不以.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 RE 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 RE 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]
行编号; 否则很难将其链接到Section to Segment mapping:
在下面。 这里还要注意:“ 段有很多类型,… LOAD:段的内容是从可执行文件中加载的,”Offset“表示内核开始读取文件内容的文件的偏移量,”FileSiz“告诉我们必须从文件中读取许多字节 ( 了解ELF … )“
所以, objdump
告诉我们:
08049100 <.text>:
… .text
部分从0x08049100
开始。
然后, readelf
告诉我们:
[02] LOAD 0x000000 0x08048000 0x08048000 0x0b9e8 0x0b9e8 RE 0x1000
…该头/段[02]从偏移零的可执行文件加载到0x08048000
; 而且这被标记为重新读取并执行内存区域。
此外, readelf
告诉我们:
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
…这意味着标题/段[02]包含许多部分 – 其中 ,也是.text
; 这现在匹配objdump
视图.text
开始高于0x08048000
。
最后,运行程序的/proc/pid/maps
告诉我们:
08048000-08054000 r-xp 00000000 08:05 131469 /usr/bin/tail
…可执行文件的可执行文件( r-xp
)“section”在0x08048000
加载 – 现在很容易看到这个“section”被我们称为错误 – 它不是一个section (根据objdump
命名法); 但是它实际上是一个“header / segment”,就像readelf
看到的那样(特别是我们前面看到的header / segment [02])。
那么,希望我有这个权利(并希望有人可以确认,如果我这样做或不:)
)