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 位代码时随时发生中断,则进程可能最终崩溃。
我正在尝试在 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 位代码时随时发生中断,则进程可能最终崩溃。