Skip to content

Commit cbbc924

Browse files
Add tests for disassembly variable annotations and decoding edge cases
This patch adds API tests to verify that DWARF variable location annotations are correctly displayed in the disassembly output. The tests cover: - Local variables in loops and functions - Multiple stack variables - Control flow edge cases - Different optimization levels (-O0, -O1, -O2) - Ensuring decoding errors are excluded from output
1 parent 6bbc8aa commit cbbc924

File tree

9 files changed

+328
-35
lines changed

9 files changed

+328
-35
lines changed
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
CXX := clang++
2-
CXX_SOURCES := main.cpp
3-
CXXFLAGS_EXTRAS := -g -O0 -fno-inline
1+
2+
# CXX_SOURCES := a_loop_with_local_variable.c b_multiple_stack_variables.c c_variable_passed_to_another_function.c d_original_example.c e_control_flow_edge.c
3+
C_SOURCES := a_loop_with_local_variable.c b_multiple_stack_variables.c c_variable_passed_to_another_function.c d_original_example.c e_control_flow_edge.c
4+
5+
46
include Makefile.rules

lldb/test/API/functionalities/rich-disassembler/TestRichDisassembler.py

Lines changed: 278 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,298 @@
22
from lldbsuite.test.decorators import *
33

44
class TestRichDisassembler(TestBase):
5-
"""
6-
Tests that the disassembler includes DWARF variable annotations in output.
7-
Specifically checks that variables like 'a' and 'temp' are shown with DW_OP locations.
8-
"""
9-
def test_variable_annotation(self):
10-
print("Building with:", self.getCompiler())
11-
self.build()
5+
6+
@no_debug_info_test
7+
def test_a_loop_with_local_variable(self):
8+
"""
9+
Tests that the disassembler includes basic DWARF variable annotation in output.
10+
Specifically checks that local variables in a loop are shown with DW_OP locations.
11+
Additionally, it verifies that the disassembly does not contain decoding errors.
12+
"""
13+
self.build(dictionary={
14+
'C_SOURCES': 'a_loop_with_local_variable.c',
15+
'CFLAGS_EXTRAS': '-g -O0'
16+
})
17+
exe = self.getBuildArtifact("a.out")
18+
target = self.dbg.CreateTarget(exe)
19+
self.assertTrue(target)
20+
21+
# Set a breakpoint inside main's loop
22+
src_file = lldb.SBFileSpec("test_loop_function_call.c")
23+
breakpoint = target.BreakpointCreateByName("main")
24+
self.assertGreater(breakpoint.GetNumLocations(), 0)
25+
26+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
27+
self.assertTrue(process, "Failed to launch process")
28+
self.assertEqual(process.GetState(), lldb.eStateStopped)
29+
30+
frame = process.GetSelectedThread().GetSelectedFrame()
31+
disasm = frame.Disassemble()
32+
print(disasm)
33+
34+
# Check that we have DWARF annotations for variables
35+
self.assertIn("i = ", disasm)
36+
self.assertIn("DW_OP", disasm)
37+
self.assertNotIn("<decoding error>", disasm)
38+
39+
40+
@no_debug_info_test
41+
def test_b_multiple_stack_variables_O0(self):
42+
"""
43+
Tests disassembler output for b_multiple_stack_variables.c built with -O0.
44+
This test checks that multiple local variables are annotated with DWARF
45+
and that their locations are distinct. It also ensures that no decoding errors appear.
46+
"""
47+
self.build(dictionary={
48+
'C_SOURCES': 'b_multiple_stack_variables.c',
49+
'CFLAGS_EXTRAS': '-g -O0'
50+
})
51+
exe = self.getBuildArtifact("a.out")
52+
target = self.dbg.CreateTarget(exe)
53+
self.assertTrue(target)
54+
55+
# Set a breakpoint inside main's loop
56+
src_file = lldb.SBFileSpec("test_loop_function_call.c")
57+
breakpoint = target.BreakpointCreateByName("main")
58+
self.assertGreater(breakpoint.GetNumLocations(), 0)
59+
60+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
61+
self.assertTrue(process, "Failed to launch process")
62+
self.assertEqual(process.GetState(), lldb.eStateStopped)
63+
64+
frame = process.GetSelectedThread().GetSelectedFrame()
65+
disasm = frame.Disassemble()
66+
print(disasm)
67+
68+
# Check that we have DWARF annotations for variables
69+
self.assertIn("a = ", disasm)
70+
self.assertIn("b = ", disasm)
71+
self.assertIn("c = ", disasm)
72+
self.assertIn("DW_OP", disasm)
73+
self.assertNotIn("<decoding error>", disasm)
74+
75+
76+
@no_debug_info_test
77+
def test_b_multiple_stack_variables_O1(self):
78+
"""
79+
Tests disassembler output for b_multiple_stack_variables.c built with -O1.
80+
Due to optimizations, some variables may be optimized out.
81+
We only check for 'c' and ensure no decoding errors appear.
82+
"""
83+
self.build(dictionary={
84+
'C_SOURCES': 'b_multiple_stack_variables.c',
85+
'CFLAGS_EXTRAS': '-g -O1'
86+
})
87+
exe = self.getBuildArtifact("a.out")
88+
target = self.dbg.CreateTarget(exe)
89+
self.assertTrue(target)
90+
91+
breakpoint = target.BreakpointCreateByName("main")
92+
self.assertGreater(breakpoint.GetNumLocations(), 0)
93+
94+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
95+
self.assertTrue(process, "Failed to launch process")
96+
self.assertEqual(process.GetState(), lldb.eStateStopped)
97+
98+
frame = process.GetSelectedThread().GetSelectedFrame()
99+
disasm = frame.Disassemble()
100+
print(disasm)
101+
102+
self.assertIn("c = ", disasm)
103+
self.assertIn("DW_OP", disasm)
104+
self.assertNotIn("<decoding error>", disasm)
105+
106+
107+
@no_debug_info_test
108+
def test_c_variable_passed_to_another_function(self):
109+
"""
110+
Tests disassembler output for c_variable_passed_to_another_function.c.
111+
This test checks that a variable passed to another function is annotated
112+
with DWARF and that its location is distinct. It also ensures that no decoding errors appear.
113+
"""
114+
self.build(dictionary={
115+
'C_SOURCES': 'c_variable_passed_to_another_function.c',
116+
'CFLAGS_EXTRAS': '-g -O0'
117+
})
118+
exe = self.getBuildArtifact("a.out")
119+
target = self.dbg.CreateTarget(exe)
120+
self.assertTrue(target)
121+
122+
breakpoint = target.BreakpointCreateByName("main")
123+
self.assertGreater(breakpoint.GetNumLocations(), 0)
124+
125+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
126+
self.assertTrue(process, "Failed to launch process")
127+
self.assertEqual(process.GetState(), lldb.eStateStopped)
128+
129+
frame = process.GetSelectedThread().GetSelectedFrame()
130+
disasm = frame.Disassemble()
131+
print(disasm)
132+
133+
self.assertIn("x = ", disasm)
134+
self.assertIn("DW_OP", disasm)
135+
self.assertNotIn("<decoding error>", disasm)
136+
137+
138+
@no_debug_info_test
139+
def test_c_variable_passed_to_another_function_O1(self):
140+
"""
141+
Tests disassembler output for c_variable_passed_to_another_function.c built with -O1.
142+
"""
143+
self.build(dictionary={
144+
'C_SOURCES': 'c_variable_passed_to_another_function.c',
145+
'CFLAGS_EXTRAS': '-g -O1'
146+
})
147+
exe = self.getBuildArtifact("a.out")
148+
target = self.dbg.CreateTarget(exe)
149+
self.assertTrue(target)
150+
151+
breakpoint = target.BreakpointCreateByName("main")
152+
self.assertGreater(breakpoint.GetNumLocations(), 0)
153+
154+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
155+
self.assertTrue(process, "Failed to launch process")
156+
self.assertEqual(process.GetState(), lldb.eStateStopped)
157+
158+
frame = process.GetSelectedThread().GetSelectedFrame()
159+
disasm = frame.Disassemble()
160+
print(disasm)
161+
162+
self.assertIn("x = ", disasm)
163+
self.assertIn("arg = ", disasm)
164+
self.assertIn("DW_OP", disasm)
165+
self.assertNotIn("<decoding error>", disasm)
166+
167+
@no_debug_info_test
168+
def test_d_original_example(self):
169+
"""
170+
Tests disassembler output for d_original_example.c.
171+
This test checks that the disassembly includes basic DWARF variable annotations
172+
and that local variables in the main function are shown with DW_OP locations.
173+
Additionally, it verifies that the disassembly does not contain decoding errors.
174+
"""
175+
self.build(dictionary={
176+
'C_SOURCES': 'd_original_example.c',
177+
'CFLAGS_EXTRAS': '-g -O0'
178+
})
12179
exe = self.getBuildArtifact("a.out")
13180
target = self.dbg.CreateTarget(exe)
14181
self.assertTrue(target)
15182

