C 预处理器和“_asm _emit”指令

C preprocessor and "_asm _emit" directive

我正在尝试在 Visual Studio 2015 中与 _asm _emit 一起实现布尔运算,想在 x86 程序中插入一些 x64 操作码,我必须做很多 mov 到内存命令,所以我尝试制作某种宏,它获取地址并以小端方式发出:

#define EmitDword(x)\
{\
    _asm _emit (x & 0x000000FF) \
    _asm _emit ((x >> 8) & 0x000000FF) \
    _asm _emit ((x >> 16) & 0x000000FF) \
    _asm _emit ((x >> 24) & 0x000000FF) \
}

但是我得到一个错误inline assembler syntax error in 'first operand'; 我的想法是,将一个变量的地址传递给宏,这样它就可以直接发送到机器代码中,然后我可以这样做:

 #define EmitDword(x)\
 {\
        _asm _emit (x & 0x000000FF) \
        _asm _emit ((x >> 8) & 0x000000FF) \
        _asm _emit ((x >> 16) & 0x000000FF) \
        _asm _emit ((x >> 24) & 0x000000FF) \
 }

/* mov qword ptr [addr],reg  */
#define X64_MovToMem(addr,reg)\
{\
  _asm _emit 0x48\
  _asm _emit 0x89\ 
  _asm _emit reg\ 
  _asm _emit 0x25\
  EmitDword(addr)\ 
}

#define _rax 4
void test()
{
    DWORD64 someData;
    X64_MovToMem(&someData,_rax);
}

有什么方法可以使用预处理器内联汇编来实现发出非常量值吗?

抱歉,您的尝试毫无意义。

  • "insert some x64 opcodes in x86 program" - 如果它是一个 x86 程序,它不会 运行 x64 操作码。
  • "_asm _emit (x & 0x000000FF)" - 你读过 emit 的 docs 了吗?您只能发出字节,不能发出 C 代码。
  • "emit emit emit emit" - 为什么您认为每次写入 1 个字节的 4 次(此代码无论如何都不会这样做)会比写入 4 个字节的 1 次更快?
  • "Is there any way to achieve the emitting of non-constant values" - 如果你正在使用 emit,那么你需要知道你试图写入的值当前存储在哪个寄存器中(这几乎是不可能完成的,因为编译器可能使用不同的每次代码更改时都会注册,并且该值甚至可能不在寄存器中)。

我试图查看你是如何写这篇文章来解决你原来的问题的。然而:

我意识到人们倾向于认为 "asm is faster." 但请记住,C 代码会被翻译成汇编程序。编译器已经在生成 asm 代码,几乎肯定比通过 emit 拼凑的代码要高效得多。

如果使用 64 位指令会产生更好的代码(这当然是可能的),您应该构建一个 64 位可执行文件。

如果您确信您可以创建比 C 编译器更高效的 asm 代码,请考虑创建一个完整的 asm 例程,然后从您的 C 代码中调用它。请注意,您将无法 link x64 汇编器进入您的 x86 程序。

您无需在 32 位进程中乱用 64 位代码来访问 64 位进程环境块。您可以使用32位代码获取其地址,它位于32位地址space内。如果你需要访问在 32 位地址 space 之外分配的内存,你只需要使用 64 位代码,我认为 Windows 永远不会在 32 位中这样做过程。

如果您确实需要在 32 位可执行文件中使用 64 位函数,那么有比使用 _asm _emit 更好的方法。要做的第一件事是用普通汇编语言编写整个 64 位函数,然后 assemble 使用普通的外部 assembler。例如,这里有一个从 64 位指针读取 MASM 语法的函数:

_TEXT   SEGMENT
__read64ptr:
    mov rax, [rsp + 8]
    mov eax, [rax]
    mov edx, [rax + 4]
    retf
_TEXT   ENDS
    END

这个简单的函数将一个 64 位指针作为堆栈上的参数。位于指向地址的 64 位值被放入 EAX 和 EDX。此函数旨在使用 32 位远调用指令调用。

注意return值占用两个32位栈槽,一个用于return地址的32位偏移量,另一个用于选择器。尽管 RETF 指令是在 64 位模式下执行的,但它默认使用 32 位堆栈大小(与 64 位 near RET 指令不同)并且将与 32 位 far return 一起正常工作地址保存在堆栈上。

遗憾的是,我们不能直接使用 Visual Studio 提供的工具来使用这个程序集文件。 64 位版本的 MASM 只创建 64 位目标文件,linker 不会让我们混合使用 32 位和 64 位目标文件。应该可以使用 NASM 和 link 将 assemble 64 位代码转换为 32 位对象,以及 Microsoft 的 linker,但是可以间接使用代码只有微软的工具。

为此,assemble 文件并手动将机器代码复制到位于 .text 部分的 C 数组中:

#pragma code_seg(push, ".text")
#pragma code_seg(pop)
char const __declspec(allocate(".text")) _read64ptr[] = {
    0x48, 0x8b, 0x44, 0x24, 0x08,   /* mov rax, [rsp + 8] */
    0x8b, 0x00,                     /* mov eax. [rax] */
    0x8b, 0x50, 0x04,               /* mov edx, [rax + 4] */
    0xcb                            /* retf */
};

要调用它,您只需使用如下代码:

struct {
    void const *offset;
    unsigned short selector;
} const _read64ptr_ind = { _read64ptr, 0x33 };

unsigned long long
read64ptr(unsigned long long address) {
    unsigned long long value;
    _asm {
        push    DWORD PTR [address + 4]
        push    DWORD PTR [address]
        call    FWORD PTR [_read64ptr_ind]
        add     esp, 8
        mov     DWORD PTR [value], eax
        mov     DWORD PTR [value + 4], edx
    }
    return value;
}

_read64ptr_ind 的间接寻址是必要的,因为在 Microsoft 内联汇编中无法编写 call 33h:_read64ptr。另请注意,64 位代码选择器 0x33 在此示例中是硬编码的,希望它不会更改。

下面是一个使用上述代码从64位TEB中读取64位PEB地址的例子(即使两者都位于32位地址space):

unsigned long long
readgsqword(unsigned long off) {
    unsigned long long value;
    _asm {
        mov edx, [off]
        mov eax, gs:[edx]
        mov edx, gs:[edx + 4]
        mov DWORD PTR [value], eax
        mov DWORD PTR [value + 4], edx
    }
    return value;
}

int
main() {
    printf("32-bit TEB address %08lx\n",
           __readfsdword(offsetof(NT_TIB, Self)));
    printf("32-bit PEB address %08lx\n", __readfsdword(0x30));
    unsigned long long teb64 = readgsqword(offsetof(NT_TIB64, Self));
    printf("64-bit TEB address %016llx\n", teb64);
    printf("64-bit PEB address %016llx\n", readgsqword(0x60));
    printf("64-bit PEB address %016llx\n", read64ptr(teb64 + 0x60));
}

运行 它在我的电脑上生成以下输出:

32-bit TEB address 7efdd000
32-bit PEB address 7efde000
64-bit TEB address 000000007efdb000
64-bit PEB address 000000007efdf000
64-bit PEB address 000000007efdf000

如您所见,可以使用 32 位指针访问所有结构,而无需任何 64 位代码。特别是,该示例显示了如何仅使用 32 位代码获取指向 64 位 PEB 的 32 位指针。

最后一点,不能保证 Windows 会在 32 位进程中正确处理 64 位代码 运行。如果在执行 64 位代码时随时发生中断,则进程可能最终崩溃。