Move GDB scripts for fibers into folly
authorAndrii Grynenko <andrii@fb.com>
Tue, 13 Dec 2016 21:57:32 +0000 (13:57 -0800)
committerFacebook Github Bot <facebook-github-bot-bot@fb.com>
Tue, 13 Dec 2016 22:03:21 +0000 (14:03 -0800)
Reviewed By: igorsugak

Differential Revision: D4320762

fbshipit-source-id: 2028de6ccc6fb129381b03d694b88677a5aa50d6

folly/fibers/scripts/gdb.py [new file with mode: 0644]
folly/fibers/scripts/utils.gdb [deleted file]

diff --git a/folly/fibers/scripts/gdb.py b/folly/fibers/scripts/gdb.py
new file mode 100644 (file)
index 0000000..1f5e9f9
--- /dev/null
@@ -0,0 +1,291 @@
+#!/usr/bin/env python3
+
+
+import gdb
+import gdb.printing
+import gdb.types
+import gdb.unwinder
+import gdb.xmethod
+import collections
+
+
+class FiberPrinter:
+    """Print a folly::fibers::Fiber"""
+
+    def __init__(self, val):
+        self.val = val
+
+        state = self.val['state_']
+        d = gdb.types.make_enum_dict(state.type)
+        d = dict((v, k) for k, v in d.items())
+        self.state = d[int(state)]
+
+    def state_to_string(self):
+        if self.state == "folly::fibers::Fiber::INVALID":
+            return "Invalid"
+        if self.state == "folly::fibers::Fiber::NOT_STARTED":
+            return "Not started"
+        if self.state == "folly::fibers::Fiber::READY_TO_RUN":
+            return "Ready to run"
+        if self.state == "folly::fibers::Fiber::RUNNING":
+            return "Running"
+        if self.state == "folly::fibers::Fiber::AWAITING":
+            return "Awaiting"
+        if self.state == "folly::fibers::Fiber::AWAITING_IMMEDIATE":
+            return "Awaiting immediate"
+        if self.state == "folly::fibers::Fiber::YIELDED":
+            return "Yielded"
+        return "Unknown"
+
+    def backtrace_available(self):
+        return self.state != "folly::fibers::Fiber::INVALID" and \
+            self.state != "folly::fibers::Fiber::NOT_STARTED" and \
+            self.state != "folly::fibers::Fiber::RUNNING"
+
+    def children(self):
+        result = collections.OrderedDict()
+        result["state"] = self.state_to_string()
+        result["backtrace available"] = self.backtrace_available()
+        return result.items()
+
+    def to_string(self):
+        return "folly::fibers::Fiber"
+
+    def display_hint(self):
+        return "folly::fibers::Fiber"
+
+
+class FiberManagerPrinter:
+    """Print a folly::fibers::Fiber"""
+
+    def __init__(self, fm):
+        self.fm = fm
+
+    def children(self):
+        all_fibers = \
+            self.fm['allFibers_']['data_']['root_plus_size_']['m_header']
+        fiber_hook = all_fibers['next_']
+
+        active_fibers = collections.OrderedDict()
+
+        while fiber_hook != all_fibers.address:
+            fiber = fiber_hook.cast(gdb.lookup_type("int64_t"))
+            fiber = fiber - gdb.parse_and_eval(
+                "(int64_t)&folly::fibers::Fiber::globalListHook_")
+            fiber = fiber.cast(
+                gdb.lookup_type('folly::fibers::Fiber').pointer()).dereference()
+
+            if FiberPrinter(fiber).state != "folly::fibers::Fiber::INVALID":
+                active_fibers[str(fiber.address)] = fiber
+
+            fiber_hook = fiber_hook.dereference()['next_']
+
+        return active_fibers.items()
+
+    def to_string(self):
+        return "folly::fibers::FiberManager"
+
+    def display_hint(self):
+        return "folly::fibers::FiberManager"
+
+
+class FrameId(object):
+    def __init__(self, sp, pc):
+        self.sp = sp
+        self.pc = pc
+
+
+class FiberUnwinderFrameFilter:
+    instance = None
+
+    @classmethod
+    def set_skip_frame_sp(cls, skip_frame_sp):
+        if cls.instance is None:
+            cls.instance = FiberUnwinderFrameFilter()
+
+        cls.instance.skip_frame_sp = skip_frame_sp
+
+    def __init__(self):
+        self.name = "FiberUnwinderFrameFilter"
+        self.priority = 100
+        self.enabled = True
+        gdb.frame_filters[self.name] = self
+
+    def filter(self, frame_iter):
+        if not self.skip_frame_sp:
+            return frame_iter
+
+        return self.filter_impl(frame_iter)
+
+    def filter_impl(self, frame_iter):
+        for frame in frame_iter:
+            frame_sp = frame.inferior_frame().read_register("rsp")
+            if frame_sp == self.skip_frame_sp:
+                continue
+            yield frame
+
+
+class FiberUnwinder(gdb.unwinder.Unwinder):
+    instance = None
+
+    @classmethod
+    def set_fiber(cls, fiber):
+        if cls.instance is None:
+            cls.instance = FiberUnwinder()
+            gdb.unwinder.register_unwinder(None, cls.instance)
+
+        fiber_impl = fiber['fiberImpl_']
+        cls.instance.fiber_context_ptr = fiber_impl['fiberContext_']
+
+    def __init__(self):
+        super(FiberUnwinder, self).__init__("Fiber unwinder")
+        self.fiber_context_ptr = None
+
+    def __call__(self, pending_frame):
+        if not self.fiber_context_ptr:
+            return None
+
+        orig_sp = pending_frame.read_register('rsp')
+        orig_pc = pending_frame.read_register('rip')
+
+        void_star_star = gdb.lookup_type('uint64_t').pointer()
+        ptr = self.fiber_context_ptr.cast(void_star_star)
+
+        # This code may need to be adjusted to newer versions of boost::context.
+        #
+        # The easiest way to get these offsets is to first attach to any
+        # program which uses folly::fibers and add a break point in
+        # boost::context::jump_fcontext. You then need to save information about
+        # frame 1 via 'info frame 1' command.
+        #
+        # After that you need to resume program until fiber switch is complete
+        # and expore the contents of saved fiber context via
+        # 'x/16gx {fiber pointer}->fiberImpl_.fiberContext_' command.
+        # You then need to match those to the following values you've previously
+        # observed in the output of 'info frame 1'  command.
+        #
+        # Value found at "rbp at X" of 'info frame 1' output:
+        rbp = (ptr + 6).dereference()
+        # Value found at "rip = X" of 'info frame 1' output:
+        rip = (ptr + 7).dereference()
+        # Value found at "caller of frame at X" of 'info frame 1' output:
+        rsp = rbp - 96
+
+        frame_id = FrameId(rsp, orig_pc)
+        unwind_info = pending_frame.create_unwind_info(frame_id)
+        unwind_info.add_saved_register('rbp', rbp)
+        unwind_info.add_saved_register('rsp', rsp)
+        unwind_info.add_saved_register('rip', rip)
+
+        self.fiber_context_ptr = None
+
+        FiberUnwinderFrameFilter.set_skip_frame_sp(orig_sp)
+
+        return unwind_info
+
+
+def fiber_activate(fiber_ptr):
+    fiber_type = gdb.lookup_type("folly::fibers::Fiber")
+    fiber = fiber_ptr.cast(fiber_type.pointer()).dereference()
+    if not FiberPrinter(fiber).backtrace_available():
+        return "Can not activate a non-waiting fiber."
+    FiberUnwinder.set_fiber(fiber)
+    return "Fiber " + str(fiber_ptr) + " activated. You can call 'bt' now."
+
+
+def fiber_deactivate():
+    FiberUnwinderFrameFilter.set_skip_frame_sp(None)
+    gdb.invalidate_cached_frames()
+    return "Fiber de-activated."
+
+
+class FiberActivateCommand(gdb.Command):
+    def __init__(self):
+        super(FiberActivateCommand, self).__init__("fiber", gdb.COMMAND_USER)
+
+    def invoke(self, arg, from_tty):
+        if not arg:
+            print("folly::fibers::Fiber* has to be passed to 'fiber' command")
+            return
+        fiber_ptr = gdb.parse_and_eval(arg)
+        print(fiber_activate(fiber_ptr))
+
+
+class FiberDeactivateCommand(gdb.Command):
+    def __init__(self):
+        super(FiberDeactivateCommand, self).__init__(
+            "fiber-deactivate", gdb.COMMAND_USER)
+
+    def invoke(self, arg, from_tty):
+        print(fiber_deactivate())
+
+
+class FiberXMethodWorker(gdb.xmethod.XMethodWorker):
+    def get_arg_types(self):
+        return None
+
+    def get_result_type(self):
+        return None
+
+    def __call__(self, *args):
+        return fiber_activate(args[0])
+
+
+class FiberXMethodMatcher(gdb.xmethod.XMethodMatcher):
+    def __init__(self):
+        super(FiberXMethodMatcher, self).__init__("Fiber method matcher")
+        self.worker = FiberXMethodWorker()
+
+    def match(self, class_type, method_name):
+        if class_type.name == "folly::fibers::Fiber" and \
+           method_name == "activate":
+            return self.worker
+        return None
+
+
+class Shortcut(gdb.Function):
+    def __init__(self, function_name, value_lambda):
+        super(Shortcut, self).__init__(function_name)
+        self.value_lambda = value_lambda
+
+    def invoke(self):
+        return self.value_lambda()
+
+
+def get_fiber_manager_map(evb_type):
+    global_cache_type = gdb.lookup_type(
+        "folly::fibers::(anonymous namespace)::GlobalCache<" + evb_type + ">")
+    global_cache_instance_ptr_ptr = gdb.parse_and_eval(
+        "&'" + global_cache_type.name + "::instance()::ret'")
+    global_cache_instance = global_cache_instance_ptr_ptr.cast(
+        global_cache_type.pointer().pointer()).dereference().dereference()
+    return global_cache_instance['map_']
+
+
+def get_fiber_manager_map_evb():
+    return get_fiber_manager_map("folly::EventBase")
+
+
+def get_fiber_manager_map_vevb():
+    return get_fiber_manager_map("folly::VirtualEventBase")
+
+
+def build_pretty_printer():
+    pp = gdb.printing.RegexpCollectionPrettyPrinter("folly_fibers")
+    pp.add_printer('fibers::Fiber', '^folly::fibers::Fiber$', FiberPrinter)
+    pp.add_printer('fibers::FiberManager', '^folly::fibers::FiberManager$',
+                   FiberManagerPrinter)
+    return pp
+
+
+def load():
+    gdb.printing.register_pretty_printer(gdb, build_pretty_printer())
+    gdb.xmethod.register_xmethod_matcher(gdb, FiberXMethodMatcher())
+    FiberActivateCommand()
+    FiberDeactivateCommand()
+    Shortcut("get_fiber_manager_map_evb", get_fiber_manager_map_evb)
+    Shortcut("get_fiber_manager_map_vevb", get_fiber_manager_map_vevb)
+
+
+def info():
+    return "Pretty printers for folly::fibers"
diff --git a/folly/fibers/scripts/utils.gdb b/folly/fibers/scripts/utils.gdb
deleted file mode 100644 (file)
index a3bd0c8..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-# Print given Fiber state
-# arg0 folly::fibers::Fiber*
-define print_folly_fiber_state
-  set $fiber = (folly::fibers::Fiber*)$arg0
-  if $fiber->state_ == folly::fibers::Fiber::INVALID
-    printf "Invalid"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::NOT_STARTED
-    printf "Not started"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::READY_TO_RUN
-    printf "Ready to run"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::RUNNING
-    printf "Running"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::AWAITING
-    printf "Awaiting"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::AWAITING_IMMEDIATE
-    printf "Awaiting immediate"
-  end
-  if $fiber->state_ == folly::fibers::Fiber::YIELDED
-    printf "Yielded"
-  end
-end
-
-# Print given Fiber
-# arg0 folly::fibers::Fiber*
-define print_folly_fiber
-  set $fiber = (folly::fibers::Fiber*)$arg0
-  printf "  (folly::fibers::Fiber*)%p\n\n", $fiber
-
-  printf "  State: "
-  print_folly_fiber_state $fiber
-  printf "\n"
-
-  if $fiber->state_ != folly::fibers::Fiber::INVALID && \
-     $fiber->state_ != folly::fibers::Fiber::NOT_STARTED && \
-     $fiber->state_ != folly::fibers::Fiber::RUNNING
-    printf "  Backtrace:\n"
-    set $frameptr = ((uint64_t*)$fiber->fiberImpl_.fiberContext_)[6]
-    set $k = 0
-    while $frameptr != 0
-      printf "    #%d at %p in ", $k, *((void**)($frameptr+8))
-      set $k = $k + 1
-      info symbol *((void**)($frameptr+8))
-      set $frameptr = *((void**)($frameptr))
-    end
-  end
-end
-
-# Print given FiberManager
-# arg0 folly::fibers::FiberManager*
-define print_folly_fiber_manager
-  set $fiberManager = (folly::fibers::FiberManager*)$arg0
-
-  printf "  (folly::fibers::FiberManager*)%p\n\n", $fiberManager
-  printf "  Fibers active: %d\n", $fiberManager->fibersActive_
-  printf "  Fibers allocated: %d\n", $fiberManager->fibersAllocated_
-  printf "  Fibers pool size: %d\n", $fiberManager->fibersPoolSize_
-  printf "  Active fiber: (folly::fibers::Fiber*)%p\n", \
-         $fiberManager->activeFiber_
-  printf "  Current fiber: (folly::fibers::Fiber*)%p\n", \
-         $fiberManager->currentFiber_
-
-  set $all_fibers = &($fiberManager->allFibers_.data_.root_plus_size_.m_header)
-  set $fiber_hook = $all_fibers->next_
-  printf "\n  Active fibers:\n"
-  while $fiber_hook != $all_fibers
-    set $fiber = (folly::fibers::Fiber*) \
-        ((int64_t)$fiber_hook - \
-         (int64_t)&folly::fibers::Fiber::globalListHook_)
-    if $fiber->state_ != folly::fibers::Fiber::INVALID
-      printf "    (folly::fibers::Fiber*)%p   State: ", $fiber
-      print_folly_fiber_state $fiber
-      printf "\n"
-    end
-    set $fiber_hook = $fiber_hook->next_
-  end
-end
-
-# Print global FiberManager map
-define print_folly_fiber_manager_map
-  set $global_cache=*(('folly::fibers::(anonymous namespace)::GlobalCache<folly::EventBase>'**) \
-      &'folly::fibers::(anonymous namespace)::GlobalCache<folly::EventBase>::instance()::ret')
-  printf "  Global FiberManager map has %d entries.\n", \
-         $global_cache->map_->_M_h->_M_element_count
-
-  set $item = $global_cache->map_->_M_h->_M_before_begin._M_nxt
-  while $item != 0
-    set $evb = ((folly::EventBase**)$item)[1]
-    set $fiberManager = ((folly::fibers::FiberManager**)$item)[2]
-    printf "    (folly::EventBase*)%p -> (folly::fibers::FiberManager*)%p\n", \
-           $evb, $fiberManager
-
-    set $item = $item->_M_nxt
-  end
-end