16-
src_file = lldb.SBFileSpec("main.cpp")
17-
breakpoint = target.BreakpointCreateByName("test")
18-
print("Breakpoint locations:", breakpoint.GetNumLocations())
183+
breakpoint = target.BreakpointCreateByName("main")
19184
self.assertGreater(breakpoint.GetNumLocations(), 0)
20185

21186
process = target.LaunchSimple(None, None, self.get_process_working_directory())
22-
print("Process state:", process.GetState())
23-
print("Exit status:", process.GetExitStatus())
24-
print("Exit description:", process.GetExitDescription())
187+
self.assertTrue(process, "Failed to launch process")
188+
self.assertEqual(process.GetState(), lldb.eStateStopped)
189+
190+
frame = process.GetSelectedThread().GetSelectedFrame()
191+
disasm = frame.Disassemble()
192+
print(disasm)
193+
194+
self.assertIn("argc = ", disasm)
195+
self.assertIn("argv = ", disasm)
196+
self.assertIn("i = ", disasm)
197+
self.assertIn("DW_OP", disasm)
198+
self.assertNotIn("<decoding error>", disasm)
199+
200+
@no_debug_info_test
201+
def test_d_original_example_O1(self):
202+
"""
203+
Tests disassembler output for d_original_example.c built with -O1.
204+
"""
205+
self.build(dictionary={
206+
'C_SOURCES': 'd_original_example.c',
207+
'CFLAGS_EXTRAS': '-g -O1'
208+
})
209+
exe = self.getBuildArtifact("a.out")
210+
target = self.dbg.CreateTarget(exe)
211+
self.assertTrue(target)
212+
213+
breakpoint = target.BreakpointCreateByName("main")
214+
self.assertGreater(breakpoint.GetNumLocations(), 0)
215+
216+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
217+
self.assertTrue(process, "Failed to launch process")
218+
self.assertEqual(process.GetState(), lldb.eStateStopped)
219+
220+
frame = process.GetSelectedThread().GetSelectedFrame()
221+
disasm = frame.Disassemble()
222+
print(disasm)
223+
224+
self.assertIn("argc = ", disasm)
225+
self.assertIn("argv = ", disasm)
226+
self.assertIn("i = ", disasm)
227+
self.assertIn("DW_OP_reg", disasm)
228+
self.assertIn("DW_OP_stack_value", disasm)
229+
self.assertNotIn("<decoding error>", disasm)
230+
231+
232+
@no_debug_info_test
233+
def test_e_control_flow_edge(self):
234+
"""
235+
Tests disassembler output for e_control_flow_edge.c with a focus on control flow edges.
236+
This test checks that the disassembly includes basic DWARF variable annotations
237+
and that local variables in the main function are shown with DW_OP locations.
238+
Additionally, it verifies that the disassembly does not contain decoding errors.
239+
"""
240+
self.build(dictionary={
241+
'C_SOURCES': 'e_control_flow_edge.c',
242+
'CFLAGS_EXTRAS': '-g -O0'
243+
})
244+
exe = self.getBuildArtifact("a.out")
245+
target = self.dbg.CreateTarget(exe)
246+
self.assertTrue(target)
247+
248+
breakpoint = target.BreakpointCreateByName("main")
249+
self.assertGreater(breakpoint.GetNumLocations(), 0)
25250

