Move GDB scripts for fibers into folly
[folly.git] / folly / fibers / scripts / gdb.py
1 #!/usr/bin/env python3
2
3
4 import gdb
5 import gdb.printing
6 import gdb.types
7 import gdb.unwinder
8 import gdb.xmethod
9 import collections
10
11
12 class FiberPrinter:
13     """Print a folly::fibers::Fiber"""
14
15     def __init__(self, val):
16         self.val = val
17
18         state = self.val['state_']
19         d = gdb.types.make_enum_dict(state.type)
20         d = dict((v, k) for k, v in d.items())
21         self.state = d[int(state)]
22
23     def state_to_string(self):
24         if self.state == "folly::fibers::Fiber::INVALID":
25             return "Invalid"
26         if self.state == "folly::fibers::Fiber::NOT_STARTED":
27             return "Not started"
28         if self.state == "folly::fibers::Fiber::READY_TO_RUN":
29             return "Ready to run"
30         if self.state == "folly::fibers::Fiber::RUNNING":
31             return "Running"
32         if self.state == "folly::fibers::Fiber::AWAITING":
33             return "Awaiting"
34         if self.state == "folly::fibers::Fiber::AWAITING_IMMEDIATE":
35             return "Awaiting immediate"
36         if self.state == "folly::fibers::Fiber::YIELDED":
37             return "Yielded"
38         return "Unknown"
39
40     def backtrace_available(self):
41         return self.state != "folly::fibers::Fiber::INVALID" and \
42             self.state != "folly::fibers::Fiber::NOT_STARTED" and \
43             self.state != "folly::fibers::Fiber::RUNNING"
44
45     def children(self):
46         result = collections.OrderedDict()
47         result["state"] = self.state_to_string()
48         result["backtrace available"] = self.backtrace_available()
49         return result.items()
50
51     def to_string(self):
52         return "folly::fibers::Fiber"
53
54     def display_hint(self):
55         return "folly::fibers::Fiber"
56
57
58 class FiberManagerPrinter:
59     """Print a folly::fibers::Fiber"""
60
61     def __init__(self, fm):
62         self.fm = fm
63
64     def children(self):
65         all_fibers = \
66             self.fm['allFibers_']['data_']['root_plus_size_']['m_header']
67         fiber_hook = all_fibers['next_']
68
69         active_fibers = collections.OrderedDict()
70
71         while fiber_hook != all_fibers.address:
72             fiber = fiber_hook.cast(gdb.lookup_type("int64_t"))
73             fiber = fiber - gdb.parse_and_eval(
74                 "(int64_t)&folly::fibers::Fiber::globalListHook_")
75             fiber = fiber.cast(
76                 gdb.lookup_type('folly::fibers::Fiber').pointer()).dereference()
77
78             if FiberPrinter(fiber).state != "folly::fibers::Fiber::INVALID":
79                 active_fibers[str(fiber.address)] = fiber
80
81             fiber_hook = fiber_hook.dereference()['next_']
82
83         return active_fibers.items()
84
85     def to_string(self):
86         return "folly::fibers::FiberManager"
87
88     def display_hint(self):
89         return "folly::fibers::FiberManager"
90
91
92 class FrameId(object):
93     def __init__(self, sp, pc):
94         self.sp = sp
95         self.pc = pc
96
97
98 class FiberUnwinderFrameFilter:
99     instance = None
100
101     @classmethod
102     def set_skip_frame_sp(cls, skip_frame_sp):
103         if cls.instance is None:
104             cls.instance = FiberUnwinderFrameFilter()
105
106         cls.instance.skip_frame_sp = skip_frame_sp
107
108     def __init__(self):
109         self.name = "FiberUnwinderFrameFilter"
110         self.priority = 100
111         self.enabled = True
112         gdb.frame_filters[self.name] = self
113
114     def filter(self, frame_iter):
115         if not self.skip_frame_sp:
116             return frame_iter
117
118         return self.filter_impl(frame_iter)
119
120     def filter_impl(self, frame_iter):
121         for frame in frame_iter:
122             frame_sp = frame.inferior_frame().read_register("rsp")
123             if frame_sp == self.skip_frame_sp:
124                 continue
125             yield frame
126
127
128 class FiberUnwinder(gdb.unwinder.Unwinder):
129     instance = None
130
131     @classmethod
132     def set_fiber(cls, fiber):
133         if cls.instance is None:
134             cls.instance = FiberUnwinder()
135             gdb.unwinder.register_unwinder(None, cls.instance)
136
137         fiber_impl = fiber['fiberImpl_']
138         cls.instance.fiber_context_ptr = fiber_impl['fiberContext_']
139
140     def __init__(self):
141         super(FiberUnwinder, self).__init__("Fiber unwinder")
142         self.fiber_context_ptr = None
143
144     def __call__(self, pending_frame):
145         if not self.fiber_context_ptr:
146             return None
147
148         orig_sp = pending_frame.read_register('rsp')
149         orig_pc = pending_frame.read_register('rip')
150
151         void_star_star = gdb.lookup_type('uint64_t').pointer()
152         ptr = self.fiber_context_ptr.cast(void_star_star)
153
154         # This code may need to be adjusted to newer versions of boost::context.
155         #
156         # The easiest way to get these offsets is to first attach to any
157         # program which uses folly::fibers and add a break point in
158         # boost::context::jump_fcontext. You then need to save information about
159         # frame 1 via 'info frame 1' command.
160         #
161         # After that you need to resume program until fiber switch is complete
162         # and expore the contents of saved fiber context via
163         # 'x/16gx {fiber pointer}->fiberImpl_.fiberContext_' command.
164         # You then need to match those to the following values you've previously
165         # observed in the output of 'info frame 1'  command.
166         #
167         # Value found at "rbp at X" of 'info frame 1' output:
168         rbp = (ptr + 6).dereference()
169         # Value found at "rip = X" of 'info frame 1' output:
170         rip = (ptr + 7).dereference()
171         # Value found at "caller of frame at X" of 'info frame 1' output:
172         rsp = rbp - 96
173
174         frame_id = FrameId(rsp, orig_pc)
175         unwind_info = pending_frame.create_unwind_info(frame_id)
176         unwind_info.add_saved_register('rbp', rbp)
177         unwind_info.add_saved_register('rsp', rsp)
178         unwind_info.add_saved_register('rip', rip)
179
180         self.fiber_context_ptr = None
181
182         FiberUnwinderFrameFilter.set_skip_frame_sp(orig_sp)
183
184         return unwind_info
185
186
187 def fiber_activate(fiber_ptr):
188     fiber_type = gdb.lookup_type("folly::fibers::Fiber")
189     fiber = fiber_ptr.cast(fiber_type.pointer()).dereference()
190     if not FiberPrinter(fiber).backtrace_available():
191         return "Can not activate a non-waiting fiber."
192     FiberUnwinder.set_fiber(fiber)
193     return "Fiber " + str(fiber_ptr) + " activated. You can call 'bt' now."
194
195
196 def fiber_deactivate():
197     FiberUnwinderFrameFilter.set_skip_frame_sp(None)
198     gdb.invalidate_cached_frames()
199     return "Fiber de-activated."
200
201
202 class FiberActivateCommand(gdb.Command):
203     def __init__(self):
204         super(FiberActivateCommand, self).__init__("fiber", gdb.COMMAND_USER)
205
206     def invoke(self, arg, from_tty):
207         if not arg:
208             print("folly::fibers::Fiber* has to be passed to 'fiber' command")
209             return
210         fiber_ptr = gdb.parse_and_eval(arg)
211         print(fiber_activate(fiber_ptr))
212
213
214 class FiberDeactivateCommand(gdb.Command):
215     def __init__(self):
216         super(FiberDeactivateCommand, self).__init__(
217             "fiber-deactivate", gdb.COMMAND_USER)
218
219     def invoke(self, arg, from_tty):
220         print(fiber_deactivate())
221
222
223 class FiberXMethodWorker(gdb.xmethod.XMethodWorker):
224     def get_arg_types(self):
225         return None
226
227     def get_result_type(self):
228         return None
229
230     def __call__(self, *args):
231         return fiber_activate(args[0])
232
233
234 class FiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
235     def __init__(self):
236         super(FiberXMethodMatcher, self).__init__("Fiber method matcher")
237         self.worker = FiberXMethodWorker()
238
239     def match(self, class_type, method_name):
240         if class_type.name == "folly::fibers::Fiber" and \
241            method_name == "activate":
242             return self.worker
243         return None
244
245
246 class Shortcut(gdb.Function):
247     def __init__(self, function_name, value_lambda):
248         super(Shortcut, self).__init__(function_name)
249         self.value_lambda = value_lambda
250
251     def invoke(self):
252         return self.value_lambda()
253
254
255 def get_fiber_manager_map(evb_type):
256     global_cache_type = gdb.lookup_type(
257         "folly::fibers::(anonymous namespace)::GlobalCache<" + evb_type + ">")
258     global_cache_instance_ptr_ptr = gdb.parse_and_eval(
259         "&'" + global_cache_type.name + "::instance()::ret'")
260     global_cache_instance = global_cache_instance_ptr_ptr.cast(
261         global_cache_type.pointer().pointer()).dereference().dereference()
262     return global_cache_instance['map_']
263
264
265 def get_fiber_manager_map_evb():
266     return get_fiber_manager_map("folly::EventBase")
267
268
269 def get_fiber_manager_map_vevb():
270     return get_fiber_manager_map("folly::VirtualEventBase")
271
272
273 def build_pretty_printer():
274     pp = gdb.printing.RegexpCollectionPrettyPrinter("folly_fibers")
275     pp.add_printer('fibers::Fiber', '^folly::fibers::Fiber$', FiberPrinter)
276     pp.add_printer('fibers::FiberManager', '^folly::fibers::FiberManager$',
277                    FiberManagerPrinter)
278     return pp
279
280
281 def load():
282     gdb.printing.register_pretty_printer(gdb, build_pretty_printer())
283     gdb.xmethod.register_xmethod_matcher(gdb, FiberXMethodMatcher())
284     FiberActivateCommand()
285     FiberDeactivateCommand()
286     Shortcut("get_fiber_manager_map_evb", get_fiber_manager_map_evb)
287     Shortcut("get_fiber_manager_map_vevb", get_fiber_manager_map_vevb)
288
289
290 def info():
291     return "Pretty printers for folly::fibers"