Windows中asm(“ nop”)的实现

时间:2019-02-28 05:21:51

标签: c assembly visual-c++ nop

是否有空的代码行以等价的asm(“ nop”)指令结尾?

volatile int x = 5;

if(x == 5){
  printf("x has not been changed yet\n");
}
else{
  ;//Is this the same as asm("nop") or __asm nop in windows?
  //alternatively could use __asm nop or __nop();
}

我看着这个答案,它使我不想使用使用内联汇编的x86特定实现。 Is `__asm nop` the Windows equivalent of `asm volatile("nop");` from GCC compiler

我可以使用这个无效的__nop(); msdn似乎推荐的功能,但是如果我不必的话,我不想在库中拖动。 https://docs.microsoft.com/en-us/cpp/intrinsics/nop?view=vs-2017

是否有一种便宜,可移植的方式来添加不会被编译出来的nop指令?我以为空分号不是nop还是已编译出来,但是由于某种原因,我今晚找不到任何信息。

说明编辑我可以使用内联asm在x86上执行此操作,但我希望它具有可移植性。我可以使用Windows库__nop(),但是我不想将库导入到我的项目中,这是不希望的开销。

我正在寻找一种可以生成NOP指令的切割器方法,该指令不会被优化(最好使用标准C语法),可以将其制成MACRO并在整个项目中使用,而其开销和工作量则最小(或者可以轻松实现)可以在Windows / linux / x86 / x64上进行改进。

谢谢。

2 个答案:

答案 0 :(得分:3)

  

是否有空的代码行以等价的asm(“ nop”)指令结尾?

不,当然不是。您可以自己尝试一下。 (在您自己的计算机上,或者在Godbolt编译器浏览器上,https://godbolt.org/

如果FOO(x);扩展为;,则您不希望无辜的CPP宏引入NOP,因为在这种情况下,FOO()的适当定义是空字符串。


__nop()不是库函数。这是完全符合您想要的功能的 int 例如

#ifdef USE_NOP

#ifdef _MSC_VER
#include <intrin.h>
#define NOP() __nop()       // _emit 0x90
#else
// assume __GNUC__ inline asm
#define NOP() asm("nop")    // implicitly volatile
#endif

#else
#define NOP()  // no NOPs
#endif

int idx(int *arr, int b) {
    NOP();
    return arr[b];
}

针对x86-64 Linux使用Clang7.0 -O3编译为该asm

idx(int*, int):
    nop
    movsxd  rax, esi                     # sign extend b
    mov     eax, dword ptr [rdi + 4*rax]
    ret

使用32位x86 MSVC 19.16 -O2 -Gv编译到此asm

int idx(int *,int) PROC                                    ; idx, COMDAT
    npad    1                           ; pad with a 1 byte NOP
    mov     eax, DWORD PTR [ecx+edx*4]  ; __vectorcall arg regs
    ret     0

并使用x64 MSVC 19.16 -O2 -Gv编译到此asm Godbolt for all of them

int idx(int *,int) PROC                             ; idx, COMDAT
    movsxd  rax, edx
    npad    1                           ; pad with a 1 byte NOP
    mov     eax, DWORD PTR [rcx+rax*4]  ; x64 __vectorcall arg regs
    ret     0

有趣的是,b到64位的符号扩展是在NOP之前完成的。显然,x64 MSVC(默认情况下)要求函数至少以2字节或更长的指令开头(可能在1字节push指令的序言之后?),因此它们支持使用{{ 1}}。


如果在1指令功能中使用此命令,则会从x64 MSVC中在jmp rel8 之前获得npad 2(2字节NOP):

npad 1
int bar(int a, int b) {
    __nop();
    return a+b;
}

我不确定MSVC将如何针对纯寄存器指令对NOP进行重新排序,但是在;; x64 MSVC 19.16 int bar(int,int) PROC ; bar, COMDAT npad 2 npad 1 lea eax, DWORD PTR [rcx+rdx] ret 0 之后的a^=b;实际上会导致__nop() 之前 NOP指令。

但是wrt。内存访问,在这种情况下,MSVC决定不重新排序任何东西以填充该2字节的插槽。

xor     ecx, edx
int sink;
int foo(int a, int b) {
    __nop();
    sink = 1;
    //a^=b;
    return a+b;
}

它首先执行LEA,但不会在;; MSVC 19.16 -O2 int foo(int,int) PROC ; foo, COMDAT npad 2 npad 1 lea eax, DWORD PTR [rcx+rdx] mov DWORD PTR int sink, 1 ; sink ret 0 之前移动它;似乎是一个明显的错过优化的地方,但是如果您要插入__nop()指令,那么显然不是优先考虑的地方。


如果您编译为__nop().obj并进行反汇编,则会看到普通的.exe 。但是不幸的是,Godbolt不支持MSVC,只有Linux编译器,所以我能做的就是复制asm文本输出。

正如您所期望的那样,使用0x90 nop进行扩展时,函数可以正常编译为相同的代码,但没有__nop()指令。


npad指令的运行次数与C抽象机中NOP()宏的运行次数相同。订购wrt。优化器或wrt无法保证周围的非nop内存访问。寄存器中的计算。

如果希望它成为编译时内存的重新排序障碍,则对于GNU C,请使用asm(“ nop” :::“ memory”);`。我认为对于MSVC,这必须分开。

答案 1 :(得分:3)

  

我的意思是我不想添加一个库来强制编译器添加一个NOP。

...以独立于编译器设置(例如优化设置)的方式以及与所有Visual C ++版本(甚至可能是其他编译器)一起使用的方式:

没有机会:只要汇编代码具有C代码所描述的行为,编译器就可以自由地生成代码。

并且由于NOP指令不会改变程序的行为,因此编译器可以自由添加或保留程序。

即使您找到了一种强制编译器生成NOP的方法,也可以:一个编译器更新或Windows更新修改某些文件,并且编译器可能不再生成NOP指令。

  

我可以使用内联asm在x86上执行此操作,但我希望它具有可移植性。

如上所述,任何强制编译器编写NOP的方法仅适用于特定CPU的特定编译器版本。

使用内联程序集或__nop(),您可能会涉及某个制造商的所有编译器(例如:所有GNU C编译器或Visual C ++的所有变体等)。

另一个问题是:您明确需要“官方” NOP指令吗?还是可以忍受任何什么都不做的指令?

如果您可以接受(几乎)什么都不做的任何指令,则读取全局或静态volatile变量可以代替NOP

static volatile char dummy;
    ...
else
{
    (void)dummy;
}

这应强制编译器添加一条MOV指令来读取变量dummy

背景:

如果编写了设备驱动程序,则可以将变量dummy链接到读取该变量具有“副作用”的某个位置。例如:读取位于VGA视频内存中的变量可能会影响屏幕内容!

使用volatile关键字,您不仅会告诉编译器变量的值可能随时更改,而且读取变量可能会产生这种影响。

这意味着编译器必须假定不读取变量会导致程序无法正常运行。它无法优化掉(实际上是不必要的)MOV指令来读取变量。