f13e6fb5194e426671ce119655aff2460724f0e1
[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 import itertools
11
12
13 class FiberPrinter:
14     """Print a folly::fibers::Fiber"""
15
16     def __init__(self, val):
17         self.val = val
18
19         state = self.val['state_']
20         d = gdb.types.make_enum_dict(state.type)
21         d = dict((v, k) for k, v in d.items())
22         self.state = d[int(state)]
23
24     def state_to_string(self):
25         if self.state == "folly::fibers::Fiber::INVALID":
26             return "Invalid"
27         if self.state == "folly::fibers::Fiber::NOT_STARTED":
28             return "Not started"
29         if self.state == "folly::fibers::Fiber::READY_TO_RUN":
30             return "Ready to run"
31         if self.state == "folly::fibers::Fiber::RUNNING":
32             return "Running"
33         if self.state == "folly::fibers::Fiber::AWAITING":
34             return "Awaiting"
35         if self.state == "folly::fibers::Fiber::AWAITING_IMMEDIATE":
36             return "Awaiting immediate"
37         if self.state == "folly::fibers::Fiber::YIELDED":
38             return "Yielded"
39         return "Unknown"
40
41     def backtrace_available(self):
42         return self.state != "folly::fibers::Fiber::INVALID" and \
43             self.state != "folly::fibers::Fiber::NOT_STARTED" and \
44             self.state != "folly::fibers::Fiber::RUNNING"
45
46     def children(self):
47         result = collections.OrderedDict()
48         result["state"] = self.state_to_string()
49         result["backtrace available"] = self.backtrace_available()
50         return result.items()
51
52     def to_string(self):
53         return "folly::fibers::Fiber"
54
55     def display_hint(self):
56         return "folly::fibers::Fiber"
57
58
59 class GetFiberXMethodWorker(gdb.xmethod.XMethodWorker):
60     def get_arg_types(self):
61         return gdb.lookup_type('int')
62
63     def get_result_type(self):
64         return gdb.lookup_type('int')
65
66     def __call__(self, *args):
67         fm = args[0]
68         index = int(args[1])
69         fiber = next(itertools.islice(fiber_manager_active_fibers(fm),
70                                       index,
71                                       None))
72         if fiber is None:
73             raise gdb.GdbError("Index out of range")
74         else:
75             return fiber
76
77
78 class GetFiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
79     def __init__(self):
80         super(GetFiberXMethodMatcher, self).__init__(
81             "Fiber address method matcher")
82         self.worker = GetFiberXMethodWorker()
83
84     def match(self, class_type, method_name):
85         if class_type.name == "folly::fibers::FiberManager" and \
86            method_name == "get_fiber":
87             return self.worker
88         return None
89
90
91 def fiber_manager_active_fibers(fm):
92     all_fibers = \
93         fm['allFibers_']['data_']['root_plus_size_']['m_header']
94     fiber_hook = all_fibers['next_']
95
96     fiber_count = 0
97
98     while fiber_hook != all_fibers.address:
99         fiber = fiber_hook.cast(gdb.lookup_type("int64_t"))
100         fiber = fiber - gdb.parse_and_eval(
101             "(int64_t)&'folly::fibers::Fiber'::globalListHook_")
102         fiber = fiber.cast(
103             gdb.lookup_type('folly::fibers::Fiber').pointer()).dereference()
104
105         if FiberPrinter(fiber).state != "folly::fibers::Fiber::INVALID":
106             yield fiber
107
108         fiber_hook = fiber_hook.dereference()['next_']
109
110         fiber_count = fiber_count + 1
111
112
113 class FiberManagerPrinter:
114     """Print a folly::fibers::Fiber"""
115
116     fiber_print_limit = 100
117
118     def __init__(self, fm):
119         self.fm = fm
120
121     def children(self):
122         def limit_with_dots(fibers_iterator):
123             num_items = 0
124             for fiber in fibers_iterator:
125                 if num_items >= self.fiber_print_limit:
126                     yield ('...', '...')
127                     return
128
129                 yield (str(fiber.address), fiber)
130                 num_items += 1
131
132         return limit_with_dots(fiber_manager_active_fibers(self.fm))
133
134     def to_string(self):
135         return "folly::fibers::FiberManager"
136
137     def display_hint(self):
138         return "folly::fibers::FiberManager"
139
140
141 class FiberPrintLimitCommand(gdb.Command):
142     def __init__(self):
143         super(FiberPrintLimitCommand, self).__init__(
144             "fiber-print-limit", gdb.COMMAND_USER)
145
146     def invoke(self, arg, from_tty):
147         if not arg:
148             print("New limit has to be passed to 'fiber_print_limit' command")
149             return
150         FiberManagerPrinter.fiber_print_limit = int(arg)
151         print("New fiber limit for FiberManager printer set to " +
152               str(FiberManagerPrinter.fiber_print_limit))
153
154
155 class FrameId(object):
156     def __init__(self, sp, pc):
157         self.sp = sp
158         self.pc = pc
159
160
161 class FiberUnwinderFrameFilter:
162     instance = None
163
164     @classmethod
165     def set_skip_frame_sp(cls, skip_frame_sp):
166         if cls.instance is None:
167             cls.instance = FiberUnwinderFrameFilter()
168
169         cls.instance.skip_frame_sp = skip_frame_sp
170
171     def __init__(self):
172         self.name = "FiberUnwinderFrameFilter"
173         self.priority = 100
174         self.enabled = True
175         gdb.frame_filters[self.name] = self
176
177     def filter(self, frame_iter):
178         if not self.skip_frame_sp:
179             return frame_iter
180
181         return self.filter_impl(frame_iter)
182
183     def filter_impl(self, frame_iter):
184         for frame in frame_iter:
185             frame_sp = frame.inferior_frame().read_register("rsp")
186             if frame_sp == self.skip_frame_sp:
187                 continue
188             yield frame
189
190
191 class FiberUnwinder(gdb.unwinder.Unwinder):
192     instance = None
193
194     @classmethod
195     def set_fiber(cls, fiber):
196         if cls.instance is None:
197             cls.instance = FiberUnwinder()
198             gdb.unwinder.register_unwinder(None, cls.instance)
199
200         fiber_impl = fiber['fiberImpl_']
201         cls.instance.fiber_context_ptr = fiber_impl['fiberContext_']
202
203     def __init__(self):
204         super(FiberUnwinder, self).__init__("Fiber unwinder")
205         self.fiber_context_ptr = None
206
207     def __call__(self, pending_frame):
208         if not self.fiber_context_ptr:
209             return None
210
211         orig_sp = pending_frame.read_register('rsp')
212         orig_pc = pending_frame.read_register('rip')
213
214         void_star_star = gdb.lookup_type('uint64_t').pointer()
215         ptr = self.fiber_context_ptr.cast(void_star_star)
216
217         # This code may need to be adjusted to newer versions of boost::context.
218         #
219         # The easiest way to get these offsets is to first attach to any
220         # program which uses folly::fibers and add a break point in
221         # boost::context::jump_fcontext. You then need to save information about
222         # frame 1 via 'info frame 1' command.
223         #
224         # After that you need to resume program until fiber switch is complete
225         # and expore the contents of saved fiber context via
226         # 'x/16gx {fiber pointer}->fiberImpl_.fiberContext_' command.
227         # You then need to match those to the following values you've previously
228         # observed in the output of 'info frame 1'  command.
229         #
230         # Value found at "rbp at X" of 'info frame 1' output:
231         rbp = (ptr + 6).dereference()
232         # Value found at "rip = X" of 'info frame 1' output:
233         rip = (ptr + 7).dereference()
234         # Value found at "caller of frame at X" of 'info frame 1' output:
235         rsp = rbp - 96
236
237         frame_id = FrameId(rsp, orig_pc)
238         unwind_info = pending_frame.create_unwind_info(frame_id)
239         unwind_info.add_saved_register('rbp', rbp)
240         unwind_info.add_saved_register('rsp', rsp)
241         unwind_info.add_saved_register('rip', rip)
242
243         self.fiber_context_ptr = None
244
245         FiberUnwinderFrameFilter.set_skip_frame_sp(orig_sp)
246
247         return unwind_info
248
249
250 def fiber_activate(fiber):
251     fiber_type = gdb.lookup_type("folly::fibers::Fiber")
252     if fiber.type != fiber_type:
253         fiber = fiber.cast(fiber_type.pointer()).dereference()
254     if not FiberPrinter(fiber).backtrace_available():
255         return "Can not activate a non-waiting fiber."
256     FiberUnwinder.set_fiber(fiber)
257     return "Fiber 0x{:12x} activated. You can call 'bt' now.".format(int(fiber.address))
258
259
260 def fiber_deactivate():
261     FiberUnwinderFrameFilter.set_skip_frame_sp(None)
262     gdb.invalidate_cached_frames()
263     return "Fiber de-activated."
264
265
266 class FiberActivateCommand(gdb.Command):
267     def __init__(self):
268         super(FiberActivateCommand, self).__init__("fiber", gdb.COMMAND_USER)
269
270     def invoke(self, arg, from_tty):
271         if not arg:
272             print("folly::fibers::Fiber* has to be passed to 'fiber' command")
273             return
274         fiber_ptr = gdb.parse_and_eval(arg)
275         print(fiber_activate(fiber_ptr))
276
277
278 class FiberDeactivateCommand(gdb.Command):
279     def __init__(self):
280         super(FiberDeactivateCommand, self).__init__(
281             "fiber-deactivate", gdb.COMMAND_USER)
282
283     def invoke(self, arg, from_tty):
284         print(fiber_deactivate())
285
286
287 class FiberXMethodWorker(gdb.xmethod.XMethodWorker):
288     def get_arg_types(self):
289         return None
290
291     def get_result_type(self):
292         return None
293
294     def __call__(self, *args):
295         return fiber_activate(args[0])
296
297
298 class FiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
299     def __init__(self):
300         super(FiberXMethodMatcher, self).__init__("Fiber method matcher")
301         self.worker = FiberXMethodWorker()
302
303     def match(self, class_type, method_name):
304         if class_type.name == "folly::fibers::Fiber" and \
305            method_name == "activate":
306             return self.worker
307         return None
308
309
310 class Shortcut(gdb.Function):
311     def __init__(self, function_name, value_lambda):
312         super(Shortcut, self).__init__(function_name)
313         self.value_lambda = value_lambda
314
315     def invoke(self):
316         return self.value_lambda()
317
318
319 def get_fiber_manager_map(evb_type):
320     try:
321         # Exception thrown if unable to find type
322         # Probably because of missing debug symbols
323         global_cache_type = gdb.lookup_type(
324             "folly::fibers::(anonymous namespace)::GlobalCache<" + evb_type + ">")
325     except gdb.error:
326         raise gdb.GdbError("Unable to find types. "
327                            "Please make sure debug info is available for this binary.\n"
328                            "Have you run 'fbload debuginfo_fbpkg'?")
329
330     global_cache_instance_ptr_ptr = gdb.parse_and_eval(
331         "&'" + global_cache_type.name + "::instance()::ret'")
332     global_cache_instance_ptr = global_cache_instance_ptr_ptr.cast(
333         global_cache_type.pointer().pointer()).dereference()
334     if global_cache_instance_ptr == 0x0:
335         raise gdb.GdbError("FiberManager map is empty.")
336
337     global_cache_instance = global_cache_instance_ptr.dereference()
338     return global_cache_instance['map_']
339
340
341 def get_fiber_manager_map_evb():
342     return get_fiber_manager_map("folly::EventBase")
343
344
345 def get_fiber_manager_map_vevb():
346     return get_fiber_manager_map("folly::VirtualEventBase")
347
348
349 def build_pretty_printer():
350     pp = gdb.printing.RegexpCollectionPrettyPrinter("folly_fibers")
351     pp.add_printer('fibers::Fiber', '^folly::fibers::Fiber$', FiberPrinter)
352     pp.add_printer('fibers::FiberManager', '^folly::fibers::FiberManager$',
353                    FiberManagerPrinter)
354     return pp
355
356
357 def load():
358     gdb.printing.register_pretty_printer(gdb, build_pretty_printer())
359     gdb.xmethod.register_xmethod_matcher(gdb, FiberXMethodMatcher())
360     gdb.xmethod.register_xmethod_matcher(gdb, GetFiberXMethodMatcher())
361     FiberPrintLimitCommand()
362     FiberActivateCommand()
363     FiberDeactivateCommand()
364     Shortcut("get_fiber_manager_map_evb", get_fiber_manager_map_evb)
365     Shortcut("get_fiber_manager_map_vevb", get_fiber_manager_map_vevb)
366
367
368 def info():
369     return "Pretty printers for folly::fibers"