为什么64位的VC ++编译后函数调用添加NOP指令?

问题描述 投票:20回答:3

我整理了以下使用Visual Studio C ++ 2008 SP1,x64 C++编译:

enter image description here

我很好奇,为什么编译器添加这些nops后那些call说明?

PS1。我能够理解,第二和第三nops将对齐在4字节保证金的代码,但是第nop打破这一假设。

PS2。已编译的C ++代码没有循环或特殊优化的东西在里面:

CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CTestDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

    //This makes no sense. I used it to set a debugger breakpoint
    ::GdiFlush();
    srand(::GetTickCount());
}

PS3。附加信息:首先,谢谢大家对你的输入。

这里的补充意见:

  1. 我的第一个猜测是,incremental linking可能已经有一些东西需要用它做。但是,在ReleaseVisual Studio构建设置的项目有incremental linking关闭。
  2. 这似乎影响x64只依据。建成x86(或Win32)相同的代码没有这些nops,即使使用的指令非常相似:

enter image description here

  1. 我试图用一个新的连接器来构建它,即使由x64产生的VS 2013代码看起来有些不同,但仍然有一些nops之后将那些calls:

enter image description here

  1. 此外dynamic VS static链接到MFC做这些nops的存在没有什么区别。这是一个与动态链接构建与VS 2013到MFC DLL文件:

enter image description here

  1. 还要注意的是后nopnear fars以及那些calls可以出现,他们什么都没有做比对。下面是我从IDA了,如果我再上一步一点点的代码的一部分:

enter image description here

正如你看到的,nop的是,发生在“对齐”下一个far指令在call地址lea B之后插入!这是没有意义的,如果这些都仅用于比对增加。

  1. 我本来倾向于认为,既然near relative calls(即那些与E8开始)比somewhat faster fars call(或与FF开始的,15在这种情况下)

enter image description here

接头可以尝试先用near calls去了,因为这些是一个字节比far calls短,如果成功,它可能垫nops的剩余空间在最后。但随后的示例(5)上述有点儿失败这一假说。

所以,我仍然没有一个明确的答案。

c++ visual-studio assembly 64bit disassembly
3个回答
3
投票

这纯粹是一种猜测,但它可能是某种SEH的优化。我说的优化,因为SEH似乎没有NOP指令来工作也没关系。 NOP可能有助于加速展开。

在下面的例子(live demo with VC2017),有一个呼叫之后插入一个NOPbasic_string::assign但不是在test1 test2(相同的,但声明为不可throwing1)。

#include <stdio.h>
#include <string>

int test1() {
  std::string s = "a";  // NOP insterted here
  s += getchar();
  return (int)s.length();
}

int test2() throw() {
  std::string s = "a";
  s += getchar();
  return (int)s.length();
}

int main()
{
  return test1() + test2();
}

部件:

test1:
    . . .
    call     std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign
    npad     1         ; nop
    call     getchar
    . . .
test2:
    . . .
    call     std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign
    call     getchar

需要注意的是MSVS编译默认与/EHsc标志(同步异常处理)。如果没有该标志的NOPs消失,并与/EHa(同步和异步异常处理),throw()不再有差别,因为SEH始终打开。


1出于某种原因,只throw()似乎减少了代码大小,使用noexcept使得生成的代码更大,召唤更NOPs。 MSVC ...


0
投票

这是特殊的填料,让异常处理程序/平仓功能正确检测无论是在功能的序言/结尾/体。


-2
投票

这是由于在64位调用约定要求堆栈是任何呼叫指令之前对准的16个字节。这不是(我knwoledge)硬件要求,但软件之一。这提供了一种以确保进入的函数(即,呼叫指令之后)时,堆栈指针的值始终是8模16因此允许简单的数据alignement和存储/从对齐位置在堆读取。

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