计算函数在内存中的偏移量
Computing offset of a function in memory
我正在阅读 uprobe tracer 的文档,其中有一条说明如何计算函数在内存中的偏移量。我在这里引用它。
Following example shows how to dump the instruction pointer and %ax
register at the probed text address. Probe zfree function in /bin/zsh:
# cd /sys/kernel/debug/tracing/
# cat /proc/`pgrep zsh`/maps | grep /bin/zsh | grep r-xp
00400000-0048a000 r-xp 00000000 08:03 130904 /bin/zsh
# objdump -T /bin/zsh | grep -w zfree
0000000000446420 g DF .text 0000000000000012 Base zfree
0x46420 is the offset of zfree in object /bin/zsh that is loaded at
0x00400000.
我不知道为什么,但他们将输出 0x446420 减去 0x400000 得到 0x46420。这对我来说是一个错误。为什么是 0x400000?
我已经尝试在我的 Fedora 23 和 4.5.6-200 内核上做同样的事情。
首先我关闭了内存地址随机化
echo 0 > /proc/sys/kernel/randomize_va_space
然后我想出了二进制文件在内存中的位置
$ cat /proc/`pgrep zsh`/maps | grep /bin/zsh | grep r-xp
555555554000-55555560f000 r-xp 00000000 fd:00 2387155 /usr/bin/zsh
取了偏移量
marko@fedora:~ $ objdump -T /bin/zsh | grep -w zfree
000000000005dc90 g DF .text 0000000000000012 Base zfree
并通过 gdb 找出 zfree 的位置
$ gdb -p 21067 --batch -ex 'p zfree'
= {<text variable, no debug info>} 0x5555555b1c90 <zfree>
marko@fedora:~ $ python
Python 2.7.11 (default, Mar 31 2016, 20:46:51)
[GCC 5.3.1 20151207 (Red Hat 5.3.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x5555555b1c90-0x555555554000)
'0x5dc90'
你看,我得到了与 objdump 相同的结果,没有减去任何东西。
但后来我在另一台装有 SLES 的机器上尝试了同样的操作,结果与 uprobe 文档中的相同。
为什么会有这样的差异?那么我该如何计算正确的偏移量呢?
据我所知,差异可能仅由检查二进制文件的构建方式引起。更准确地说——ELF 是否有固定的加载地址。让我们做简单的实验。我们有简单的测试代码:
int main(void) { return 0; }
然后,通过两种方式构建它:
$ gcc -o t1 t.c # create image with fixed load address
$ gcc -o t2 t.c -pie # create load-base independent image
现在,让我们检查这两个图像的加载基地址:
$ readelf -l --wide t1 | grep LOAD
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x00067c 0x00067c R E 0x200000
LOAD 0x000680 0x0000000000600680 0x0000000000600680 0x000228 0x000230 RW 0x200000
$ readelf -l --wide t2 | grep LOAD
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0008cc 0x0008cc R E 0x200000
LOAD 0x0008d0 0x00000000002008d0 0x00000000002008d0 0x000250 0x000258 RW 0x2000
这里可以看到第一张图片需要固定加载地址-0x400000
,第二张根本没有地址要求。
现在我们可以比较 objdump
告诉 main
的地址:
$ objdump -t t1 | grep ' main'
00000000004004b6 g F .text 000000000000000b main
$ objdump -t t2 | grep ' main'
0000000000000710 g F .text 000000000000000b main
我们看到,address是一个完整的虚拟地址,main
的第一个字节如果在address加载图片,会占用这个虚拟地址,存放在程序头中。当然,第二张图片永远不会加载到 0x0
,而是加载到另一个随机选择的位置,这将偏移实际函数位置。
我正在阅读 uprobe tracer 的文档,其中有一条说明如何计算函数在内存中的偏移量。我在这里引用它。
Following example shows how to dump the instruction pointer and %ax register at the probed text address. Probe zfree function in /bin/zsh:
# cd /sys/kernel/debug/tracing/ # cat /proc/`pgrep zsh`/maps | grep /bin/zsh | grep r-xp 00400000-0048a000 r-xp 00000000 08:03 130904 /bin/zsh # objdump -T /bin/zsh | grep -w zfree 0000000000446420 g DF .text 0000000000000012 Base zfree
0x46420 is the offset of zfree in object /bin/zsh that is loaded at 0x00400000.
我不知道为什么,但他们将输出 0x446420 减去 0x400000 得到 0x46420。这对我来说是一个错误。为什么是 0x400000?
我已经尝试在我的 Fedora 23 和 4.5.6-200 内核上做同样的事情。
首先我关闭了内存地址随机化
echo 0 > /proc/sys/kernel/randomize_va_space
然后我想出了二进制文件在内存中的位置
$ cat /proc/`pgrep zsh`/maps | grep /bin/zsh | grep r-xp
555555554000-55555560f000 r-xp 00000000 fd:00 2387155 /usr/bin/zsh
取了偏移量
marko@fedora:~ $ objdump -T /bin/zsh | grep -w zfree
000000000005dc90 g DF .text 0000000000000012 Base zfree
并通过 gdb 找出 zfree 的位置
$ gdb -p 21067 --batch -ex 'p zfree'
= {<text variable, no debug info>} 0x5555555b1c90 <zfree>
marko@fedora:~ $ python
Python 2.7.11 (default, Mar 31 2016, 20:46:51)
[GCC 5.3.1 20151207 (Red Hat 5.3.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0x5555555b1c90-0x555555554000)
'0x5dc90'
你看,我得到了与 objdump 相同的结果,没有减去任何东西。
但后来我在另一台装有 SLES 的机器上尝试了同样的操作,结果与 uprobe 文档中的相同。
为什么会有这样的差异?那么我该如何计算正确的偏移量呢?
据我所知,差异可能仅由检查二进制文件的构建方式引起。更准确地说——ELF 是否有固定的加载地址。让我们做简单的实验。我们有简单的测试代码:
int main(void) { return 0; }
然后,通过两种方式构建它:
$ gcc -o t1 t.c # create image with fixed load address
$ gcc -o t2 t.c -pie # create load-base independent image
现在,让我们检查这两个图像的加载基地址:
$ readelf -l --wide t1 | grep LOAD
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x00067c 0x00067c R E 0x200000
LOAD 0x000680 0x0000000000600680 0x0000000000600680 0x000228 0x000230 RW 0x200000
$ readelf -l --wide t2 | grep LOAD
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0008cc 0x0008cc R E 0x200000
LOAD 0x0008d0 0x00000000002008d0 0x00000000002008d0 0x000250 0x000258 RW 0x2000
这里可以看到第一张图片需要固定加载地址-0x400000
,第二张根本没有地址要求。
现在我们可以比较 objdump
告诉 main
的地址:
$ objdump -t t1 | grep ' main'
00000000004004b6 g F .text 000000000000000b main
$ objdump -t t2 | grep ' main'
0000000000000710 g F .text 000000000000000b main
我们看到,address是一个完整的虚拟地址,main
的第一个字节如果在address加载图片,会占用这个虚拟地址,存放在程序头中。当然,第二张图片永远不会加载到 0x0
,而是加载到另一个随机选择的位置,这将偏移实际函数位置。