dcb17e21f370301890c99b448d8bb746b5e5544b
[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     fiber_print_limit = 100
62
63     def __init__(self, fm):
64         self.fm = fm
65
66     def children(self):
67         all_fibers = \
68             self.fm['allFibers_']['data_']['root_plus_size_']['m_header']
69         fiber_hook = all_fibers['next_']
70
71         active_fibers = collections.OrderedDict()
72
73         fiber_count = 0
74
75         while fiber_hook != all_fibers.address:
76             if fiber_count == FiberManagerPrinter.fiber_print_limit:
77                 active_fibers["..."] = "..."
78                 break
79
80             fiber = fiber_hook.cast(gdb.lookup_type("int64_t"))
81             fiber = fiber - gdb.parse_and_eval(
82                 "(int64_t)&'folly::fibers::Fiber'::globalListHook_")
83             fiber = fiber.cast(
84                 gdb.lookup_type('folly::fibers::Fiber').pointer()).dereference()
85
86             if FiberPrinter(fiber).state != "folly::fibers::Fiber::INVALID":
87                 active_fibers[str(fiber.address)] = fiber
88
89             fiber_hook = fiber_hook.dereference()['next_']
90
91             fiber_count = fiber_count + 1
92
93         return active_fibers.items()
94
95     def to_string(self):
96         return "folly::fibers::FiberManager"
97
98     def display_hint(self):
99         return "folly::fibers::FiberManager"
100
101
102 class FiberPrintLimitCommand(gdb.Command):
103     def __init__(self):
104         super(FiberPrintLimitCommand, self).__init__(
105             "fiber-print-limit", gdb.COMMAND_USER)
106
107     def invoke(self, arg, from_tty):
108         if not arg:
109             print("New limit has to be passed to 'fiber_print_limit' command")
110             return
111         FiberManagerPrinter.fiber_print_limit = int(arg)
112         print("New fiber limit for FiberManager printer set to " +
113               str(FiberManagerPrinter.fiber_print_limit))
114
115
116 class FrameId(object):
117     def __init__(self, sp, pc):
118         self.sp = sp
119         self.pc = pc
120
121
122 class FiberUnwinderFrameFilter:
123     instance = None
124
125     @classmethod
126     def set_skip_frame_sp(cls, skip_frame_sp):
127         if cls.instance is None:
128             cls.instance = FiberUnwinderFrameFilter()
129
130         cls.instance.skip_frame_sp = skip_frame_sp
131
132     def __init__(self):
133         self.name = "FiberUnwinderFrameFilter"
134         self.priority = 100
135         self.enabled = True
136         gdb.frame_filters[self.name] = self
137
138     def filter(self, frame_iter):
139         if not self.skip_frame_sp:
140             return frame_iter
141
142         return self.filter_impl(frame_iter)
143
144     def filter_impl(self, frame_iter):
145         for frame in frame_iter:
146             frame_sp = frame.inferior_frame().read_register("rsp")
147             if frame_sp == self.skip_frame_sp:
148                 continue
149             yield frame
150
151
152 class FiberUnwinder(gdb.unwinder.Unwinder):
153     instance = None
154
155     @classmethod
156     def set_fiber(cls, fiber):
157         if cls.instance is None:
158             cls.instance = FiberUnwinder()
159             gdb.unwinder.register_unwinder(None, cls.instance)
160
161         fiber_impl = fiber['fiberImpl_']
162         cls.instance.fiber_context_ptr = fiber_impl['fiberContext_']
163
164     def __init__(self):
165         super(FiberUnwinder, self).__init__("Fiber unwinder")
166         self.fiber_context_ptr = None
167
168     def __call__(self, pending_frame):
169         if not self.fiber_context_ptr:
170             return None
171
172         orig_sp = pending_frame.read_register('rsp')
173         orig_pc = pending_frame.read_register('rip')
174
175         void_star_star = gdb.lookup_type('uint64_t').pointer()
176         ptr = self.fiber_context_ptr.cast(void_star_star)
177
178         # This code may need to be adjusted to newer versions of boost::context.
179         #
180         # The easiest way to get these offsets is to first attach to any
181         # program which uses folly::fibers and add a break point in
182         # boost::context::jump_fcontext. You then need to save information about
183         # frame 1 via 'info frame 1' command.
184         #
185         # After that you need to resume program until fiber switch is complete
186         # and expore the contents of saved fiber context via
187         # 'x/16gx {fiber pointer}->fiberImpl_.fiberContext_' command.
188         # You then need to match those to the following values you've previously
189         # observed in the output of 'info frame 1'  command.
190         #
191         # Value found at "rbp at X" of 'info frame 1' output:
192         rbp = (ptr + 6).dereference()
193         # Value found at "rip = X" of 'info frame 1' output:
194         rip = (ptr + 7).dereference()
195         # Value found at "caller of frame at X" of 'info frame 1' output:
196         rsp = rbp - 96
197
198         frame_id = FrameId(rsp, orig_pc)
199         unwind_info = pending_frame.create_unwind_info(frame_id)
200         unwind_info.add_saved_register('rbp', rbp)
201         unwind_info.add_saved_register('rsp', rsp)
202         unwind_info.add_saved_register('rip', rip)
203
204         self.fiber_context_ptr = None
205
206         FiberUnwinderFrameFilter.set_skip_frame_sp(orig_sp)
207
208         return unwind_info
209
210
211 def fiber_activate(fiber_ptr):
212     fiber_type = gdb.lookup_type("folly::fibers::Fiber")
213     fiber = fiber_ptr.cast(fiber_type.pointer()).dereference()
214     if not FiberPrinter(fiber).backtrace_available():
215         return "Can not activate a non-waiting fiber."
216     FiberUnwinder.set_fiber(fiber)
217     return "Fiber " + str(fiber_ptr) + " activated. You can call 'bt' now."
218
219
220 def fiber_deactivate():
221     FiberUnwinderFrameFilter.set_skip_frame_sp(None)
222     gdb.invalidate_cached_frames()
223     return "Fiber de-activated."
224
225
226 class FiberActivateCommand(gdb.Command):
227     def __init__(self):
228         super(FiberActivateCommand, self).__init__("fiber", gdb.COMMAND_USER)
229
230     def invoke(self, arg, from_tty):
231         if not arg:
232             print("folly::fibers::Fiber* has to be passed to 'fiber' command")
233             return
234         fiber_ptr = gdb.parse_and_eval(arg)
235         print(fiber_activate(fiber_ptr))
236
237
238 class FiberDeactivateCommand(gdb.Command):
239     def __init__(self):
240         super(FiberDeactivateCommand, self).__init__(
241             "fiber-deactivate", gdb.COMMAND_USER)
242
243     def invoke(self, arg, from_tty):
244         print(fiber_deactivate())
245
246
247 class FiberXMethodWorker(gdb.xmethod.XMethodWorker):
248     def get_arg_types(self):
249         return None
250
251     def get_result_type(self):
252         return None
253
254     def __call__(self, *args):
255         return fiber_activate(args[0])
256
257
258 class FiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
259     def __init__(self):
260         super(FiberXMethodMatcher, self).__init__("Fiber method matcher")
261         self.worker = FiberXMethodWorker()
262
263     def match(self, class_type, method_name):
264         if class_type.name == "folly::fibers::Fiber" and \
265            method_name == "activate":
266             return self.worker
267         return None
268
269
270 class Shortcut(gdb.Function):
271     def __init__(self, function_name, value_lambda):
272         super(Shortcut, self).__init__(function_name)
273         self.value_lambda = value_lambda
274
275     def invoke(self):
276         return self.value_lambda()
277
278
279 def get_fiber_manager_map(evb_type):
280     global_cache_type = gdb.lookup_type(
281         "folly::fibers::(anonymous namespace)::GlobalCache<" + evb_type + ">")
282     global_cache_instance_ptr_ptr = gdb.parse_and_eval(
283         "&'" + global_cache_type.name + "::instance()::ret'")
284     global_cache_instance = global_cache_instance_ptr_ptr.cast(
285         global_cache_type.pointer().pointer()).dereference().dereference()
286     return global_cache_instance['map_']
287
288
289 def get_fiber_manager_map_evb():
290     return get_fiber_manager_map("folly::EventBase")
291
292
293 def get_fiber_manager_map_vevb():
294     return get_fiber_manager_map("folly::VirtualEventBase")
295
296
297 def build_pretty_printer():
298     pp = gdb.printing.RegexpCollectionPrettyPrinter("folly_fibers")
299     pp.add_printer('fibers::Fiber', '^folly::fibers::Fiber$', FiberPrinter)
300     pp.add_printer('fibers::FiberManager', '^folly::fibers::FiberManager$',
301                    FiberManagerPrinter)
302     return pp
303
304
305 def load():
306     gdb.printing.register_pretty_printer(gdb, build_pretty_printer())
307     gdb.xmethod.register_xmethod_matcher(gdb, FiberXMethodMatcher())
308     FiberPrintLimitCommand()
309     FiberActivateCommand()
310     FiberDeactivateCommand()
311     Shortcut("get_fiber_manager_map_evb", get_fiber_manager_map_evb)
312     Shortcut("get_fiber_manager_map_vevb", get_fiber_manager_map_vevb)
313
314
315 def info():
316     return "Pretty printers for folly::fibers"