Skip to content

Commit ff6dfaf

Browse files
djbluejeaye
andauthored
Implement gdb/pretty_print.py (#767)
* Implement `gdb/pretty_print.py` * Add frame filter to pretty printer * Add `jank_print_opaque_box` * Code cleanup * Fix pretty printer issues with tagged pointers * Fix opaque_box pretty printing * Filter out internal implementation detail frames * Rework some C API bits * Install gdb plugin with jank * Disable filtering * Install gdb dir * Add gdb dir to flake --------- Co-authored-by: jeaye <contact@jeaye.com>
1 parent 8140570 commit ff6dfaf

5 files changed

Lines changed: 236 additions & 0 deletions

File tree

compiler+runtime/cmake/install.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ jank_glob_install_without_prefix(
100100
# This is used for formatting C++ code at runtime.
101101
install(FILES ${CMAKE_SOURCE_DIR}/../.clang-format DESTINATION share)
102102

103+
# This can be used to improve debugging in GDB.
104+
install(DIRECTORY ${CMAKE_SOURCE_DIR}/../gdb DESTINATION share)
105+
103106
# If we've built jank with a local Clang/LLVM, we can't reasonably expect the target system
104107
# to have our custom Clang. In this case, the default behavior is to install Clang alongside
105108
# jank, within jank's resource dir. We copy Clang as well as Clang's resource dir and jank

compiler+runtime/include/cpp/jank/c_api.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ extern "C"
157157
jank_object_ref jank_set_create(jank_u64 size, ...);
158158

159159
jank_object_ref jank_box(char const *type, void const *o);
160+
char const *jank_box_type(jank_object_ref o);
160161
void *jank_unbox(char const *type, jank_object_ref o);
161162
void *jank_unbox_with_source(char const *type, jank_object_ref o, jank_object_ref source);
162163

@@ -314,11 +315,15 @@ extern "C"
314315
jank_bool jank_truthy(jank_object_ref o);
315316
jank_bool jank_equal(jank_object_ref l, jank_object_ref r);
316317
jank_uhash jank_to_hash(jank_object_ref o);
318+
char const *jank_to_string(jank_object_ref o);
319+
char const *jank_to_code_string(jank_object_ref o);
317320
jank_i64 jank_to_integer(jank_object_ref o);
318321
jank_i64 jank_shift_mask_case_integer(jank_object_ref o, jank_i64 shift, jank_i64 mask);
319322

320323
void jank_set_meta(jank_object_ref o, jank_object_ref meta);
321324

325+
jank_object_ref jank_tag_pointer(void *ptr);
326+
322327
void jank_throw(jank_object_ref o);
323328
void jank_profile_enter(char const *label);
324329
void jank_profile_exit(char const *label);

compiler+runtime/src/cpp/jank/c_api.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,13 @@ extern "C"
546546
return make_box<obj::opaque_box>(o, type).erase().raw();
547547
}
548548

549+
char const *jank_box_type(jank_object_ref const o)
550+
{
551+
object_ref const box_obj(reinterpret_cast<object *>(o));
552+
auto const op_box{ try_object<obj::opaque_box>(box_obj) };
553+
return op_box->canonical_type.c_str();
554+
}
555+
549556
void *jank_unbox(char const * const type, jank_object_ref const o)
550557
{
551558
object_ref const box_obj(reinterpret_cast<object *>(o));
@@ -942,6 +949,18 @@ extern "C"
942949
return to_hash(o_obj);
943950
}
944951

952+
char const *jank_to_string(jank_object_ref const o)
953+
{
954+
object_ref const o_obj(reinterpret_cast<object *>(o));
955+
return o_obj.to_string().c_str();
956+
}
957+
958+
char const *jank_to_code_string(jank_object_ref const o)
959+
{
960+
object_ref const o_obj(reinterpret_cast<object *>(o));
961+
return o_obj.to_code_string().c_str();
962+
}
963+
945964
static i64 to_integer_or_hash(object_ref const o)
946965
{
947966
if(is_integer(o))
@@ -986,6 +1005,11 @@ extern "C"
9861005
runtime::reset_meta(o_obj, meta_obj);
9871006
}
9881007

1008+
jank_object_ref jank_tag_pointer(void * const ptr)
1009+
{
1010+
return runtime::object_ref{ runtime::detail::untagged(ptr) }.raw();
1011+
}
1012+
9891013
void jank_throw(jank_object_ref const o)
9901014
{
9911015
throw runtime::object_ref{ reinterpret_cast<object *>(o) };

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
root = ./.;
6666
fileset = lib.fileset.unions [
6767
./.clang-format
68+
./gdb
6869
./compiler+runtime
6970
];
7071
});

