【问题标题】:What is the real signature of a C++ constructor after compilation编译后 C++ 构造函数的真正签名是什么
【发布时间】:2011-12-03 08:29:07
【问题描述】:

C++中构造函数的real签名是什么?

背景: 我尝试挂钩 dll 的内部函数。我有 pdb-Debug Files 并且能够获得函数的确切位置,包括它的未修饰名称。 函数如下:

protected: __cdecl ClassName::ClassName(bool * __ptr64) __ptr64

所以它显然是一个构造函数。 我尝试了 void function(void * pThisPointer, bool * pBoolPointer),但由于程序在简单地转发调用后崩溃(其他函数以这种方式工作正常),我认为我的签名是错误的。

你知道构造函数使用什么签名(很可能不是虚拟的)吗? 或者您有什么其他想法可能会出错?

编辑: 我使用 x64 和 Visual Studio 2010 编译器,目标编译器应该类似于 Visual Studio 编译器,因为它是一个 microsoft dll。

【问题讨论】:

  • 构造函数不是常规函数;无论你偶然发现什么方法,它们都可能包含或不包含许多魔法。
  • 其实是__thiscall。更神奇的是调用基本构造函数并在最后交换 vtable 指针。自己不容易做到的事情。
  • 我不需要完全自己调用它。我只是想“包裹”它。所以当你说 __thiscall 时,这是否仅仅意味着我必须添加 void * thisPointer 作为第一个参数?

标签: c++ constructor hook reverse-engineering signature


【解决方案1】:

您不能直接调用构造函数。就 C++ 而言,构造函数没有名称。换句话说,您不能调用构造函数。许多编译器创建两个甚至三个不同的函数。你应该打电话给哪个?

答案是否定的。您不能也不应尝试直接从您的代码中调用构造函数。

【讨论】:

  • 我的 pdb 文件显示了构造函数的错误名称。我看到了函数的 asm 反汇编。事实上,如果我只调用它,它就可以工作。如果我在调用后尝试增加调试计数器,程序只会崩溃。所以我的猜测是堆栈以某种方式被损坏或类似的东西。当我在通话前增加计数器时,一切正常,也是。编译器应该和 Visual Studio 编译器类似
  • @ArtificialMind:不,不应该。从链接的角度来看,构造函数显然必须有一个名称(实际上是多个名称)。从语言本身的角度来看,构造函数没有名称。当您创建某个类的实例时调用哪些函数以及哪些函数是您不应该依赖的。你不能也不能从你的代码中调用构造函数,即使它的名字是乱七八糟的。 (事实上​​,直接用乱七八糟的名字来调用任何东西都是非法的。)
  • 这个 dll 是一个 windows dll,所以在我的版本中它不太可能改变。所以依赖它不改变应该是“安全的”。
【解决方案2】:

最简单的方法是查看实际构造函数的反汇编,看看它访问了什么。 MSVC(和其他编译器)的通常约定是将 this 作为隐藏的第一个参数传递。通常它使用 __thiscall 约定(即在 x86 上的 ecx 中),但在 x64 上只有一种约定,所以 __thiscall strong> 与 __cdecl__stdcall 相同。

但是,MSVC 的另一个不太为人所知的约定是 构造函数必须返回 this 指针。我怀疑这就是导致您崩溃的原因。

这里可能不太相关,但请查看 MSVC C++ 内部的my article。它描述了 x86 实现,但很多东西都适用

【讨论】:

    【解决方案3】:

    由于您不能合法地将函数指针指向构造函数,因此就 C++ 本身而言,它实际上没有有意义的签名。

    实现做自己的事,但通常一个类在发出的代码中至少可以有两个构造函数体 - 一个用于正常使用,另一个用于用作虚拟基础时。编译器知道如何使用每一个——你需要确切地知道在你的实现中这个发出的代码中完成了对象构造的哪些部分,调用者在调用它之前做了什么,调用者在它返回之后做了什么,以及如果调用者抛出异常,调用者做了什么,或者即使你已经从调试信息中找出了它的地址,你也无法正确调用它。

    要对对象构造进行逆向工程,您应该查看在使用此构造函数创建对象的地方发出的代码。

    【讨论】:

    • 也许我让自己不够清楚:我不想自己称呼它。我只是想钩住它,这样我就可以记录调用构造函数的时间和参数。如果我只是传递到目前为止的参数,一切都会很好,我什至可以在调用之前增加一个全局计数器变量。但是如果我之后尝试增加它,程序就会崩溃。如果我记录 this-Pointer,那么我会得到大约 30 个不同的指针,用于 > 1000 次调用,有些使用更多,有些使用更少,但我认为这对应于内存管理
    • @ArtificialMind:我仍然认为最好的办法是查看发出的代码来调用它。如果您是对的,并且堆栈已损坏,那么这可能是因为调用者需要在构造函数返回后执行一些堆栈操作,但在增加计数器之前您没有执行此操作。也就是说,这个内部构造函数的调用约定可能与常规函数不同,cdecl 可能会产生误导。它甚至不必是常规调用约定之一,尽管它必须记录在 Windows C++ ABI 中的某处。
    • 这听起来很有希望。据我所见,该函数以push rbx; sub rsp, 20h 开头并以add rsp,20h; pop rbx; retn 结尾,我理解为“保存rbx,分配20byte 一个堆栈,在堆栈上释放20 byte,恢复rbx,返回”听起来我必须确保所有寄存器都期望 rbx (这是调用基于堆栈的约定的第二个参数吗?)与进入我的函数相同。是否有一些内在的保存所有寄存器并在退出时恢复它们?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-06
    相关资源
    最近更新 更多