Windows中asm(“nop”)的实现

问题描述 投票:0回答:2

是一个空行代码,以分号等同于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

我可以使用这个void __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上工作)。

谢谢。

c assembly visual-c++ nop
2个回答
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指令读取变量。


3
投票

是一个空行代码,以分号等同于asm(“nop”)指令结束?

不,当然不。你可以自己尝试一下。 (在您自己的机器上,或在Godbolt编译器资源管理器上,https://godbolt.org/

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


__nop()不是图书馆的功能。这是一个内在的,完全符合你的要求。例如

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

使用Clang7.0 -O3为x86-64 Linux编译到这个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指令的序言之后,可能?),因此它们支持使用jmp rel8进行热修补。


如果在1指令函数中使用它,则在x64 MSVC的npad 2之前得到npad 1(2字节NOP):

int bar(int a, int b) {
    __nop();
    return a+b;
}
;; x64 MSVC 19.16
int bar(int,int) PROC                                  ; bar, COMDAT
    npad    2
    npad    1
    lea     eax, DWORD PTR [rcx+rdx]
    ret     0

我不确定MSVC会对纯寄存器指令重新排序NOP有多积极,但a^=b;之后的__nop()实际上会在NOP指令之前产生xor ecx, edx

但是,wrt。在这种情况下,MSVC决定不重新排序任何东西来填充那个2字节的插槽。

int sink;
int foo(int a, int b) {
    __nop();
    sink = 1;
    //a^=b;
    return a+b;
}
;; 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

它首先执行LEA,但不会在__nop()之前移动它;似乎是一个明显错过的优化,但如果你插入__nop()指令然后再次优化显然不是优先级。


如果你编译成.obj.exe并拆卸,你会看到一个普通的0x90 nop。但不幸的是,Godbolt不支持MSVC,只支持Linux编译器,因此我可以轻松完成复制asm文本输出。

正如你所期望的那样,在__nop() ifdefed out的情况下,函数正常编译,编译相同但没有npad指令。


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

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

© www.soinside.com 2019 - 2024. All rights reserved.