x86_64中运行时代码替换的绝对寻址

时间:2011-11-02 10:39:16

标签: gcc assembly 64-bit x86-64 addressing

我目前正在使用32位代码替换方案,其中代码移动到另一个位置,读取变量和类指针。由于x86_64不支持绝对寻址,因此无法在代码的新位置获取变量的正确地址。问题在于,由于rip相对寻址,指令指针地址与编译时不同。

那么有没有办法在x86_64中使用绝对寻址或以其他方式获取变量的地址而非指令指针相对?

类似于:leaq variable(%%rax), %%rbx也会有所帮助。我只想依赖指令指针。

2 个答案:

答案 0 :(得分:7)

尝试使用x86_64的 large 代码模型。在gcc中,可以使用 -mcmodel = large 选择。编译器将对代码和数据使用64位绝对寻址。

您还可以添加 -fno-pic 以禁止生成与位置无关的代码。

编辑:我使用 -mcmodel = large 构建了一个小型测试应用,生成的二进制文件包含

等序列
400b81:       48 b9 f0 30 60 00 00    movabs $0x6030f0,%rcx
400b88:       00 00 00 
400b8b:       49 b9 d0 09 40 00 00    movabs $0x4009d0,%r9
400b92:       00 00 00 
400b95:       48 8b 39                mov    (%rcx),%rdi
400b98:       41 ff d1                callq  *%r9

是一个绝对64位立即(在这种情况下是地址)的加载,后跟间接调用或间接加载。指令序列

moveabs $variable, %rbx
addq %rax, %rbx

相当于“leaq offset64bit(%rax),%rbx”(不存在),有一些副作用,如标志更改等。

答案 1 :(得分:2)

你所询问的是可行的,但不是很容易。

一种方法是在其指令中补偿代码移动。您需要找到使用RIP相对寻址的所有指令(它们具有05h,0dh,15h,1dh,25h,2dh,35h或3dh的ModRM字节并调整它们的disp32字段移动量(因此移动在虚拟地址空间中限制为+/- 2GB,如果64位地址空间大于4GB,则可能无法保证)。

您也可以将这些指令替换为等效指令,最有可能将每个原始指令替换为多个指令,例如:

; These replace the original instruction and occupy exactly as many bytes as the original instruction:
  JMP Equivalent1
  NOP
  NOP
Equivalent1End:

; This is the code equivalent to the original instruction:
Equivalent1:
  Equivalent subinstruction 1
  Equivalent subinstruction 2
  ...
  JMP Equivalent1End

这两种方法至少需要一些基本的x86反汇编程序。

前者可能需要在Windows上使用VirtualAlloc()(或Linux上的某些等效项)以确保包含原始代码的修补副本的内存在原始代码的+/- 2GB范围内。并且在特定地址的分配仍然可能失败。

后者不仅需要原始的反汇编,还需要完整的指令解码和生成。

可能还有其他的怪癖可以解决。

也可以通过在TF寄存器中设置RFLAGS标志来找到指令边界,以使CPU在每条指令执行结束时生成single-step调试中断。调试异常处理程序需要捕获这些并记录下一条指令的RIP值。我相信这可以在Windows中使用Structured Exception Handling (SEH)完成(从未尝试使用调试中断),不确定Linux。为此,您必须执行所有代码,每条指令。

顺便说一下,在64位模式下有绝对寻址,例如,参见累加器指令的MOV,操作码从0A0h到0A3h。