Add the ability to get a C++ vtable ValueObject from another
ValueObject.
This patch adds the ability to ask a ValueObject for a ValueObject that
represents the virtual function table for a C++ class. If the
ValueObject is not a C++ class with a vtable, a valid ValueObject value
will be returned that contains an appropriate error. If it is successful
a valid ValueObject that represents vtable will be returned. The
ValueObject that is returned will have a name that matches the demangled
value for a C++ vtable mangled name like "vtable for <class-name>". It
will have N children, one for each virtual function pointer. Each
child's value is the function pointer itself, the summary is the
symbolication of this function pointer, and the type will be a valid
function pointer from the debug info if there is debug information
corresponding to the virtual function pointer.
The vtable SBValue will have the following:
- SBValue::GetName() returns "vtable for <class>"
- SBValue::GetValue() returns a string representation of the vtable
address
- SBValue::GetSummary() returns NULL
- SBValue::GetType() returns a type appropriate for a uintptr_t type for
the current process
- SBValue::GetLoadAddress() returns the address of the vtable adderess
- SBValue::GetValueAsUnsigned(...) returns the vtable address
- SBValue::GetNumChildren() returns the number of virtual function
pointers in the vtable
- SBValue::GetChildAtIndex(...) returns a SBValue that represents a
virtual function pointer
The child SBValue objects that represent a virtual function pointer has
the following values:
- SBValue::GetName() returns "[%u]" where %u is the vtable function
pointer index
- SBValue::GetValue() returns a string representation of the virtual
function pointer
- SBValue::GetSummary() returns a symbolicated respresentation of the
virtual function pointer
- SBValue::GetType() returns the function prototype type if there is
debug info, or a generic funtion prototype if there is no debug info
- SBValue::GetLoadAddress() returns the address of the virtual function
pointer
- SBValue::GetValueAsUnsigned(...) returns the virtual function pointer
- SBValue::GetNumChildren() returns 0
- SBValue::GetChildAtIndex(...) returns invalid SBValue for any index
Examples of using this API via python:
```
(lldb) script vtable = lldb.frame.FindVariable("shape_ptr").GetVTable()
(lldb) script vtable
vtable for Shape = 0x0000000100004088 {
[0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3
[1] = 0x0000000100003e4c a.out`Shape::~Shape() at main.cpp:3
[2] = 0x0000000100003e7c a.out`Shape::area() at main.cpp:4
[3] = 0x0000000100003e3c a.out`Shape::optional() at main.cpp:7
}
(lldb) script c = vtable.GetChildAtIndex(0)
(lldb) script c
(void ()) [0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3
```
187 lines
7.7 KiB
Python
187 lines
7.7 KiB
Python
"""
|
|
Make sure the getting a variable path works and doesn't crash.
|
|
"""
|
|
|
|
|
|
import lldb
|
|
import lldbsuite.test.lldbutil as lldbutil
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
class TestVTableValue(TestBase):
|
|
# If your test case doesn't stress debug info, then
|
|
# set this to true. That way it won't be run once for
|
|
# each debug info format.
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
@skipUnlessPlatform(["linux", "macosx"])
|
|
def test_vtable(self):
|
|
self.build()
|
|
lldbutil.run_to_source_breakpoint(
|
|
self, "At the end", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
|
|
# Test a shape instance to make sure we get the vtable correctly.
|
|
shape = self.frame().FindVariable("shape")
|
|
vtable = shape.GetVTable()
|
|
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
|
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
|
# Make sure we have the right number of virtual functions in our vtable
|
|
# for the shape class.
|
|
self.assertEquals(vtable.GetNumChildren(), 4)
|
|
|
|
# Verify vtable address
|
|
vtable_addr = vtable.GetValueAsUnsigned(0)
|
|
expected_addr = self.expected_vtable_addr(shape)
|
|
self.assertEquals(vtable_addr, expected_addr)
|
|
|
|
for (idx, vtable_entry) in enumerate(vtable.children):
|
|
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
|
|
|
# Test a shape reference to make sure we get the vtable correctly.
|
|
shape = self.frame().FindVariable("shape_ref")
|
|
vtable = shape.GetVTable()
|
|
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
|
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
|
# Make sure we have the right number of virtual functions in our vtable
|
|
# for the shape class.
|
|
self.assertEquals(vtable.GetNumChildren(), 4)
|
|
|
|
# Verify vtable address
|
|
vtable_addr = vtable.GetValueAsUnsigned(0)
|
|
expected_addr = self.expected_vtable_addr(shape)
|
|
self.assertEquals(vtable_addr, expected_addr)
|
|
|
|
for (idx, vtable_entry) in enumerate(vtable.children):
|
|
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
|
|
|
|
|
# Test we get the right vtable for the Rectangle instance.
|
|
rect = self.frame().FindVariable("rect")
|
|
vtable = rect.GetVTable()
|
|
self.assertEquals(vtable.GetName(), "vtable for Rectangle")
|
|
self.assertEquals(vtable.GetTypeName(), "vtable for Rectangle")
|
|
|
|
# Make sure we have the right number of virtual functions in our vtable
|
|
# with the extra virtual function added by the Rectangle class
|
|
self.assertEquals(vtable.GetNumChildren(), 5)
|
|
|
|
# Verify vtable address
|
|
vtable_addr = vtable.GetValueAsUnsigned()
|
|
expected_addr = self.expected_vtable_addr(rect)
|
|
self.assertEquals(vtable_addr, expected_addr)
|
|
|
|
for (idx, vtable_entry) in enumerate(vtable.children):
|
|
self.verify_vtable_entry(vtable_entry, vtable_addr, idx)
|
|
|
|
@skipUnlessPlatform(["linux", "macosx"])
|
|
def test_base_class_ptr(self):
|
|
self.build()
|
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
|
self, "Shape is Rectangle", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
|
|
shape = self.frame().FindVariable("shape")
|
|
rect = self.frame().FindVariable("rect")
|
|
|
|
shape_ptr = self.frame().FindVariable("shape_ptr")
|
|
shape_ptr_vtable = shape_ptr.GetVTable()
|
|
self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Rectangle")
|
|
self.assertEquals(shape_ptr_vtable.GetNumChildren(), 5)
|
|
self.assertEquals(shape_ptr.GetValueAsUnsigned(0),
|
|
rect.GetLoadAddress())
|
|
lldbutil.continue_to_source_breakpoint(
|
|
self, process, "Shape is Shape", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
self.assertEquals(shape_ptr.GetValueAsUnsigned(0),
|
|
shape.GetLoadAddress())
|
|
self.assertEquals(shape_ptr_vtable.GetNumChildren(), 4)
|
|
self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Shape")
|
|
|
|
@skipUnlessPlatform(["linux", "macosx"])
|
|
def test_no_vtable(self):
|
|
self.build()
|
|
lldbutil.run_to_source_breakpoint(
|
|
self, "At the end", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
|
|
var = self.frame().FindVariable("not_virtual")
|
|
self.assertEqual(var.GetVTable().GetError().GetCString(),
|
|
'type "NotVirtual" doesn\'t have a vtable')
|
|
|
|
var = self.frame().FindVariable("argc")
|
|
self.assertEqual(var.GetVTable().GetError().GetCString(),
|
|
'no language runtime support for the language "c"')
|
|
|
|
@skipUnlessPlatform(["linux", "macosx"])
|
|
def test_overwrite_vtable(self):
|
|
self.build()
|
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
|
self, "At the end", lldb.SBFileSpec("main.cpp")
|
|
)
|
|
|
|
# Test a shape instance to make sure we get the vtable correctly.
|
|
shape = self.frame().FindVariable("shape")
|
|
vtable = shape.GetVTable()
|
|
self.assertEquals(vtable.GetName(), "vtable for Shape")
|
|
self.assertEquals(vtable.GetTypeName(), "vtable for Shape")
|
|
# Make sure we have the right number of virtual functions in our vtable
|
|
# for the shape class.
|
|
self.assertEquals(vtable.GetNumChildren(), 4)
|
|
|
|
# Overwrite the first entry in the vtable and make sure we can still
|
|
# see the bogus value which should have no summary
|
|
vtable_addr = vtable.GetValueAsUnsigned()
|
|
data = str("\x01\x01\x01\x01\x01\x01\x01\x01")
|
|
error = lldb.SBError()
|
|
process.WriteMemory(vtable_addr, data, error)
|
|
|
|
scribbled_child = vtable.GetChildAtIndex(0)
|
|
self.assertEquals(scribbled_child.GetValueAsUnsigned(0),
|
|
0x0101010101010101)
|
|
self.assertEquals(scribbled_child.GetSummary(), None)
|
|
|
|
def expected_vtable_addr(self, var: lldb.SBValue) -> int:
|
|
load_addr = var.GetLoadAddress()
|
|
read_from_memory_error = lldb.SBError()
|
|
vtable_addr = self.process().ReadPointerFromMemory(
|
|
load_addr, read_from_memory_error
|
|
)
|
|
self.assertTrue(read_from_memory_error.Success())
|
|
return vtable_addr
|
|
|
|
def expected_vtable_entry_func_ptr(self, vtable_addr: int, idx: int):
|
|
vtable_entry_addr = vtable_addr + idx * self.process().GetAddressByteSize()
|
|
read_func_ptr_error = lldb.SBError()
|
|
func_ptr = self.process().ReadPointerFromMemory(vtable_entry_addr,
|
|
read_func_ptr_error)
|
|
self.assertTrue(read_func_ptr_error.Success())
|
|
return func_ptr
|
|
|
|
def verify_vtable_entry(self, vtable_entry: lldb.SBValue, vtable_addr: int,
|
|
idx: int):
|
|
"""Verify the vtable entry looks something like:
|
|
|
|
(double ()) [0] = 0x0000000100003a10 a.out`Rectangle::Area() at main.cpp:14
|
|
|
|
"""
|
|
# Check function ptr
|
|
vtable_entry_func_ptr = vtable_entry.GetValueAsUnsigned(0)
|
|
self.assertEquals(
|
|
vtable_entry_func_ptr,
|
|
self.expected_vtable_entry_func_ptr(vtable_addr, idx),
|
|
)
|
|
|
|
sb_addr = self.target().ResolveLoadAddress(vtable_entry_func_ptr)
|
|
sym_ctx = sb_addr.GetSymbolContext(lldb.eSymbolContextEverything)
|
|
|
|
# Make sure the type is the same as the function type
|
|
func_type = sym_ctx.GetFunction().GetType()
|
|
if func_type.IsValid():
|
|
self.assertEquals(vtable_entry.GetType(),
|
|
func_type.GetPointerType())
|
|
|
|
# The summary should be the address description of the function pointer
|
|
summary = vtable_entry.GetSummary()
|
|
self.assertEquals(str(sb_addr), summary)
|