如何使用 Visual C++ 的内联汇编程序插入重复的 NOP 语句?
How can I insert repeated NOP statements using Visual C++'s inline assembler?
使用 Microsoft 编译器的 Visual C++ 允许我们使用以下方式定义内联汇编代码:
__asm {
nop
}
我需要的是一个宏,它可以将这样的指令乘以 n 次,例如:
ASM_EMIT_MULT(op, times)
例如:
ASM_EMIT_MULT(0x90, 160)
这可能吗?我该怎么做?
使用 MASM,这很容易做到。安装的一部分是一个名为 listing.inc
的文件(因为现在每个人都将 MASM 作为 Visual Studio 的一部分,所以它将位于您的 Visual Studio 根 directory/VC/include 中)。此文件定义了一系列 npad
宏,这些宏采用单个 size
参数并扩展为适当的非破坏性 "padding" 操作码序列。如果你只需要一个字节的填充,你可以使用明显的 nop
指令。但是,英特尔实际上建议使用 other non-destructive opcodes of the appropriate length, as do other vendors,而不是使用一长串 nop
直到达到所需的长度。这些预定义的 npad
宏使您无需记住 table,更不用说使代码更具可读性了。
不幸的是,内联汇编不是一个全功能的汇编器。有很多缺失的东西是您希望在像 MASM 这样的真正的汇编程序中找到的。宏 (MACRO
) and repeats (REPEAT
/REPT
) 属于缺失的部分。
然而,ALIGN
directives are available in inline assembly. These will generate the required number of nop
s or other non-destructive opcodes to enforce alignment of the next instruction。使用它非常简单。这是一个非常愚蠢的例子,我在其中使用了工作代码并随机添加了 align
s:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
align 4
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
align 16
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
align 8
}
}
这会生成以下输出(MSVC 的程序集列表使用 npad x
,其中 x
是字节数,就像您在 MASM 中编写的那样):
PUBLIC CountDigits
_TEXT SEGMENT
_value$ = 8
CountDigits PROC
00000 8b 54 24 04 mov edx, DWORD PTR _value$[esp-4]
00004 0f bd c2 bsr eax, edx
00007 90 npad 1 ;// enforcing the "align 4"
00008 35 e0 ff ff 3f xor eax, 1073741792
0000d 8b 04 85 84 00
00 00 mov eax, DWORD PTR _kMaxDigits[eax*4+132]
00014 eb 0a 8d a4 24
00 00 00 00 8d
49 00 npad 12 ;// enforcing the "align 16"
00020 3b 14 85 fc ff
ff ff cmp edx, DWORD PTR _kPowers[eax*4-4]
00027 83 d8 00 sbb eax, 0
0002a 8d 9b 00 00 00
00 npad 6 ;// enforcing the "align 8"
00030 c2 04 00 ret 4
CountDigits ENDP
_TEXT ENDS
如果您实际上不想强制对齐,而只是想插入任意数量的 nop
s(也许作为以后热补丁的填充物?),那么您可以使用 C 宏来模拟效果:
#define NOP1 __asm { nop }
#define NOP2 NOP1 NOP1
#define NOP4 NOP2 NOP2
#define NOP8 NOP4 NOP4
#define NOP16 NOP8 NOP8
// ...
#define NOP64 NOP16 NOP16 NOP16 NOP16
// ...etc.
然后根据需要添加代码:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
NOP8
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
NOP4
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
}
}
产生以下输出:
PUBLIC CountDigits
_TEXT SEGMENT
_value$ = 8
CountDigits PROC
00000 8b 54 24 04 mov edx, DWORD PTR _value$[esp-4]
00004 0f bd c2 bsr eax, edx
00007 90 npad 1 ;// these are, of course, just good old NOPs
00008 90 npad 1
00009 90 npad 1
0000a 90 npad 1
0000b 90 npad 1
0000c 90 npad 1
0000d 90 npad 1
0000e 90 npad 1
0000f 35 e0 ff ff 3f xor eax, 1073741792
00014 8b 04 85 84 00
00 00 mov eax, DWORD PTR _kMaxDigits[eax*4+132]
0001b 90 npad 1
0001c 90 npad 1
0001d 90 npad 1
0001e 90 npad 1
0001f 3b 14 85 fc ff
ff ff cmp edx, DWORD PTR _kPowers[eax*4-4]
00026 83 d8 00 sbb eax, 0
00029 c2 04 00 ret 4
CountDigits ENDP
_TEXT ENDS
或者,更酷的是,我们可以使用一些模板元编程魔法在 style 中获得相同的效果。只需定义以下模板函数及其特化(防止无限递归很重要):
template <size_t N> __forceinline void npad()
{
npad<N-1>();
__asm { nop }
}
template <> __forceinline void npad<0>() { }
并像这样使用它:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
}
npad<8>();
__asm
{
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
}
npad<4>();
__asm
{
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
}
}
这将在所有优化的构建中产生所需的输出(与上面的完全相同)——无论您是针对大小 (/O1
) 还是速度 (/O2
) 进行优化——...但不在调试版本中。如果您在调试版本中需要它,您将不得不求助于 C 宏。 :-(
基于 Cody Gray Answer 和使用模板递归和内联或 forceinline 进行元编程的代码示例,如之前的代码所述
template <size_t N> __forceinline void npad()
{
npad<N-1>();
__asm { nop }
}
template <> __forceinline void npad<0>() { }
如果不设置一些选项,它将无法在 visual studio 上运行,并且不能保证它会运行
Although __forceinline is a stronger indication to the compiler than
__inline, inlining is still performed at the compiler's discretion, but no heuristics are used to determine the benefits from inlining this function.
您可以在此处阅读更多相关信息 https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019
使用 Microsoft 编译器的 Visual C++ 允许我们使用以下方式定义内联汇编代码:
__asm {
nop
}
我需要的是一个宏,它可以将这样的指令乘以 n 次,例如:
ASM_EMIT_MULT(op, times)
例如:
ASM_EMIT_MULT(0x90, 160)
这可能吗?我该怎么做?
使用 MASM,这很容易做到。安装的一部分是一个名为 listing.inc
的文件(因为现在每个人都将 MASM 作为 Visual Studio 的一部分,所以它将位于您的 Visual Studio 根 directory/VC/include 中)。此文件定义了一系列 npad
宏,这些宏采用单个 size
参数并扩展为适当的非破坏性 "padding" 操作码序列。如果你只需要一个字节的填充,你可以使用明显的 nop
指令。但是,英特尔实际上建议使用 other non-destructive opcodes of the appropriate length, as do other vendors,而不是使用一长串 nop
直到达到所需的长度。这些预定义的 npad
宏使您无需记住 table,更不用说使代码更具可读性了。
不幸的是,内联汇编不是一个全功能的汇编器。有很多缺失的东西是您希望在像 MASM 这样的真正的汇编程序中找到的。宏 (MACRO
) and repeats (REPEAT
/REPT
) 属于缺失的部分。
然而,ALIGN
directives are available in inline assembly. These will generate the required number of nop
s or other non-destructive opcodes to enforce alignment of the next instruction。使用它非常简单。这是一个非常愚蠢的例子,我在其中使用了工作代码并随机添加了 align
s:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
align 4
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
align 16
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
align 8
}
}
这会生成以下输出(MSVC 的程序集列表使用 npad x
,其中 x
是字节数,就像您在 MASM 中编写的那样):
PUBLIC CountDigits
_TEXT SEGMENT
_value$ = 8
CountDigits PROC
00000 8b 54 24 04 mov edx, DWORD PTR _value$[esp-4]
00004 0f bd c2 bsr eax, edx
00007 90 npad 1 ;// enforcing the "align 4"
00008 35 e0 ff ff 3f xor eax, 1073741792
0000d 8b 04 85 84 00
00 00 mov eax, DWORD PTR _kMaxDigits[eax*4+132]
00014 eb 0a 8d a4 24
00 00 00 00 8d
49 00 npad 12 ;// enforcing the "align 16"
00020 3b 14 85 fc ff
ff ff cmp edx, DWORD PTR _kPowers[eax*4-4]
00027 83 d8 00 sbb eax, 0
0002a 8d 9b 00 00 00
00 npad 6 ;// enforcing the "align 8"
00030 c2 04 00 ret 4
CountDigits ENDP
_TEXT ENDS
如果您实际上不想强制对齐,而只是想插入任意数量的 nop
s(也许作为以后热补丁的填充物?),那么您可以使用 C 宏来模拟效果:
#define NOP1 __asm { nop }
#define NOP2 NOP1 NOP1
#define NOP4 NOP2 NOP2
#define NOP8 NOP4 NOP4
#define NOP16 NOP8 NOP8
// ...
#define NOP64 NOP16 NOP16 NOP16 NOP16
// ...etc.
然后根据需要添加代码:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
NOP8
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
NOP4
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
}
}
产生以下输出:
PUBLIC CountDigits
_TEXT SEGMENT
_value$ = 8
CountDigits PROC
00000 8b 54 24 04 mov edx, DWORD PTR _value$[esp-4]
00004 0f bd c2 bsr eax, edx
00007 90 npad 1 ;// these are, of course, just good old NOPs
00008 90 npad 1
00009 90 npad 1
0000a 90 npad 1
0000b 90 npad 1
0000c 90 npad 1
0000d 90 npad 1
0000e 90 npad 1
0000f 35 e0 ff ff 3f xor eax, 1073741792
00014 8b 04 85 84 00
00 00 mov eax, DWORD PTR _kMaxDigits[eax*4+132]
0001b 90 npad 1
0001c 90 npad 1
0001d 90 npad 1
0001e 90 npad 1
0001f 3b 14 85 fc ff
ff ff cmp edx, DWORD PTR _kPowers[eax*4-4]
00026 83 d8 00 sbb eax, 0
00029 c2 04 00 ret 4
CountDigits ENDP
_TEXT ENDS
或者,更酷的是,我们可以使用一些模板元编程魔法在 style 中获得相同的效果。只需定义以下模板函数及其特化(防止无限递归很重要):
template <size_t N> __forceinline void npad()
{
npad<N-1>();
__asm { nop }
}
template <> __forceinline void npad<0>() { }
并像这样使用它:
unsigned long CountDigits(unsigned long value)
{
__asm
{
mov edx, DWORD PTR [value]
bsr eax, edx
}
npad<8>();
__asm
{
xor eax, 1073741792
mov eax, DWORD PTR [4 * eax + kMaxDigits+132]
}
npad<4>();
__asm
{
cmp edx, DWORD PTR [4 * eax + kPowers-4]
sbb eax, 0
}
}
这将在所有优化的构建中产生所需的输出(与上面的完全相同)——无论您是针对大小 (/O1
) 还是速度 (/O2
) 进行优化——...但不在调试版本中。如果您在调试版本中需要它,您将不得不求助于 C 宏。 :-(
基于 Cody Gray Answer 和使用模板递归和内联或 forceinline 进行元编程的代码示例,如之前的代码所述
template <size_t N> __forceinline void npad()
{
npad<N-1>();
__asm { nop }
}
template <> __forceinline void npad<0>() { }
如果不设置一些选项,它将无法在 visual studio 上运行,并且不能保证它会运行
Although __forceinline is a stronger indication to the compiler than __inline, inlining is still performed at the compiler's discretion, but no heuristics are used to determine the benefits from inlining this function.
您可以在此处阅读更多相关信息 https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019