如何使用 Visual C++ 的内联汇编器插入重复的 NOP 语句?

Posted

技术标签:

【中文标题】如何使用 Visual C++ 的内联汇编器插入重复的 NOP 语句?【英文标题】:How can I insert repeated NOP statements using Visual C++'s inline assembler? 【发布时间】:2016-07-20 14:47:06 【问题描述】:

Visual C++,使用微软的编译器,允许我们定义内联汇编代码:

__asm 
    nop

我需要的是一个宏,它可以将这样的指令乘以 n 次,例如:

ASM_EMIT_MULT(op, times)

例如:

ASM_EMIT_MULT(0x90, 160)

这可能吗?我怎么能这样做?

【问题讨论】:

msdn.microsoft.com/en-us/library/kyzds0ks.aspx , msdn.microsoft.com/en-us/library/352sth8z.aspx @JoseManuelAbarcaRodríguez 感谢您提供链接,但这些参考资料不清楚我该如何解决问题 【参考方案1】:

使用 MASM,这非常简单。安装的一部分是一个名为 listing.inc 的文件(因为现在每个人都将 MASM 作为 Visual Studio 的一部分,所以这将位于您的 Visual Studio 根目录/VC/include 中)。该文件定义了一系列npad 宏,它们采用单个size 参数并扩展为适当的非破坏性“填充”操作码序列。如果您只需要一个字节的填充,则使用明显的nop 指令。但与其使用一长串的nops 直到达到所需的长度,英特尔实际上推荐other non-destructive opcodes of the appropriate length,就像other vendors 一样。这些预定义的npad 宏使您不必记住该表,更不用说使代码更具可读性了。

不幸的是,内联汇编不是一个全功能的汇编程序。在真正的汇编程序(如 MASM)中缺少很多东西。缺少的东西包括宏 (MACRO) 和重复 (REPEAT/REPT)。

但是,ALIGN directivesare available in inline assembly。 These will generate the required number of nops or other non-destructive opcodes to enforce alignment of the next instruction。使用它非常简单。这是一个非常愚蠢的示例,我在其中获取了工作代码并在其中添加了随机的aligns:

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

如果您实际上并不想强制对齐,而只是想插入任意数量的nops(也许作为以后热补丁的填充物?),那么您可以使用 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 宏。 :-(

【讨论】:

太棒了! C++ 模板元编程太棒了!这是一件非常糟糕的事情,它也不适用于 /Od :( npad() 真的很好,我希望我们也可以提供一个与_emit 内联的字节,例如:... void npad(BYTE byte) ...; _asm _emit byte ,这样我们就可以发出某个字节 N 次。 你应该能够做到这一点,@karliwson。只需将byte 作为模板 参数而不是函数参数传递。 太棒了!!!!元编程是要走的路。可悲的是它不适用于 C 如果您在使用元编程示例时遇到问题,您可能想从这里开始***.com/questions/41102421/…【参考方案2】:

基于 Cody Gray 答案和代码示例,使用模板递归和内联或强制内联进行元编程,如之前的代码所述

template <size_t N> __forceinline void npad()

    npad<N-1>();
    __asm   nop 

template <> __forceinline void npad<0>()   

如果不设置一些选项,它不会在 Visual Studio 上运行,也不能保证它会运行

虽然 __forceinline 对编译器的指示比 __inline,内联仍由编译器自行决定执行,但没有使用启发式方法来确定内联此函数的好处。

你可以在这里https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019阅读更多信息

【讨论】:

以上是关于如何使用 Visual C++ 的内联汇编器插入重复的 NOP 语句?的主要内容,如果未能解决你的问题,请参考以下文章

GNU g++ 内联汇编块,如 Apple g++/Visual C++?

Visual Studio 2010 像 Visual Studio 6 一样在 C++ 中编译内联程序集?

具有非内联汇编的 Qt C++ 项目

使用内联汇编器从 GCC 中的共享库调用函数

纯 C++ 代码比内联汇编程序快 10 倍。为啥?

如何理解 Visual C++ 中的 _asm 命令