sda*_*aau 3 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
...
Run Code Online (Sandbox Code Playgroud)
我假设我在这里看到了tail's' main()的调用,没有删除符号.无论如何,可执行部分的开头是,根据这个,0x08049100; 我对它最终到底的地方感兴趣.
然后,我tail在后台运行,得到它的pid:
$ /usr/bin/tail -f & echo $!
28803
Run Code Online (Sandbox Code Playgroud)
......我检查一下/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]
Run Code Online (Sandbox Code Playgroud)
现在我有tail三次 - 但是可执行段r-xp(这是.text?)显然是0x08048000(一个地址显然是用SYSV标准化为x86 ;另见内存程序剖析:Gustavo Duarte的图像)
使用gnuplot下面的脚本,我到达了这张图片:

第一个(最上面)的图显示了objdump(从...开始0x0)部分的"文件偏移" ; 中间图显示"VMA"(虚拟存储器地址)的部分从objdump底部和底部的图表显示布局/proc/pid/maps- 从这两个开始0x08048000; 所有三个图都显示相同的范围.
比较最上层和中间的情节,似乎这些部分"从原样"可执行文件到VMA地址(除了结尾)之外更多地翻译; 这样整个可执行文件(不仅仅是.text部分)从中开始0x08048000.
但是比较中间和底部的情节,似乎当一个程序在内存中运行时,只有 .text被"推回" 0x08048000- 而且不仅如此,它现在看起来更大了!
我到目前为止唯一的解释是,我在某处读到了(但丢失了链接):内存中的图像必须分配整数页(大小为4096字节),并从页边界开始.整个页面数量说明了更大的尺寸 - 但是,鉴于所有这些都是虚拟地址,为什么需要将它们"捕捉"到页面边界(也可能不会将虚拟地址映射到物理地址)页面边界?)
那么 - 有人可以提供一个解释,以便为什么/proc/pid/maps在不同的虚拟地址区域中看到.text部分objdump?
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) ;
Run Code Online (Sandbox Code Playgroud)
简短的回答是,可加载的段基于类型为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-
Run Code Online (Sandbox Code Playgroud)
从/ 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
Run Code Online (Sandbox Code Playgroud)
你会注意到map和objdump对后续部分的加载地址所说的有区别,但这与加载器计算部分占用的内存量以及对齐字段有关.第一个可加载段映射到0x08048000,大小为0x0000e4d4,因此您希望它从0x08048000变为0x080564d4,但对齐表示在2 ^ 12字节页上对齐.如果你做数学运算,你最终得到0x8057000,匹配/ proc/pid/maps.所以第二个段映射到0x8057000并且大小为0x0000054c(结束于0x805754c),它与0x8058000对齐,匹配/ proc/pid/maps.