gdb/jank.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import re
2+
import gdb
3+
from itertools import count
4+
5+
def jank_address(oref):
6+
data_ptr = oref["data"].format_string(raw=True)
7+
return re.search("(0x[0-9a-fA-F]+)", data_ptr).group(1)
8+
9+
def to_py_string(val):
10+
address = jank_address(val)
11+
eval_result = gdb.parse_and_eval(f"jank_to_code_string((jank_object_ref){address})")
12+
match = re.search("0x([0-9a-fA-F]{12}) (.*)", str(eval_result))
13+
edn_str = match.group(2)
14+
return edn_str[1:-1].replace('\\"', '"')
15+
16+
def jank_truthy(val):
17+
address = jank_address(val)
18+
return int(gdb.parse_and_eval(f"jank_truthy((jank_object_ref){address})")) != 0
19+
20+
def jank_call(var_name, val):
21+
address = jank_address(val)
22+
resolve_var = f'jank_eval(jank_read_string_c("{var_name}"))'
23+
jank_call1 = f'jank_call1({resolve_var}, (jank_object_ref){address})'
24+
return gdb.parse_and_eval(f'(jank::runtime::object_ref){{(jank::runtime::object*){jank_call1}}}')
25+
26+
def jank_pr_str(val):
27+
try:
28+
return to_py_string(jank_call("pr-str", val))
29+
except Exception as e:
30+
return str(repr(e))
31+
32+
def jank_type(val):
33+
try:
34+
return to_py_string(jank_call("type", val))
35+
except Exception as e:
36+
return str(repr(e))
37+
38+
def jank_first(val):
39+
return jank_call("first", val)
40+
41+
def jank_second(val):
42+
return jank_call("second", val)
43+
44+
def jank_rest(val):
45+
return jank_call("rest", val)
46+
47+
def jank_seq(val):
48+
return jank_call("seq", val)
49+
50+
def jank_map(val):
51+
return jank_truthy(jank_call("map?", val))
52+
53+
def jank_coll(val):
54+
return jank_truthy(jank_call("coll?", val))
55+
56+
def jank_seqable(val):
57+
return jank_truthy(jank_call("seqable?", val))
58+
59+
class jank_print_item:
60+
def __init__(self, val):
61+
self.val = val
62+
def to_string(self):
63+
try:
64+
return jank_pr_str(self.val)
65+
except Exception as e:
66+
return str(repr(e))
67+
68+
class jank_print_map:
69+
def __init__(self, val):
70+
self.val = val
71+
def to_string(self):
72+
return jank_pr_str(self.val)
73+
def children(self):
74+
try:
75+
head = jank_seq(self.val)
76+
for i in count():
77+
if not jank_truthy(jank_seq(head)):
78+
break
79+
else:
80+
entry = jank_first(head)
81+
yield jank_pr_str(jank_first(entry)), jank_second(entry)
82+
head = jank_rest(head)
83+
except Exception as e:
84+
yield str(repr(e))
85+
86+
class jank_print_seq:
87+
def __init__(self, val):
88+
self.val = val
89+
def to_string(self):
90+
return jank_pr_str(self.val)
91+
def children(self):
92+
try:
93+
head = self.val
94+
for i in count():
95+
if not jank_truthy(jank_seq(head)):
96+
break
97+
else:
98+
yield str(i), jank_first(head)
99+
head = jank_rest(head)
100+
except Exception as e:
101+
yield str(repr(e))
102+
103+
def jank_box_type(val):
104+
address = jank_address(val)
105+
canonical_type = gdb.parse_and_eval(f"jank_box_type((jank_object_ref){address})")
106+
match = re.search("0x([0-9a-fA-F]{12}) (.*)", str(canonical_type))
107+
return match.group(2)[1:-1].replace('\\"', '"').rstrip()
108+
109+
def gdb_lookup_type(type_name):
110+
if type_name.endswith('*'):
111+
return gdb.lookup_type(type_name[:-1].rstrip()).pointer()
112+
else:
113+
return gdb.lookup_type(type_name).pointer()
114+
115+
class jank_print_opaque_box:
116+
def __init__(self, val):
117+
self.val = val
118+
def to_string(self):
119+
return jank_pr_str(self.val)
120+
def children(self):
121+
try:
122+
address = jank_address(self.val)
123+
canonical_type = jank_box_type(self.val)
124+
value = gdb.parse_and_eval(f'jank_unbox("{canonical_type}", (jank_object_ref){address})')
125+
yield "data", value.cast(gdb_lookup_type(canonical_type)).dereference()
126+
except Exception as e:
127+
yield str(repr(e)), self.val["data"]
128+
129+
def jank_runtime_oref(val):
130+
try:
131+
t = jank_type(val)
132+
if t == "opaque_box":
133+
return jank_print_opaque_box(val)
134+
if jank_map(val):
135+
return jank_print_map(val)
136+
if jank_seqable(val):
137+
return jank_print_seq(val)
138+
else:
139+
return jank_print_item(val)
140+
except Exception as e:
141+
return str(repr(e))
142+
143+
def get_type_name(val):
144+
return str(val.type.unqualified().strip_typedefs())
145+
146+
def jank_pretty_print(val):
147+
type_name = get_type_name(val)
148+
if type_name.startswith("jank::runtime::oref"):
149+
return jank_runtime_oref(val)
150+
151+
gdb.pretty_printers.append(jank_pretty_print)
152+
153+
# We could filter these, if we want, but they're quite helpful.
154+
jank_internal_frames = (
155+
# "jank::analyze",
156+
# "jank::evaluate",
157+
# "jank::runtime::apply_to",
158+
# "jank::runtime::dynamic_call",
159+
# "jank::runtime::obj",
160+
# "jank::runtime::oref",
161+
# "jank::runtime::visit_seqable",
162+
)
163+
164+
class JankFrameFilter:
165+
def __init__(self):
166+
self.name = "jank"
167+
self.priority = 100
168+
self.enabled = True
169+
gdb.frame_filters[self.name] = self
170+
def filter(self, frame_iter):
171+
for f in frame_iter:
172+
frame = f.inferior_frame()
173+
name = frame.name() if frame else None
174+
if name and name.startswith(jank_internal_frames):
175+
continue
176+
yield JankFrameDecorator(f)
177+
178+
class JankFrameDecorator(gdb.FrameDecorator.FrameDecorator):
179+
def __init__(self, fobj):
180+
super().__init__(fobj)
181+
182+
def filename(self):
183+
frame = self.inferior_frame()
184+
if frame:
185+
sal = frame.find_sal()
186+
if sal and sal.symtab:
187+
return sal.symtab.fullname()
188+
return super().filename()
189+
190+
def frame_locals(self):
191+
orig = self.inferior_frame()
192+
if orig is None:
193+
return iter([])
194+
try:
195+
block = orig.block()
196+
except RuntimeError:
197+
return iter([])
198+
199+
return ((sym, sym.value(orig)) for sym in block
200+
if sym.is_variable and not re.match(r'^v\d+$', sym.name))
201+
202+
203+
JankFrameFilter()

0 commit comments

Comments
 (0)