如何根据IDA反编译的伪代码还原生成该代码的C++源码?
我通过IDA7反编译某二进制文件,得到以下内容,已确认伪代码翻译准确。分析发现A::start()的逻辑是判断getFD是否来自ref_B虚表,用于处理子类重写getFD但未重写start的场景,以下是反编译内容及源码还原分析:
反编译内容
1. A::start() 伪代码
int A::start(){ int (__fastcall *v3)(B *const); // x2 unsigned int fd; // w0 int (__fastcall *v5)(A *const, int); // x3 int v6; // w20 v3 = *(this->_vptr.B + 6); // 指向getFD(ref_B的第6个函数) if ( v3 != B::getFD ) { fd = v3(&this->B); v5 = *(this->_vptr.B + 7); if ( v5 == B::listen ) goto LABEL_4; LABEL_9: v6 = v5(this, fb); if ( v6 ) goto LABEL_10; goto LABEL_4; } fd = this->mFd; v5 = *(this->_vptr.B + 7); if ( v5 != B::listen ) // 核心点:A未重写listen,所以此判断永远不成立 // 因此不能直接替换为this->listen(fb) goto LABEL_9; LABEL_4: if ( evutil_socketpair(1, 1, 0, fd) ) // 来自libevent2 { fd = -1LL; return -1; } LABEL_10: return 0; }
2. A的构造函数伪代码
void A::A(A *const this) { B::B(&this->B); this->_vptr.B = ref_B; }
3. ref_B虚表内容
.data.rel.ro:000000555644D8D8 DCQ _ZTI1A ; `typeinfo for'A .data.rel.ro:000000555644D8E0 ref_B DCQ _ZThn8_N1AD1Ev ; `non-virtual thunk to'A::~A() .data.rel.ro:000000555644D8E8 DCQ _ZThn8_N1AD0Ev ; `non-virtual thunk to'A::~A() .data.rel.ro:000000555644D8F0 DCQ _ZThn8_N1A4openE8TYPE ; `non-virtual thunk to'A::open(TYPE) .data.rel.ro:000000555644D8F8 DCQ _ZThn8_N1A5closeEv ; `non-virtual thunk to'A::close(void) .data.rel.ro:000000555644D900 DCQ _ZN1B9get******Ev ; B::get******(void) .data.rel.ro:000000555644D908 DCQ _ZN1B9write****EPKcm ; B::write****(char const*,ulong) .data.rel.ro:000000555644D910 DCQ _ZN1B5getFDEv ; B::getFD(void) .data.rel.ro:000000555644D918 DCQ _ZN1B6listenEi ; `non-virtual thunk to'B::listen(int)
4. 类A结构
struct __cppobj A : B { pthread_mutex_t mMutex; };
5. 类B结构
struct B { int (**_vptr.B)(...); // IDA生成,等价于_vptr_B int mFd; };
源码还原分析
这种代码是GCC等编译器对非虚成员函数中调用虚函数的优化结果,对应的C++源码结构如下:
#include <event2/util.h> // 基类B struct B { int mFd; // 虚函数列表(对应ref_B中的顺序) virtual ~B() = default; virtual void open(int type) {} virtual void close() {} virtual int getXXX() { return 0; } virtual size_t writeXXX(const char*, size_t) { return 0; } virtual int getFD() { return mFd; } virtual int listen(int fb) { return 0; } // 非虚成员函数start int start(int fb) { int fd; // 编译器优化:直接检查虚表中的getFD是否为B的原生版本 if (this->__vptr[6] != reinterpret_cast<void*>(&B::getFD)) { fd = getFD(); // 子类重写了getFD,需通过虚调用获取 // 检查listen是否为B的原生版本 if (this->__vptr[7] == reinterpret_cast<void*>(&B::listen)) { goto do_socketpair; } int ret = listen(fb); if (ret) { return 0; } goto do_socketpair; } else { fd = mFd; // 未重写getFD,直接访问成员变量 if (this->__vptr[7] != reinterpret_cast<void*>(&B::listen)) { int ret = listen(fb); if (ret) { return 0; } goto do_socketpair; } } do_socketpair: int fds[2]; if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { return -1; } // 原逻辑中fd被用于socketpair,此处省略具体赋值逻辑 return 0; } }; // 子类A,继承自B struct A : B { pthread_mutex_t mMutex; // 重写部分虚函数,未重写getFD、listen ~A() override {} void open(int type) override {} void close() override {} };
关键细节说明
- 虚表thunk函数:ref_B中的
_ZThn8_N1AD1Ev等是编译器生成的非虚thunk,用于调整this指针——因为A比B多了8字节的mMutex成员,调用A的虚函数时需要将this指针偏移8字节,才能正确访问A的成员。 - 编译器优化逻辑:
start是B的非虚成员函数,编译器会判断当前对象的虚表项是否为B的原生函数:- 若
getFD未被重写,直接访问mFd,避免虚调用的开销; - 若
getFD被重写,则通过虚表调用; - 对
listen的判断同理,决定是否需要虚调用。
- 若
- 场景适配:这种代码用于处理「子类重写了基类的部分虚函数,但未重写包含这些虚函数调用的非虚成员函数」的场景,既保证多态正确性,又尽可能减少虚调用的性能损耗。
内容的提问来源于stack exchange,提问作者yed
相关产品推荐
相关产品推荐