251+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
26252
self.assertTrue(process, "Failed to launch process")
27-
self.assertEqual(process.GetState(), lldb.eStateStopped, "Process did not stop")
253+
self.assertEqual(process.GetState(), lldb.eStateStopped)
28254

29255
frame = process.GetSelectedThread().GetSelectedFrame()
30256
disasm = frame.Disassemble()
31257
print(disasm)
32258

33-
# Check that at least one DWARF annotation is shown.
259+
self.assertIn("a = ", disasm)
260+
self.assertIn("b = ", disasm)
34261
self.assertIn("DW_OP_", disasm)
262+
self.assertNotIn("<decoding error>", disasm)
263+
264+
@no_debug_info_test
265+
def test_e_control_flow_edge_O1(self):
266+
"""
267+
Tests disassembler output for e_control_flow_edge.c built with -O1.
268+
This test checks that the disassembly annotation does not contain decoding errors.
269+
"""
270+
self.build(dictionary={
271+
'C_SOURCES': 'e_control_flow_edge.c',
272+
'CFLAGS_EXTRAS': '-g -O1'
273+
})
274+
exe = self.getBuildArtifact("a.out")
275+
target = self.dbg.CreateTarget(exe)
276+
self.assertTrue(target)
277+
278+
breakpoint = target.BreakpointCreateByName("main")
279+
self.assertGreater(breakpoint.GetNumLocations(), 0)
280+
281+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
282+
self.assertTrue(process, "Failed to launch process")
283+
self.assertEqual(process.GetState(), lldb.eStateStopped)
284+
285+
frame = process.GetSelectedThread().GetSelectedFrame()
286+
disasm = frame.Disassemble()
287+
print(disasm)
288+
289+
self.assertNotIn("<decoding error>", disasm)
290+
291+
292+
293+
294+
295+
296+
297+
35298

36-
# Check that at least one variable name is annotated.
37-
self.assertRegex(disasm, r'[a-zA-Z_]\w*\s*=\s*DW_OP_')
38299

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <stdio.h>
2+
3+
int main() {
4+
for (int i = 0; i < 3; ++i) {
5+
puts("Hi");
6+
}
7+
return 0;
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <stdio.h>
2+
3+
int main() {
4+
int a = 1;
5+
int b = 2;
6+
int c = a + b;
7+
return c;
8+
}
9+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <stdio.h>
2+
3+
void foo(int arg) {
4+
printf("%d\n", arg);
5+
}
6+
7+
int main() {
8+
int x = 10;
9+
foo(x);
10+
return 0;
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include <stdio.h>
2+
3+
int main(int argc, char **argv) {
4+
for (int i = 1; i < argc; ++i)
5+
puts(argv[i]);
6+
return 0;
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <stdio.h>
2+
3+
int main() {
4+
int a = 1;
5+
if (a > 0) {
6+
int b = 2;
7+
return b;
8+
}
9+
return a;
10+
}

lldb/test/API/functionalities/rich-disassembler/main.cpp

Lines changed: 0 additions & 15 deletions
This file was deleted.
-16.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)