class Base {public: Base(){}protected: float x;};class VBase {public: VBase(){} virtual void test(){}; virtual void foo(){};protected: float x;};class VBaseA: public VBase {public: VBaseA(){} virtual void test(){} virtual void foo(){};protected: float x;};class VBaseB: public VBase {public: VBaseB(){} virtual void test(){ printf("test \n"); } virtual void foo(){};protected: float x;};class VDerived : public VBaseA, public Base, public VBaseB {public: VDerived(){} virtual void test(){} virtual void foo(){};protected: float x;};int main(int argc, char *argv[]){ VDerived *pDerived = new VDerived(); //0x0000000103407f30 Base *pBase = (Base*)pDerived; //0x0000000103407f40 VBaseA *pvBaseA = static_cast<VBaseA*>(pDerived);//0x0000000103407f30 VBaseB *pvBaseB = static_cast<VBaseB*>(pDerived);//0x0000000103407f30 这里应该为0x0000000103407f48,但是显示的是0x0000000103407f30 unsigned long pBaseAddressbase = (unsigned long)pBase; unsigned long pvBaseAAddressbase = (unsigned long)pvBaseA; unsigned long pvBaseBAddressbase = (unsigned long)pvBaseB; pvBaseB->test();}
movl %esp, %ebp //movl是指令名称。%则表明esp和ebp是寄存器.在AT&T语法中, 第一个是源操作数,第二个是目的操作数。
MOVQ EBP, ESP //interl手册,你会看到是没有%的intel语法, 它的操作数顺序刚好相反
(lldb) memory read pDerived0x103407f30: 40 40 00 00 01 00 00 00 00 00 00 00 00 00 00 00 @@..............0x103407f40: 10 00 00 00 00 00 00 00 60 40 00 00 01 00 00 00 ........`@......(lldb) memory read pvBaseB0x103407f48: 60 40 00 00 01 00 00 00 00 00 00 00 00 00 00 00 `@..............0x103407f58: de 2d 05 10 00 00 00 00 00 00 00 00 00 00 00 00 .-..............
pBaseB->test();0x100003c84 <+244>: movq -0x40(%rbp), %rax //-x40存方的是pBaseB指针的内容,这里取出pBaseB指向的地址0x100003c88 <+248>: movq (%rax), %rcx //然后将 rax的内容赋值给rcx0x100003c8b <+251>: movq %rax, %rdi // 之后再将rax的值给到rdi寄存器:我们都知道,rdi寄存器是函数调用的第一个参数,这里的this是基类的地址-> 0x100003c8e <+254>: callq *(%rcx) // 在这里取出rcx的地址,然后通过*(rcx) 间接调用rcx中存的地址
我们再跳到VDerived::test函数的汇编实现, 在这里通过lldb的命令:
register read rdi
查看函数的第一个传参,也就是 this的地址,已经是派生类的地址了,不是调用前基类的地址
testCPPVirtualMemeory`VDerived::test: 0x100003e00 <+0>: pushq %rbp // 栈低指针压栈 0x100003e01 <+1>: movq %rsp, %rbp // 将BP指针指向SP,因为上一级函数的栈顶指针是下一级函数的栈底指针 0x100003e04 <+4>: subq $0x10, %rsp // 开始函数栈帧空间 0x100003e08 <+8>: movq %rdi, -0x8(%rbp) // 将函数第一个参数入栈,也就是this 指针-> 0x100003e0c <+12>: leaq 0x15c(%rip), %rdi ; "test\n" 0x100003e13 <+19>: movb $0x0, %al 0x100003e15 <+21>: callq 0x100003efc ; symbol stub for: printf 0x100003e1a <+26>: addq $0x10, %rsp //回收栈空间 0x100003e1e <+30>: popq %rbp //出栈 指回上一层 rbp 0x100003e1f <+31>: retq //指向下一条命令
// Now go through all virtual member functions and add them to the current // vftable. This is done by // - replacing overridden methods in their existing slots, as long as they // don't require return adjustment; calculating This adjustment if needed. // - adding new slots for methods of the current base not present in any // sub-bases; // - adding new slots for methods that require Return adjustment. // We keep track of the methods visited in the sub-bases in MethodInfoMap.
void VFTableBuilder::AddMethods(BaseSubobject Base, unsigned BaseDepth, const CXXRecordDecl *LastVBase, BasesSetVectorTy &VisitedBases) { const CXXRecordDecl *RD = Base.getBase(); if (!RD->isPolymorphic()) return;
const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD);
// See if this class expands a vftable of the base we look at, which is either // the one defined by the vfptr base path or the primary base of the current // class. const CXXRecordDecl *NextBase = nullptr, *NextLastVBase = LastVBase; CharUnits NextBaseOffset; if (BaseDepth < WhichVFPtr.PathToIntroducingObject.size()) { NextBase = WhichVFPtr.PathToIntroducingObject[BaseDepth]; if (isDirectVBase(NextBase, RD)) { NextLastVBase = NextBase; NextBaseOffset = MostDerivedClassLayout.getVBaseClassOffset(NextBase); } else { NextBaseOffset = Base.getBaseOffset() + Layout.getBaseClassOffset(NextBase); } } else if (const CXXRecordDecl *PrimaryBase = Layout.getPrimaryBase()) { assert(!Layout.isPrimaryBaseVirtual() && "No primary virtual bases in this ABI"); NextBase = PrimaryBase; NextBaseOffset = Base.getBaseOffset(); }
if (NextBase) { AddMethods(BaseSubobject(NextBase, NextBaseOffset), BaseDepth + 1, NextLastVBase, VisitedBases); if (!VisitedBases.insert(NextBase)) llvm_unreachable("Found a duplicate primary base!"); }
SmallVector<const CXXMethodDecl*, 10> VirtualMethods; // Put virtual methods in the proper order. GroupNewVirtualOverloads(RD, VirtualMethods);
// Now go through all virtual member functions and add them to the current // vftable. This is done by // - replacing overridden methods in their existing slots, as long as they // don't require return adjustment; calculating This adjustment if needed. // - adding new slots for methods of the current base not present in any // sub-bases; // - adding new slots for methods that require Return adjustment. // We keep track of the methods visited in the sub-bases in MethodInfoMap. for (const CXXMethodDecl *MD : VirtualMethods) { FinalOverriders::OverriderInfo FinalOverrider = Overriders.getOverrider(MD, Base.getBaseOffset()); const CXXMethodDecl *FinalOverriderMD = FinalOverrider.Method; const CXXMethodDecl *OverriddenMD = FindNearestOverriddenMethod(MD, VisitedBases);
ThisAdjustment ThisAdjustmentOffset; bool ReturnAdjustingThunk = false, ForceReturnAdjustmentMangling = false; CharUnits ThisOffset = ComputeThisOffset(FinalOverrider); ThisAdjustmentOffset.NonVirtual = (ThisOffset - WhichVFPtr.FullOffsetInMDC).getQuantity(); if ((OverriddenMD || FinalOverriderMD != MD) && WhichVFPtr.getVBaseWithVPtr()) CalculateVtordispAdjustment(FinalOverrider, ThisOffset, ThisAdjustmentOffset);
unsigned VBIndex = LastVBase ? VTables.getVBTableIndex(MostDerivedClass, LastVBase) : 0;
if (OverriddenMD) { // If MD overrides anything in this vftable, we need to update the // entries. MethodInfoMapTy::iterator OverriddenMDIterator = MethodInfoMap.find(OverriddenMD);
// If the overridden method went to a different vftable, skip it. if (OverriddenMDIterator == MethodInfoMap.end()) continue;
MethodInfo &OverriddenMethodInfo = OverriddenMDIterator->second;
VBIndex = OverriddenMethodInfo.VBTableIndex;
// Let's check if the overrider requires any return adjustments. // We must create a new slot if the MD's return type is not trivially // convertible to the OverriddenMD's one. // Once a chain of method overrides adds a return adjusting vftable slot, // all subsequent overrides will also use an extra method slot. ReturnAdjustingThunk = !ComputeReturnAdjustmentBaseOffset( Context, MD, OverriddenMD).isEmpty() || OverriddenMethodInfo.UsesExtraSlot;
if (!ReturnAdjustingThunk) { // No return adjustment needed - just replace the overridden method info // with the current info. MethodInfo MI(VBIndex, OverriddenMethodInfo.VFTableIndex); MethodInfoMap.erase(OverriddenMDIterator);
assert(!MethodInfoMap.count(MD) && "Should not have method info for this method yet!"); MethodInfoMap.insert(std::make_pair(MD, MI)); continue; }
// In case we need a return adjustment, we'll add a new slot for // the overrider. Mark the overridden method as shadowed by the new slot. OverriddenMethodInfo.Shadowed = true;
// Force a special name mangling for a return-adjusting thunk // unless the method is the final overrider without this adjustment. ForceReturnAdjustmentMangling = !(MD == FinalOverriderMD && ThisAdjustmentOffset.isEmpty()); } else if (Base.getBaseOffset() != WhichVFPtr.FullOffsetInMDC || MD->size_overridden_methods()) { // Skip methods that don't belong to the vftable of the current class, // e.g. each method that wasn't seen in any of the visited sub-bases // but overrides multiple methods of other sub-bases. continue; }
// If we got here, MD is a method not seen in any of the sub-bases or // it requires return adjustment. Insert the method info for this method. MethodInfo MI(VBIndex, HasRTTIComponent ? Components.size() - 1 : Components.size(), ReturnAdjustingThunk);
assert(!MethodInfoMap.count(MD) && "Should not have method info for this method yet!"); MethodInfoMap.insert(std::make_pair(MD, MI));
// Check if this overrider needs a return adjustment. // We don't want to do this for pure virtual member functions. BaseOffset ReturnAdjustmentOffset; ReturnAdjustment ReturnAdjustment; if (!FinalOverriderMD->isPure()) { ReturnAdjustmentOffset = ComputeReturnAdjustmentBaseOffset(Context, FinalOverriderMD, MD); } if (!ReturnAdjustmentOffset.isEmpty()) { ForceReturnAdjustmentMangling = true; ReturnAdjustment.NonVirtual = ReturnAdjustmentOffset.NonVirtualOffset.getQuantity(); if (ReturnAdjustmentOffset.VirtualBase) { const ASTRecordLayout &DerivedLayout = Context.getASTRecordLayout(ReturnAdjustmentOffset.DerivedClass); ReturnAdjustment.Virtual.Microsoft.VBPtrOffset = DerivedLayout.getVBPtrOffset().getQuantity(); ReturnAdjustment.Virtual.Microsoft.VBIndex = VTables.getVBTableIndex(ReturnAdjustmentOffset.DerivedClass, ReturnAdjustmentOffset.VirtualBase); } }
AddMethod(FinalOverriderMD, ThunkInfo(ThisAdjustmentOffset, ReturnAdjustment, ForceReturnAdjustmentMangling ? MD : nullptr)); }}
struct ThunkInfo { /// The \c this pointer adjustment. ThisAdjustment This;
/// The return adjustment. ReturnAdjustment Return;
/// Holds a pointer to the overridden method this thunk is for, /// if needed by the ABI to distinguish different thunks with equal /// adjustments. Otherwise, null. /// CAUTION: In the unlikely event you need to sort ThunkInfos, consider using /// an ABI-specific comparator. const CXXMethodDecl *Method;
ThunkInfo() : Method(nullptr) { }
ThunkInfo(const ThisAdjustment &This, const ReturnAdjustment &Return, const CXXMethodDecl *Method = nullptr) : This(This), Return(Return), Method(Method) {}
friend bool operator==(const ThunkInfo &LHS, const ThunkInfo &RHS) { return LHS.This == RHS.This && LHS.Return == RHS.Return && LHS.Method == RHS.Method; }
bool isEmpty() const { return This.isEmpty() && Return.isEmpty() && Method == nullptr; }};
}
void ItaniumMangleContextImpl::mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk, raw_ostream &Out) { // <special-name> ::= T <call-offset> <base encoding> // # base is the nominal target function of thunk // <special-name> ::= Tc <call-offset> <call-offset> <base encoding> // # base is the nominal target function of thunk // # first call-offset is 'this' adjustment // # second call-offset is result adjustment
assert(!isa<CXXDestructorDecl>(MD) && "Use mangleCXXDtor for destructor decls!"); CXXNameMangler Mangler(*this, Out); Mangler.getStream() << "_ZT"; if (!Thunk.Return.isEmpty()) Mangler.getStream() << 'c';
// Mangle the 'this' pointer adjustment. Mangler.mangleCallOffset(Thunk.This.NonVirtual, Thunk.This.Virtual.Itanium.VCallOffsetOffset);
// Mangle the return pointer adjustment if there is one. if (!Thunk.Return.isEmpty()) Mangler.mangleCallOffset(Thunk.Return.NonVirtual, Thunk.Return.Virtual.Itanium.VBaseOffsetOffset);
Mangler.mangleFunctionEncoding(MD);}
public A { virtual void test() { }}public B { virtual void test1() { }}public C { virtual void test2() { }}public D : public virtual A, public virtual B, public C { virtual void test1() { // 这里实现的test1函数在 B类的虚函数表里就是virtual-trunk的类型 } virtual void test2() { // 这里实现的test2函数在 C类的虚函数表示就是no-virtual-trunk的类型 }}
// For Itanium, if the type has a vtable pointer in the object, it will be at // offset 0 // in the object. That will point to the "address point" within the vtable // (not the beginning of the // vtable.) We can then look up the symbol containing this "address point" // and that symbol's name // demangled will contain the full class name. // The second pointer above the "address point" is the "offset_to_top". We'll // use that to get the // start of the value object which holds the dynamic type.
bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress( ValueObject &in_value, lldb::DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name, Address &dynamic_address, Value::ValueType &value_type) { // For Itanium, if the type has a vtable pointer in the object, it will be at // offset 0 // in the object. That will point to the "address point" within the vtable // (not the beginning of the // vtable.) We can then look up the symbol containing this "address point" // and that symbol's name // demangled will contain the full class name. // The second pointer above the "address point" is the "offset_to_top". We'll // use that to get the // start of the value object which holds the dynamic type. //
class_type_or_name.Clear(); value_type = Value::ValueType::eValueTypeScalar;
// Only a pointer or reference type can have a different dynamic and static // type: if (CouldHaveDynamicValue(in_value)) { // First job, pull out the address at 0 offset from the object. AddressType address_type; lldb::addr_t original_ptr = in_value.GetPointerValue(&address_type); if (original_ptr == LLDB_INVALID_ADDRESS) return false;
ExecutionContext exe_ctx(in_value.GetExecutionContextRef());
Process *process = exe_ctx.GetProcessPtr();
if (process == nullptr) return false;
Status error; const lldb::addr_t vtable_address_point = process->ReadPointerFromMemory(original_ptr, error);
if (!error.Success() || vtable_address_point == LLDB_INVALID_ADDRESS) { return false; }
class_type_or_name = GetTypeInfoFromVTableAddress(in_value, original_ptr, vtable_address_point);
if (class_type_or_name) { TypeSP type_sp = class_type_or_name.GetTypeSP(); // There can only be one type with a given name, // so we've just found duplicate definitions, and this // one will do as well as any other. // We don't consider something to have a dynamic type if // it is the same as the static type. So compare against // the value we were handed. if (type_sp) { if (ClangASTContext::AreTypesSame(in_value.GetCompilerType(), type_sp->GetForwardCompilerType())) { // The dynamic type we found was the same type, // so we don't have a dynamic type here... return false; }
// The offset_to_top is two pointers above the vtable pointer. const uint32_t addr_byte_size = process->GetAddressByteSize(); const lldb::addr_t offset_to_top_location = vtable_address_point - 2 * addr_byte_size; // Watch for underflow, offset_to_top_location should be less than // vtable_address_point if (offset_to_top_location >= vtable_address_point) return false; const int64_t offset_to_top = process->ReadSignedIntegerFromMemory( offset_to_top_location, addr_byte_size, INT64_MIN, error);
if (offset_to_top == INT64_MIN) return false; // So the dynamic type is a value that starts at offset_to_top // above the original address. lldb::addr_t dynamic_addr = original_ptr + offset_to_top; if (!process->GetTarget().GetSectionLoadList().ResolveLoadAddress( dynamic_addr, dynamic_address)) { dynamic_address.SetRawAddress(dynamic_addr); } return true; } } }
return class_type_or_name.IsEmpty() == false;}
通过上面代码分析可知,每次在通过LLDB 命令expression动态调用 指针地址的时候,LLDB 会去按照调试器默认的格式进行格式化,格式化的前提是动态获取到对应的类型和偏移后的地址;
在碰到C++有虚表的时候,且不是虚表中的第一个基类指针的时候,就会使用指针上头的offset_to_top 获取到这个对应动态的类型和返回动态获取的该类型对象开始的地址。
点击阅读原文查看详情!