【发布时间】:2017-06-30 20:37:47
【问题描述】:
我使用 Visual Studio C++ 2008 SP1,x64C++ 编译器编译了以下内容:
我很好奇,为什么编译器会在那些 calls 之后添加那些 nop 指令?
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。 附加信息: 首先,感谢大家的意见。
以下是补充意见:
我的第一个猜测是incremental linking 可能与它有关。但是,项目的
Visual Studio中的Release构建设置关闭了incremental linking。这似乎只影响
x64构建。以x86(或Win32)构建的相同代码没有nops,即使使用的指令非常相似:
- 我尝试使用更新的链接器构建它,尽管
VS 2013生成的x64代码看起来有些不同,但它仍然在一些nops 之后添加了nops:
- 另外
dynamic与static链接到MFC 对nops 的存在没有影响。这是使用VS 2013动态链接到 MFC dll 构建的:
- 还要注意那些
nops 也可以出现在near和farcalls 之后,它们与对齐无关。这是我从IDA获得的部分代码,如果我再往前走一点:
如您所见,nop 插入在 far call 之后,恰好“对齐”B 地址上的下一个 lea 指令!如果这些只是为了对齐而添加的,那是没有意义的。
- 我最初倾向于相信,因为
nearrelativecalls(即以E8开头的那些)是somewhat faster而不是farcalls(或以calls开头的那些)FF,15在这种情况下)
链接器可能会先尝试使用nearcalls,因为这些比farcalls 短一个字节,如果成功,它可能会用nops 填充剩余空间在末尾。但是上面的例子(5)有点推翻了这个假设。
所以我仍然没有明确的答案。
【问题讨论】:
-
填充对齐?
-
看起来很像 RIP 相关的间接调用,链接器对直接调用放宽了——间接调用在 x86 上要长一个字节,因此链接器插入了 nop 以使它们具有相同的长度。跨度>
-
我怀疑@Fanael 是正确的。摆脱那个 NOP 意味着改变所有的代码。但是转移所有代码会改变很多地址。似乎是通过 NOP 解决的先有鸡还是先有蛋的问题。
-
@Mysticial Weird 这对于 ELF 如何进行(动态)链接来说是不必要的。
-
@fuz 使用 ELF 函数调用共享库中的函数总是通过存根函数进行。在 Windows 上,对 DLL 中的函数的函数调用通常是使用间接调用指令直接进行的。上面反汇编中的
call cs:LoadIconW指令就是一个例子。反汇编程序调用LoadIconW的位置包含一个指向实际LoadIconW函数的指针。
标签: c++ visual-studio assembly 64-bit disassembly