2

我在工作中一直在使用免费的 Digital Mars 编译器(我知道很淘气),并创建了一些代码来检查编译的函数并查看字节码以用于学习目的,看看我是否可以从编译器的构建方式中学到任何有价值的东西它的功能。但是,在 MSVC++ 中重新创建相同的方法失败得很惨,我得到的结果非常混乱。我有这样的功能:

unsigned int __stdcall test()
{
  return 42;
}

然后我做:

unsigned char* testCode = (unsigned char*)test;

在这种情况下,我似乎无法让 C++ static_cast 工作(它会引发编译器错误)......因此是 C 风格的强制转换,但这不是重点......我也尝试过使用参考 &test,但这无济于事。

现在,当我检查 testCode 指向的内存内容时,我感到很困惑,因为我看到的甚至看起来都不像有效代码,甚至还有一个调试断点卡在那里......它看起来像这样(目标是 IA -32):

0xe9, 0xbc, 0x18, 0x00, 0x00, 0xcc...

这显然是错误的,0xe9 是相对跳转指令,从 0xbc 字节外看是这样的:

0xcc,0xcc,0xcc……

即按照未分配或未使用内存的预期,内存初始化为调试断点操作码。

我对返回 42 的函数的期望是:

0x8b、0x2a、0x00、0x00、0x00、0xc3

或者至少一些 mov 的味道,然后是 ret (0xc2、0xc3、0xca 或 0xcb)再往下一点

MSVC++ 是出于安全原因采取措施阻止我做这种事情,还是我做了一些愚蠢的事情而没有意识到?使用 DMC 作为编译器,此方法似乎可以正常工作...

我也很难以另一种方式(执行字节),但我怀疑根本原因是相同的。

任何帮助或提示将不胜感激。

4

4 回答 4

2

我只能猜测,但我很确定您正在检查调试版本。在调试模式下,MSVC++ 编译器将所有调用替换为对跳转存根的调用。这意味着,每个函数都从跳转到真正的函数开始,这正是您在这里所面临的。
周围的 0xCC 字节确实是断点指令,以便在您执行不应该执行的代码时触发可能附加的调试器。
在发布版本中尝试相同的操作。这应该按预期工作。

编辑: 这实际上受链接器设置 /INCREMENTAL 的影响。您所描述的效果没有出现在发布版本中的原因是,如果打开了任何类型的优化,这些跳转存根就会被简单地优化掉(这当然通常是发布版本的情况)。

于 2009-02-06T22:25:49.453 回答
2

对于您想要的演员表:

unsigned char* testCode = reinterpret_cast<unsigned char*>( test );

在项目 -> 属性 -> C/C++ -> 常规中将调试信息格式从“用于编辑和继续的程序数据库 (/ZI)”切换为“程序数据库 (/Zi)”。我相信正是这种设置导致编译器插入跳转代码,以便调试器可以在程序运行时重建函数并对其进行热补丁。也可能关闭“启用最小重建”。

在 MSVC 中检查代码的一种更简单的方法是简单地设置一个断点并检查反汇编(右键单击该行并从弹出菜单中选择“转到反汇编”。它使用源代码注释反汇编,以便您可以看到每一行编译成什么。

于 2009-02-06T22:38:48.507 回答
1

如果您想查看给定编译函数的程序集和机器代码,向编译器提供 /FAcs 命令行选项并查看随后的 .asm 文件会更容易。

我不确定将函数指针转换为字节流的定义行为是什么——它甚至可能无法正常工作——但另一个可能造成混淆的原因是 x86 函数都是可变大小的,而且也是 little-endian .

于 2009-02-06T21:43:30.943 回答
1

如果这是在打开增量链接的情况下,那么您看到的是jmp [destination]. 您可以运行调试器并查看反汇编要验证的内容。

于 2009-02-06T22:09:44.137 回答