我目前正在使用一些 32 位代码替换方案,其中移动到另一个位置的代码读取变量和类指针。由于 x86_64 不支持绝对寻址,因此我无法在代码的新位置获取变量的正确地址。详细的问题是,由于 rip 相对寻址,指令指针地址与编译时不同。
那么有没有办法在 x86_64 中使用绝对寻址或另一种方法来获取变量的地址而不是指令指针相对?
类似的东西:leaq variable(%%rax), %%rbx
也会有所帮助。我只想不依赖指令指针。
我目前正在使用一些 32 位代码替换方案,其中移动到另一个位置的代码读取变量和类指针。由于 x86_64 不支持绝对寻址,因此我无法在代码的新位置获取变量的正确地址。详细的问题是,由于 rip 相对寻址,指令指针地址与编译时不同。
那么有没有办法在 x86_64 中使用绝对寻址或另一种方法来获取变量的地址而不是指令指针相对?
类似的东西:leaq variable(%%rax), %%rbx
也会有所帮助。我只想不依赖指令指针。
尝试使用 x86_64 的大代码模型。在 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”(不存在),具有一些副作用,例如标志更改等。
你问的是可行的,但不是很容易。
一种方法是补偿指令中的代码移动。您需要找到所有使用 RIP 相对寻址的指令(它们的ModRM
字节为 05h、0dh、15h、1dh、25h、2dh、35h 或 3dh)并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 反汇编例程。
前者可能需要VirtualAlloc()
在 Windows(或 Linux 上的某些等效项)上使用,以确保包含原始代码的修补副本的内存在该原始代码的 +/- 2GB 范围内。并且在特定地址的分配仍然可能失败。
后者不仅需要原始反汇编,还需要完整的指令解码和生成。
可能还有其他怪癖需要解决。
指令边界也可以通过设置寄存器中的TF
标志RFLAGS
来使CPUsingle-step
在每条指令执行结束时产生调试中断来找到指令边界。调试异常处理程序需要捕获这些并记录下一条指令的 RIP 值。我相信这可以Structured Exception Handling (SEH)
在 Windows 中使用(从未尝试过调试中断),不确定 Linux。为此,您必须执行所有代码,每条指令。
顺便说一句,在 64 位模式中有绝对寻址,例如,参见 MOV 到/从累加器指令,操作码从 0A0h 到 0A3h。