选项 1 实际上似乎工作正常。考虑以下程序:
#include <stdio.h>
void f(int *p) {
__asm mov eax, p
__asm mov ebx, [eax]
// break here
}
void main()
{
int i = 0x12345678;
f(&i);
}
使用 Visual Studio 2008 SP1,一个单文件 C++ 程序和调试版本,当我进入 f() 的末尾时,我在寄存器窗口中得到以下内容:
EAX = 004DF960
EBX = 12345678
ECX = 00000000
EDX = 00000001
ESI = 00000000
EDI = 004DF884
EIP = 003013C3
ESP = 004DF7B8
EBP = 004DF884
EFL = 00000202
查看 EAX、EBX 和 ESP 中的值,这看起来是一个很好的证据,表明您实际上在 EAX 中拥有您想要的指针。EAX 中的地址仅比 ESP 中的地址高一点点,这表明它比堆栈高一帧。加载到 EBX 中的取消引用值表明我们得到了正确的地址。
加载全局地址略有不同。以下示例使用 LEA 指令来完成任务。
#include <stdio.h>
int a[] = { 0x1234, 0x4567 };
void main()
{
// __asm mov eax, a ; interpreted as eax <- a[0]
__asm lea eax, a ; interpreted as eax <- &a[0]
__asm mov ebx, [eax]
__asm mov ecx, [eax+4]
// break here
}
走到 main() 的末尾会得到以下寄存器值。EAX 获取数组中第一个元素的地址,而 EBX 和 ECX 获取其成员的值。
EAX = 00157038
EBX = 00001234
ECX = 00004567
EDX = 00000001
ESI = 00000000
EDI = 0047F800
EIP = 001513C9
ESP = 0047F734
EBP = 0047F800
EFL = 00000202
魔法不在 LEA 指令本身。相反,__asm 指令似乎根据使用的是 MOV 指令还是 LEA 指令来不同地处理 C/C++ 标识符。这是取消注释 MOV 指令时同一程序的 ASM 转储。请注意 MOV 指令如何获取 a[] 的内容作为其参数(DWORD PTR),而 LEA 指令获取其偏移量。
; ...
PUBLIC ?a@@3PAHA ; a
_DATA SEGMENT
?a@@3PAHA DD 01234H ; a
DD 04567H
_DATA ENDS
; ...
mov eax, DWORD PTR ?a@@3PAHA
lea eax, OFFSET ?a@@3PAHA
mov ebx, DWORD PTR [eax]
mov ecx, DWORD PTR [eax+4]
; ...