aboutsummaryrefslogtreecommitdiff
path: root/lldb/test/API/functionalities/vtable/TestVTableValue.py
blob: f0076ea28f7599def8c5713f51dd1448085404ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""
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

    @skipIf(compiler="clang", compiler_version=["<", "9.0"])
    @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.assertEqual(vtable.GetName(), "vtable for Shape")
        self.assertEqual(vtable.GetTypeName(), "vtable for Shape")
        # Make sure we have the right number of virtual functions in our vtable
        # for the shape class.
        self.assertEqual(vtable.GetNumChildren(), 4)

        # Verify vtable address
        vtable_addr = vtable.GetValueAsUnsigned(0)
        expected_addr = self.expected_vtable_addr(shape)
        self.assertEqual(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.assertEqual(vtable.GetName(), "vtable for Shape")
        self.assertEqual(vtable.GetTypeName(), "vtable for Shape")
        # Make sure we have the right number of virtual functions in our vtable
        # for the shape class.
        self.assertEqual(vtable.GetNumChildren(), 4)

        # Verify vtable address
        vtable_addr = vtable.GetValueAsUnsigned(0)
        expected_addr = self.expected_vtable_addr(shape)
        self.assertEqual(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.assertEqual(vtable.GetName(), "vtable for Rectangle")
        self.assertEqual(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.assertEqual(vtable.GetNumChildren(), 5)

        # Verify vtable address
        vtable_addr = vtable.GetValueAsUnsigned()
        expected_addr = self.expected_vtable_addr(rect)
        self.assertEqual(vtable_addr, expected_addr)

        for idx, vtable_entry in enumerate(vtable.children):
            self.verify_vtable_entry(vtable_entry, vtable_addr, idx)

    @skipIf(compiler="clang", compiler_version=["<", "9.0"])
    @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.assertEqual(shape_ptr_vtable.GetName(), "vtable for Rectangle")
        self.assertEqual(shape_ptr_vtable.GetNumChildren(), 5)
        self.assertEqual(shape_ptr.GetValueAsUnsigned(0), rect.GetLoadAddress())
        lldbutil.continue_to_source_breakpoint(
            self, process, "Shape is Shape", lldb.SBFileSpec("main.cpp")
        )
        self.assertEqual(shape_ptr.GetValueAsUnsigned(0), shape.GetLoadAddress())
        self.assertEqual(shape_ptr_vtable.GetNumChildren(), 4)
        self.assertEqual(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.assertEqual(vtable.GetName(), "vtable for Shape")
        self.assertEqual(vtable.GetTypeName(), "vtable for Shape")
        # Make sure we have the right number of virtual functions in our vtable
        # for the shape class.
        self.assertEqual(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()

        is_64bit = self.process().GetAddressByteSize() == 8
        data = str(
            "\x01\x01\x01\x01\x01\x01\x01\x01" if is_64bit else "\x01\x01\x01\x01"
        )
        error = lldb.SBError()
        bytes_written = process.WriteMemory(vtable_addr, data, error)

        self.assertSuccess(error)
        self.assertGreater(
            bytes_written, 0, "Failed to overwrite first entry in vtable"
        )

        scribbled_child = vtable.GetChildAtIndex(0)
        self.assertEqual(
            scribbled_child.GetValueAsUnsigned(0),
            0x0101010101010101 if is_64bit else 0x01010101,
        )
        self.assertEqual(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.assertEqual(
            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.assertEqual(vtable_entry.GetType(), func_type.GetPointerType())

        # The summary should be the address description of the function pointer
        summary = vtable_entry.GetSummary()
        self.assertEqual(str(sb_addr), summary)