C预处理器和" _asm _emit"指示

时间:2016-10-22 03:51:49

标签: c visual-c++ assembly c-preprocessor inline-assembly

我试图在Visual Studio 2015中与_asm _emit一起尝试实现布尔算法,想在x86程序中插入一些x64操作码,我必须对内存做很多mov命令,所以我尝试制作某种宏,它接收地址并以little-endian发出,所以:

#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);
}

有没有办法使用预处理器内联汇编来实现非常量值的发射?

2 个答案:

答案 0 :(得分:3)

您无需在32位进程中使用64位代码来访问64位进程环境块。您可以使用32位代码获取其地址,它位于32位地址空间内。如果需要访问在32位地址空间之外分配的内存,则只需要使用64位代码,我不认为Windows会在32位进程中执行此操作。

如果你确实需要在32位可执行文件中使用64位函数,那么使用_asm _emit比使用更好的方法。首先要做的是用普通的汇编程序编写整个64位函数,并用普通的外部汇编程序组装它。例如,这是一个从MASM语法中的64位指针读取的函数:

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

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

请注意,返回值占用两个32位堆栈槽,一个用于返回地址的32位偏移,另一个用于选择器。尽管RETF指令在64位模式下执行,但默认情况下它使用32位堆栈大小(与近似RET指令的64位不同),并且可以正确使用保存在32位远端返回地址。叠加。

不幸的是,我们不能直接使用Visual Studio提供的工具来使用此程序集文件。 64位版本的MASM仅创建64位目标文件,链接器不允许我们混合使用32位和64位目标文件。应该可以使用NASM将64位代码组装成32位对象并与Microsoft的链接器链接,但是可以仅使用Microsoft的工具间接使用代码。

为此,请汇编文件并手动将机器代码复制到.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位地址空间中):

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位代码的任何时候发生中断,则该过程可能最终崩溃。

答案 1 :(得分:1)

对不起,但你要做的事情毫无意义。

  • "在x86程序中插入一些x64操作码" - 如果是x86程序,则不会运行x64操作码。
  • " _asm _emit(x& 0x000000FF)" - 你读过docs的发射吗?您只能发出字节,而不是C代码。
  • "发出发射的发射" - 为什么你认为每次写1个字节的4次写入(这段代码无论如何都不会这样做)比写4个字节要快1个?
  • "有没有办法实现非恒定值的发射" - 如果你正在使用emit,那么你需要知道你正在尝试写入的值当前存储在哪个寄存器中(因为编译器可能在每次代码更改时使用不同的寄存器,而且值几乎不可能实现)甚至可能不在寄存器中。)

我试图回顾你是如何写这个来解决你原来的问题。但是:

我意识到有一种倾向认为'asm更快。"但请记住,C代码被转换为汇编程序。编译器已经生成了asm代码,几乎可以肯定它比你通过emit可以拼凑的东西效率更高。

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

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