- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在我的 Linux 程序中,我需要一个函数,它接受地址 addr
并检查位于 addr
的 callq
指令是否正在调用从共享库加载的特定函数func
。我的意思是,我需要检查 addr
处是否有类似 callq func@PLT
的内容。
那么,在Linux上,如何从callq func@PLT
指令到达函数func
的真实地址?
最佳答案
只有在动态链接器解析实际加载地址之后,您才能在运行时找到这一点。
警告:接下来是更深层次的魔法......
为了说明正在发生的情况,请使用调试器:
#include <stdio.h>
int main(int argc, char **argv) { printf("Hello, World!\n"); return 0; }
编译它( gcc -O8 ...
)。 objdump -d
在二进制显示上(对于普通字符串,printf()
的优化被替换为 puts()
,尽管...):
Disassembly of section .init:[ ... ]Disassembly of section .plt:0000000000400408 <__libc_start_main@plt-0x10>: 400408: ff 35 a2 04 10 00 pushq 1049762(%rip) # 5008b0 <_GLOBAL_OFFSET_TABLE_+0x8>> 40040e: ff 25 a4 04 10 00 jmpq *1049764(%rip) # 5008b8 <_GLOBAL_OFFSET_TABLE_+0x10>[ ... ]0000000000400428 <puts@plt>: 400428: ff 25 9a 04 10 00 jmpq *1049754(%rip) # 5008c8 <_GLOBAL_OFFSET_TABLE_+0x20> 40042e: 68 01 00 00 00 pushq $0x1 400433: e9 d0 ff ff ff jmpq 400408 <_init+0x18>[ ... ]0000000000400500 <main>: 400500: 48 83 ec 08 sub $0x8,%rsp 400504: bf 0c 06 40 00 mov $0x40060c,%edi 400509: e8 1a ff ff ff callq 400428 <puts@plt> 40050e: 31 c0 xor %eax,%eax 400510: 48 83 c4 08 add $0x8,%rsp 400514: c3 retq
现在将其加载到 gdb
。然后:
$ gdb ./tccGNU gdb Red Hat Linux (6.3.0.0-0.30.1rh)[ ... ](gdb) x/3i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>0x40042e: pushq $0x10x400433: jmpq 0x400408(gdb) x/gx 0x5008c80x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
请注意,该值指向紧接第一个 jmpq
之后的指令。 ;这意味着 puts@plt
第一次调用时,槽将简单地“落入”:
(gdb) x/3i 0x4004080x400408: pushq 1049762(%rip) # 0x5008b0 <_GLOBAL_OFFSET_TABLE_+8>0x40040e: jmpq *1049764(%rip) # 0x5008b8 <_GLOBAL_OFFSET_TABLE_+16>0x400414: nop(gdb) x/gx 0x5008b00x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000000000000000(gdb) x/gx 0x5008b80x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000000000000000
函数地址和参数尚未初始化。
这是程序加载后、执行前的状态。现在开始执行它:
(gdb) break mainBreakpoint 1 at 0x400500(gdb) runStarting program: tcc(no debugging symbols found)(no debugging symbols found)Breakpoint 1, 0x0000000000400500 in main ()(gdb) x/i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>(gdb) x/gx 0x5008c80x5008c8 <_GLOBAL_OFFSET_TABLE_+32>: 0x000000000040042e
所以这还没有改变 - 但目标(GOT
初始化的libc
内容)现在不同了:
(gdb) x/gx 0x5008b00x5008b0 <_GLOBAL_OFFSET_TABLE_+8>: 0x0000002a9566b9a8(gdb) x/gx 0x5008b80x5008b8 <_GLOBAL_OFFSET_TABLE_+16>: 0x0000002a955609f0(gdb) disas 0x0000002a955609f0Dump of assembler code for function _dl_runtime_resolve:0x0000002a955609f0 <_dl_runtime_resolve+0>: sub $0x38,%rsp[ ... ]
即在程序加载时,动态链接器将首先解析“init
”部分。它取代了 GOT
带有重定向到动态链接代码的指针的引用。
因此,当第一次通过 .plt
调用外部二进制函数时引用,它会再次跳转到链接器。让它这样做,然后检查程序 - 状态再次改变:
(gdb) break *0x0000000000400514Breakpoint 2 at 0x400514(gdb) continueContinuing.Hello, World!Breakpoint 2, 0x0000000000400514 in main ()(gdb) x/i 0x4004280x400428: jmpq *1049754(%rip) # 0x5008c8 <_GLOBAL_OFFSET_TABLE_+32>(gdb) x/gx 0x5008c80x5008c8 : 0x0000002a956c8870(gdb) disas 0x0000002a956c8870Dump of assembler code for function puts:0x0000002a956c8870 <puts+0>: mov %rbx,0xffffffffffffffe0(%rsp)[ ... ]
因此,您的重定向直接进入 libc
现在-PLT
引用puts()
终于解决了。
链接器的指令在哪里插入实际的函数加载地址(我们已经看到它对 _dl_runtime_resolve
所做的事情来自 ELF 二进制文件中的特殊部分:
$ readelf -a tcc[ ... ]Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align[ ... ] INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2][ ... ]Dynamic section at offset 0x700 contains 21 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6][ ... ]Relocation section '.rela.plt' at offset 0x3c0 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend0000005008c0 000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 00000005008c8 000200000007 R_X86_64_JUMP_SLO 0000000000000000 puts + 0
ELF 不仅仅是上面这些,但这三个部分告诉内核的二进制格式处理程序“这个 ELF 二进制文件有一个需要加载的解释器”(这是动态链接器)/首先初始化,它需要 libc.so.6
,并且抵消 0x5008c0
和0x5008c8
程序可写数据部分中的 必须替换为 __libc_start_main
的加载地址和puts
,分别是实际执行动态链接步骤时的情况。
从 ELF 的角度来看,这到底是如何发生的,取决于解释器(又名动态链接器实现)的细节。
关于linux - 如何从 `func`获取 `callq func@PLT`的实际地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15066725/
我知道 callq 是什么指令确实;在 this answer 中有描述.该指令应有一个参数,即被调用函数的地址。 但是,在反汇编目标文件时,我看到以下行显示 callq具有两个值而不是一个参数的指令
我有一些由工具生成的 x86_64 架构的 gnu 汇编代码,有以下说明: movq %rsp, %rbp leaq str(%rip), %rdi callq puts movl $0, %eax
我正在 gdb session 中分析事后崩溃。我正在查看函数的反汇编输出,我看到了: => 0x00007f8d354aed52 : callq *(%rax) => 表示这是崩溃时调用的指
我有一些模板繁重的 C++ 代码,我想确保编译器尽可能优化,因为它在编译时具有大量信息。为了评估它的性能,我决定看一下它生成的目标文件的反汇编。下面是我从 objdump -dC 中得到的片段: 00
我有一些模板繁重的 C++ 代码,我想确保编译器尽可能优化,因为它在编译时拥有大量信息。为了评估它的性能,我决定看看它生成的目标文件的反汇编。下面是我从 objdump -dC 得到的片段: 0000
我正在 CentOS 7.9 上使用 crash 实用程序调试内核 oops (vmcore),我有一个函数 foo,它调用回调,但是当我反汇编 foo 时,我没有看到引用回调的 callq 指令,我
在我的 Linux 程序中,我需要一个函数,它接受地址 addr 并检查位于 addr 的 callq 指令是否正在调用从共享库加载的特定函数func。我的意思是,我需要检查 addr 处是否有类似
我是一名优秀的程序员,十分优秀!