| # Copyright 2016 The Fuchsia Authors |
| # |
| # Use of this source code is governed by a MIT-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/MIT |
| |
| # GDB support for zircon kernel. |
| |
| # TODO(dje): gdb should let us use a better command class than COMMAND_DATA. |
| # TODO(dje): Add arm64 support. |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import sys |
| import gdb |
| import gdb.printing |
| import re |
| from gdb.unwinder import Unwinder |
| |
| if sys.version_info > (3,): |
| long = int |
| |
| # The command prefix, passed in from gdbinit.py. |
| _ZIRCON_COMMAND_PREFIX = "zircon" |
| _KERNEL_EXCEPTION_UNWINDER_PARAMETER = "kernel-exception-unwinder" |
| |
| _THREAD_MAGIC = 0x74687264 |
| _BOOT_MAGIC = 0x544f4f42 |
| _KERNEL_BASE_ADDRESS = "KERNEL_BASE_ADDRESS" |
| |
| print("Loading zircon.elf-gdb.py ...") |
| |
| |
| def _get_architecture(): |
| # TODO(dje): gdb doesn't provide us with a simple way to do this. |
| return gdb.execute("show architecture", to_string=True) |
| |
| |
| def _is_x86_64(): |
| """Return True if we're on an x86-64 platform.""" |
| return re.search(r"x86-64", _get_architecture()) |
| |
| |
| def _is_arm64(): |
| """Return True if we're on an aarch64 platform.""" |
| return re.search(r"aarch64", _get_architecture()) |
| |
| |
| # The default is 2 seconds which is too low. |
| # But don't override something the user set. |
| # [If the user set it to the default, too bad. :-)] |
| # TODO(dje): Alas this trips over upstream PR 20084. |
| #_DEFAULT_GDB_REMOTETIMEOUT = 2 |
| #if int(gdb.parameter("remotetimeout")) == _DEFAULT_GDB_REMOTETIMEOUT: |
| # gdb.execute("set remotetimeout 10") |
| |
| |
| class _ZirconPrefix(gdb.Command): |
| """zircon command prefix""" |
| |
| def __init__(self): |
| super(_ZirconPrefix, self).__init__( |
| "%s" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, prefix=True) |
| |
| |
| class _InfoZircon(gdb.Command): |
| """info zircon command prefix""" |
| |
| def __init__(self): |
| super(_InfoZircon, self).__init__( |
| "info %s" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, prefix=True) |
| |
| |
| class _SetZircon(gdb.Command): |
| """set zircon command prefix""" |
| |
| def __init__(self): |
| super(_SetZircon, self).__init__( |
| "set %s" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, prefix=True) |
| |
| |
| class _ShowZircon(gdb.Command): |
| """show zircon command prefix""" |
| |
| def __init__(self): |
| super(_ShowZircon, self).__init__( |
| "show %s" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, prefix=True) |
| |
| |
| class _ZirconMaxInfoThreads(gdb.Parameter): |
| """Parameter to limit output of "info zircon threads" command. |
| |
| This parameter is an escape hatch to catch corrupted lists. |
| We don't want "info zircon threads" to loop forever. |
| |
| The value is the maximum number of threads that will be printed. |
| """ |
| |
| set_doc = "Set the maximum number of zircon threads to print." |
| show_doc = "Show the maximum number of zircon threads to print." |
| |
| def __init__(self): |
| super(_ZirconMaxInfoThreads, self).__init__( |
| "%s max-info-threads" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, |
| gdb.PARAM_UINTEGER) |
| self.value = 1000 |
| |
| def get_show_string(self, pvalue): |
| return "Maximum number of threads to print is " + pvalue + "." |
| |
| def get_set_string(self): |
| # Ugh. There doesn't seem to be a way to implement a gdb parameter in |
| # Python that will be silent when the user changes the value. |
| if self.value is None: |
| value = "unlimited" |
| else: |
| value = self.value |
| return "Maximum number of threads to print been set to %s." % (value) |
| |
| |
| class _ZirconMaxInfoProcesses(gdb.Parameter): |
| """Parameter to limit output of "info zircon processes" command. |
| |
| This parameter is an escape hatch to catch corrupted lists. |
| We don't want "info zircon processes" to loop forever. |
| |
| The value is the maximum number of processes that will be printed. |
| """ |
| |
| set_doc = "Set the maximum number of zircon processes to print." |
| show_doc = "Show the maximum number of zircon processes to print." |
| |
| def __init__(self): |
| super(_ZirconMaxInfoProcesses, self).__init__( |
| "%s max-info-processes" % (_ZIRCON_COMMAND_PREFIX), |
| gdb.COMMAND_DATA, gdb.PARAM_UINTEGER) |
| self.value = 1000 |
| |
| def get_show_string(self, pvalue): |
| return "Maximum number of processes to print is " + pvalue + "." |
| |
| def get_set_string(self): |
| # Ugh. There doesn't seem to be a way to implement a gdb parameter in |
| # Python that will be silent when the user changes the value. |
| if self.value is None: |
| value = "unlimited" |
| else: |
| value = self.value |
| return "Maximum number of processes to print been set to %s." % (value) |
| |
| |
| class _ZirconMaxInfoHandles(gdb.Parameter): |
| """Parameter to limit output of "info zircon handles" command. |
| |
| This parameter is an escape hatch to catch corrupted lists. |
| We don't want "info zircon handles" to loop forever. |
| |
| The value is the maximum number of handles that will be printed. |
| """ |
| |
| set_doc = "Set the maximum number of zircon handles to print." |
| show_doc = "Show the maximum number of zircon handles to print." |
| |
| def __init__(self): |
| super(_ZirconMaxInfoHandles, self).__init__( |
| "%s max-info-handles" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_DATA, |
| gdb.PARAM_UINTEGER) |
| self.value = 1000 |
| |
| def get_show_string(self, pvalue): |
| return "Maximum number of handles to print is " + pvalue + "." |
| |
| def get_set_string(self): |
| # Ugh. There doesn't seem to be a way to implement a gdb parameter in |
| # Python that will be silent when the user changes the value. |
| if self.value is None: |
| value = "unlimited" |
| else: |
| value = self.value |
| return "Maximum number of handles to print been set to %s." % (value) |
| |
| |
| def containerof(node_ptr, type_name, member_name): |
| """Python version of zircon's containerof macro.""" |
| # TODO(dje): This could only be computed once. |
| # For more popular types, compute all possible once. |
| char_ptr = gdb.lookup_type("char").pointer() |
| ptr = node_ptr.cast(char_ptr) |
| type_object_ptr = gdb.lookup_type(type_name).pointer() |
| offsetof = long(gdb.Value(0).cast(type_object_ptr)[member_name].address) |
| return (ptr - offsetof).cast(type_object_ptr) |
| |
| |
| def _build_zircon_pretty_printers(): |
| pp = gdb.printing.RegexpCollectionPrettyPrinter("zircon") |
| # Insert printer registration here. |
| #pp.add_printer("foo", "^foo$", _ZirconFooPrinter) |
| return pp |
| |
| |
| def register_zircon_pretty_printers(obj): |
| if obj is None: |
| obj = gdb |
| gdb.printing.register_pretty_printer( |
| obj, _build_zircon_pretty_printers(), replace=True) |
| |
| |
| def _get_thread_list(): |
| """ Return a list of all Thread threads. |
| |
| The result is constrained by "zircon max-info-threads". |
| """ |
| threads = [] |
| head = gdb.parse_and_eval("&thread_list") |
| t = head["next"] |
| count = 0 |
| max_threads = gdb.parameter( |
| "%s max-info-threads" % (_ZIRCON_COMMAND_PREFIX)) |
| int_type = gdb.lookup_type("int") |
| ptr_size = int_type.pointer().sizeof |
| # Note: A "corrupted" list can happen for a short time while an |
| # element is being added/removed. And, in non-stop mode, the list can |
| # change while we're at it. This isn't a problem in all-stop mode, but |
| # non-stop mode is generally preferable. We'll see how this works in |
| # practice. |
| while t and t != head: |
| if max_threads is not None and count >= max_threads: |
| break |
| # Catch misaligned pointers. |
| # Casting to int shouldn't be necessary, but the python API doesn't |
| # support creating an int from a pointer. |
| # We assume the object is aligned at least as great as a pointer. |
| if (t.cast(int_type) & (ptr_size - 1)) != 0: |
| break |
| # TODO(dje): Do a range check? |
| thread_ptr = containerof(t, "thread", "thread_list_node") |
| if thread_ptr["magic"] != _THREAD_MAGIC: |
| break |
| # TODO(dje): Move this to a routine, more list printers will want this. |
| threads.append(thread_ptr) |
| t = t["next"] |
| count += 1 |
| return threads |
| |
| |
| def _get_process_list(): |
| """Return list of all processes. |
| |
| The result is constrained by "zircon max-info-processes". |
| """ |
| processes = [] |
| head = gdb.parse_and_eval("&process_list") |
| head = head["head_"] |
| p = head |
| count = 0 |
| max_processes = gdb.parameter( |
| "%s max-info-processes" % (_ZIRCON_COMMAND_PREFIX)) |
| int_type = gdb.lookup_type("int") |
| ptr_size = int_type.pointer().sizeof |
| # Note: A "corrupted" list can happen for a short time while an |
| # element is being added/removed. And, in non-stop mode, the list can |
| # change while we're at it. This isn't a problem in all-stop mode, but |
| # non-stop mode is generally preferable. We'll see how this works in |
| # practice. |
| while p: |
| if max_processes is not None and count >= max_processes: |
| break |
| # Catch misaligned pointers. |
| # Casting to int shouldn't be necessary, but the python API doesn't |
| # support creating an int from a pointer. |
| # We assume the object is aligned at least as great as a pointer. |
| if (p.cast(int_type) & (ptr_size - 1)) != 0: |
| break |
| # TODO(dje): Do a range check? |
| # TODO(dje): Move this to a routine, more list printers will want this. |
| processes.append(p) |
| p = p["next_"] |
| count += 1 |
| if p == head: |
| break |
| return processes |
| |
| |
| def _get_handle_list(process): |
| """Return list of all handles of process. |
| |
| The result is constrained by "zircon max-info-handles". |
| """ |
| handles = [] |
| head = process["handles_"] |
| head = head["head_"] |
| h = head |
| count = 0 |
| max_handles = gdb.parameter( |
| "%s max-info-handles" % (_ZIRCON_COMMAND_PREFIX)) |
| int_type = gdb.lookup_type("int") |
| ptr_size = int_type.pointer().sizeof |
| # Note: A "corrupted" list can happen for a short time while an |
| # element is being added/removed. And, in non-stop mode, the list can |
| # change while we're at it. This isn't a problem in all-stop mode, but |
| # non-stop mode is generally preferable. We'll see how this works in |
| # practice. |
| while h: |
| if max_handles is not None and count >= max_handles: |
| break |
| # Catch misaligned pointers. |
| # Casting to int shouldn't be necessary, but the python API doesn't |
| # support creating an int from a pointer. |
| # We assume the object is aligned at least as great as a pointer. |
| if (h.cast(int_type) & (ptr_size - 1)) != 0: |
| break |
| # TODO(dje): Do a range check? |
| # TODO(dje): Move this to a routine, more list printers will want this. |
| handles.append(h) |
| h = h["next_"] |
| count += 1 |
| if h == head: |
| break |
| return handles |
| |
| |
| def _print_thread_summary(thread, number, tls_entry, user_thread_ptr_t): |
| user_thread = thread["tls"][tls_entry] |
| if user_thread: |
| user_thread_ptr = user_thread.cast(user_thread_ptr_t) |
| # TODO(dje): Why is str necessary here? Otherwise -> |
| # "Cannot convert value to int." |
| pid = str(user_thread_ptr["process_"]["id_"]) |
| else: |
| pid = "kern" |
| name = str(thread["name"].lazy_string().value()).strip('"') |
| print( |
| "%3d %5s %#16x %-32s %s" % |
| (number, pid, thread.address, name, thread["state"])) |
| |
| |
| class _InfoZirconThreads(gdb.Command): |
| """info zircon threads command |
| |
| This command prints a list of all zircon threads. |
| TODO: Allow specifying which threads to print. |
| """ |
| |
| def __init__(self): |
| super(_InfoZirconThreads, self).__init__( |
| "info %s threads" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_USER) |
| |
| def invoke(self, arg, from_tty): |
| # Do this first to make sure the previous value gets cleared out. |
| # There's no way to unset a convenience var, so KISS. |
| gdb.execute("set $zx_threads = (Thread*[1]) { 0 }") |
| tls_entry_lkuser = gdb.parse_and_eval("TLS_ENTRY_LKUSER") |
| threads = _get_thread_list() |
| num_threads = len(threads) |
| # The array is origin-1-indexed. Have a null first entry to KISS. |
| gdb.execute("set $zx_threads = (Thread*[%d]) { 0 }" % (num_threads + 1)) |
| |
| # Populate the array first, before printing the summary, to make sure this |
| # gets done even if there's an error during printing. |
| num = 1 |
| for thread_ptr in threads: |
| gdb.execute( |
| "set $zx_threads[%d] = (Thread*) %u" % (num, thread_ptr)) |
| num += 1 |
| |
| # Translating gdb values to python often trips over these. Heads up. |
| save_print_address = "yes" if gdb.parameter("print address") else "no" |
| save_print_symbol = "yes" if gdb.parameter("print symbol") else "no" |
| gdb.execute("set print address off") |
| gdb.execute("set print symbol off") |
| |
| print( |
| "%3s %5s %-18s %-32s %s" % |
| ("Num", "Pid", "Thread*", "Name", "State")) |
| # Make sure we restore these when we're done. |
| try: |
| user_thread_ptr_t = gdb.lookup_type("UserThread").pointer() |
| num = 1 |
| for thread_ptr in threads: |
| # TODO(dje): remove dereference |
| _print_thread_summary( |
| thread_ptr.dereference(), num, tls_entry_lkuser, |
| user_thread_ptr_t) |
| num += 1 |
| finally: |
| gdb.execute("set print address %s" % (save_print_address)) |
| gdb.execute("set print symbol %s" % (save_print_symbol)) |
| if num_threads: |
| print("Note: Each thread is now available in $zx_threads[num].") |
| else: |
| print("<no threads>") |
| |
| |
| def _print_process_summary(process, number): |
| state = str(process["state_"]) |
| state = state.replace("ProcessDispatcher::", "") |
| print("%3d %#16x %4u %s" % (number, process.address, process["id_"], state)) |
| |
| |
| class _InfoZirconProcesses(gdb.Command): |
| """info zircon processes command |
| |
| This command prints a list of all zircon processes. |
| TODO: Allow specifying which processes to print. |
| """ |
| |
| def __init__(self): |
| super(_InfoZirconProcesses, self).__init__( |
| "info %s processes" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_USER) |
| |
| def invoke(self, arg, from_tty): |
| # Do this first to make sure the previous value gets cleared out. |
| # There's no way to unset a convenience var, so KISS. |
| gdb.execute("set $zx_processes = (ProcessDispatcher*[1]) { 0 }") |
| tls_entry_lkuser = gdb.parse_and_eval("TLS_ENTRY_LKUSER") |
| processes = _get_process_list() |
| num_processes = len(processes) |
| # The array is origin-1-indexed. Have a null first entry to KISS. |
| gdb.execute( |
| "set $zx_processes = (ProcessDispatcher*[%d]) { 0 }" % |
| (num_processes + 1)) |
| |
| # Populate the array first, before printing the summary, to make sure this |
| # gets done even if there's an error during printing. |
| num = 1 |
| for process_ptr in processes: |
| gdb.execute( |
| "set $zx_processes[%d] = (ProcessDispatcher*) %u" % |
| (num, process_ptr)) |
| num += 1 |
| |
| # Translating gdb values to python often trips over these. Heads up. |
| save_print_address = "yes" if gdb.parameter("print address") else "no" |
| save_print_symbol = "yes" if gdb.parameter("print symbol") else "no" |
| gdb.execute("set print address off") |
| gdb.execute("set print symbol off") |
| |
| print( |
| "%3s %-18s %4s %s" % ("Num", "ProcessDispatcher*", "Pid", "State")) |
| # Make sure we restore these when we're done. |
| try: |
| num = 1 |
| for process_ptr in processes: |
| _print_process_summary(process_ptr.dereference(), num) |
| num += 1 |
| finally: |
| gdb.execute("set print address %s" % (save_print_address)) |
| gdb.execute("set print symbol %s" % (save_print_symbol)) |
| if num_processes: |
| print("Note: Each process is now available in $zx_processes[num].") |
| else: |
| print("<no processes>") |
| |
| |
| def _print_handle_summary(handle, number): |
| process_id = handle["process_id_"] |
| rights = handle["rights_"] |
| dispatcher = handle["dispatcher_"]["ptr_"] |
| # TODO(dje): This is a hack to get the underlying type from the vtable. |
| # The python API should support this directly. |
| dispatcher_text = gdb.execute( |
| "output *(Dispatcher*) %s" % (dispatcher), to_string=True) |
| dispatcher_split_text = dispatcher_text.split(" ", 1) |
| if len(dispatcher_split_text) == 2: |
| dispatcher_type = dispatcher_split_text[0].strip("()") |
| else: |
| dispatcher_type = "Dispatcher" |
| dispatcher_text = "(%s*) %s" % (dispatcher_type, dispatcher) |
| print( |
| " %3d %-18s %4u %#8x %s" % |
| (number, handle.address, process_id, rights, dispatcher_text)) |
| |
| |
| class _InfoZirconHandles(gdb.Command): |
| """info zircon handles command |
| |
| This command prints a list of all zircon handles. |
| TODO: Allow specifying which handles to print. |
| """ |
| |
| def __init__(self): |
| super(_InfoZirconHandles, self).__init__( |
| "info %s handles" % (_ZIRCON_COMMAND_PREFIX), gdb.COMMAND_USER) |
| |
| def invoke(self, arg, from_tty): |
| processes = _get_process_list() |
| |
| # Translating gdb values to python often trips over these. Heads up. |
| save_print_address = "yes" if gdb.parameter("print address") else "no" |
| save_print_symbol = "yes" if gdb.parameter("print symbol") else "no" |
| gdb.execute("set print address on") |
| gdb.execute("set print symbol off") |
| |
| # Make sure we restore these when we're done. |
| try: |
| for p in processes: |
| handles = _get_handle_list(p) |
| num_handles = len(handles) |
| |
| print("Process %u" % (p["id_"])) |
| print( |
| " %3s %-18s %4s %8s %s" % |
| ("Num", "Handle*", "Pid", "Rights", "Dispatcher")) |
| |
| num = 1 |
| for handle_ptr in handles: |
| _print_handle_summary(handle_ptr.dereference(), num) |
| num += 1 |
| |
| if not num_handles: |
| print(" <no handles>") |
| |
| finally: |
| gdb.execute("set print address %s" % (save_print_address)) |
| gdb.execute("set print symbol %s" % (save_print_symbol)) |
| |
| |
| class _ZirconKernelExceptionUnwinder(gdb.Parameter): |
| """Parameter to enable zircon kernel exception unwinding. |
| |
| This parameter is an escape hatch in case there are problems with the unwinder. |
| |
| N.B. Perhaps this command should flush registers. |
| It doesn't now to avoid side-effects, and the user is responsible for typing |
| "flushregs" if s/he wants to reprint a recent backtrace. |
| """ |
| |
| set_doc = "Set whether the zircon kernel exception unwinder is enabled." |
| show_doc = "Show whether the zircon kernel exception unwinder is enabled." |
| |
| def __init__(self): |
| super(_ZirconKernelExceptionUnwinder, self).__init__( |
| "%s %s" % |
| (_ZIRCON_COMMAND_PREFIX, _KERNEL_EXCEPTION_UNWINDER_PARAMETER), |
| gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) |
| self.value = True |
| |
| def get_show_string(self, pvalue): |
| value = "enabled" if self.value else "disabled" |
| return "The kernel exception unwinder is %s." % (value) |
| |
| def get_set_string(self): |
| # Ugh. There doesn't seem to be a way to implement a gdb parameter in |
| # Python that will be silent when the user changes the value. |
| value = "enabled" if self.value else "disabled" |
| return "The kernel exception unwinder is %s." % (value) |
| |
| |
| class _Amd64KernelExceptionUnwinder(Unwinder): |
| # See arch/x86/64/exceptions.S. |
| AT_IFRAME_SETUP = "interrupt_common_iframe_set_up_for_debugger" |
| INTERRUPT_COMMON = "interrupt_common" |
| |
| class FrameId(object): |
| |
| def __init__(self, sp, pc): |
| self.sp = sp |
| self.pc = pc |
| |
| @staticmethod |
| def lookup_minsym(minsym_name): |
| """Return the address of "minimal symbol" minsym_name.""" |
| # TODO(dje): What's here now is a quick hack to get things going. |
| # GDB's python API doesn't yet provide the ability to look up minsyms. |
| try: |
| output = gdb.execute("output %s" % (minsym_name), to_string=True) |
| except (gdb.error): |
| return None |
| symbol_value = None |
| if not output.startswith("No symbol"): |
| symbol_match = re.search(r"0x[0-9a-f]+", output) |
| if symbol_match is not None: |
| symbol_value = long(symbol_match.group(0), 16) |
| return symbol_value |
| |
| @staticmethod |
| def is_user_space(iframe): |
| """Return True if iframe is from user space.""" |
| # See arch/x86/include/arch/x86/descriptor.h:SELECTOR_PL. |
| return (iframe["cs"] & 3) != 0 |
| |
| def __init__(self): |
| super(_Amd64KernelExceptionUnwinder, |
| self).__init__("AMD64 kernel exception unwinder") |
| # We assume uintptr_t is present in the debug info. |
| # We *could* use unsigned long, but it's a bit of an obfuscation. |
| self.uintptr_t = gdb.lookup_type("uintptr_t") |
| # We assume "unsigned int" is 32 bits. |
| self.uint32_t = gdb.lookup_type("unsigned int") |
| self.iframe_ptr_t = gdb.lookup_type("iframe_t").pointer() |
| # We need to know when the pc is at the point where it has called |
| # x86_exception_handler. |
| self.at_iframe_setup = self.lookup_minsym( |
| _Amd64KernelExceptionUnwinder.AT_IFRAME_SETUP) |
| self.interrupt_common_begin_addr = self.lookup_minsym( |
| _Amd64KernelExceptionUnwinder.INTERRUPT_COMMON) |
| |
| def is_in_icommon(self, pc): |
| """Return True if pc is in the interrupt_common function.""" |
| # First do the preferred test. |
| # If the pc is here then we've called into x86_exception_handler. |
| if self.at_iframe_setup is not None and pc == self.at_iframe_setup: |
| return True |
| # Fall back to this in case the special symbol doesn't exist. |
| if self.interrupt_common_begin_addr is None: |
| return False |
| end_addr = self.interrupt_common_begin_addr + 64 |
| return pc >= self.interrupt_common_begin_addr and pc < end_addr |
| |
| def __call__(self, pending_frame): |
| try: |
| # Punt if disabled. |
| if not gdb.parameter( |
| "%s %s" % |
| (_ZIRCON_COMMAND_PREFIX, _KERNEL_EXCEPTION_UNWINDER_PARAMETER)): |
| return None |
| # Note: We use rip,rsp here instead of pc,sp to work around bug 20128 |
| #pc = pending_frame.read_register("pc").cast(self.uintptr_t) |
| pc = pending_frame.read_register("rip").cast(self.uintptr_t) |
| #print "icommon unwinder, pc = %#x" % (long(str(pc))) # work around bug 20126 |
| if not self.is_in_icommon(pc): |
| return None |
| sp = pending_frame.read_register("rsp").cast(self.uintptr_t) |
| #print "icommon unwinder, sp = %#x" % (long(str(sp))) |
| iframe = sp.cast(self.iframe_ptr_t) |
| # This is only for kernel faults, not user-space ones. |
| if self.is_user_space(iframe): |
| return None |
| frame_id = self.FrameId(sp, pc) |
| unwind_info = pending_frame.create_unwind_info(frame_id) |
| unwind_info.add_saved_register("rip", iframe["ip"]) |
| unwind_info.add_saved_register("rsp", iframe["user_sp"]) |
| unwind_info.add_saved_register("rax", iframe["rax"]) |
| unwind_info.add_saved_register("rbx", iframe["rbx"]) |
| unwind_info.add_saved_register("rcx", iframe["rcx"]) |
| unwind_info.add_saved_register("rdx", iframe["rdx"]) |
| unwind_info.add_saved_register("rbp", iframe["rbp"]) |
| unwind_info.add_saved_register("rsi", iframe["rsi"]) |
| unwind_info.add_saved_register("r8", iframe["r8"]) |
| unwind_info.add_saved_register("r9", iframe["r9"]) |
| unwind_info.add_saved_register("r10", iframe["r10"]) |
| unwind_info.add_saved_register("r11", iframe["r11"]) |
| unwind_info.add_saved_register("r12", iframe["r12"]) |
| unwind_info.add_saved_register("r13", iframe["r13"]) |
| unwind_info.add_saved_register("r14", iframe["r14"]) |
| unwind_info.add_saved_register("r15", iframe["r15"]) |
| # Flags is recorded as 64 bits, but gdb needs to see 32. |
| unwind_info.add_saved_register( |
| "eflags", iframe["flags"].cast(self.uint32_t)) |
| #print "Unwind info:" |
| #print unwind_info |
| return unwind_info |
| except (gdb.error, RuntimeError): |
| return None |
| |
| |
| _ZirconPrefix() |
| _InfoZircon() |
| _SetZircon() |
| _ShowZircon() |
| |
| _ZirconMaxInfoThreads() |
| _ZirconMaxInfoProcesses() |
| _ZirconMaxInfoHandles() |
| |
| _InfoZirconThreads() |
| _InfoZirconProcesses() |
| _InfoZirconHandles() |
| |
| _ZirconKernelExceptionUnwinder() |
| |
| _ull = gdb.lookup_type("unsigned long long") |
| _uint = gdb.lookup_type("unsigned int") |
| |
| |
| def _cast(value, t, shift=64): |
| return int(value.cast(t)) & ((1 << shift) - 1) |
| |
| |
| def _cast_ull(value): |
| """ Cast a value to unsigned long long """ |
| global _ull |
| return _cast(value, _ull) |
| |
| |
| def _cast_uint(value): |
| """ Cast a value to unsigned int""" |
| global _uint |
| return _cast(value, _uint, 32) |
| |
| |
| def _read_symbol_address(name): |
| """ Read the address of a symbol """ |
| addr = gdb.parse_and_eval("&" + name) |
| try: |
| if addr is not None: |
| return _cast_ull(addr) |
| except gdb.MemoryError: |
| pass |
| print("Can't find %s to lookup KASLR relocation" % name) |
| return None |
| |
| |
| def _read_pointer(addr): |
| """ Read a pointer in an address """ |
| value = gdb.parse_and_eval("*(unsigned long*)0x%x" % addr) |
| try: |
| if value is not None: |
| return _cast_ull(value) |
| except gdb.MemoryError: |
| pass |
| |
| print("Can't read 0x%x to lookup KASLR ptr value" % addr) |
| return None |
| |
| |
| def _read_uint(addr): |
| """ Read a uint """ |
| value = gdb.parse_and_eval("*(unsigned int*)0x%x" % addr) |
| try: |
| if value is not None: |
| return _cast_uint(value) |
| except gdb.MemoryError: |
| pass |
| print("Can't read 0x%x to lookup KASLR uint value" % addr) |
| return None |
| |
| |
| def _offset_symbols_and_breakpoints(kernel_relocated_base=None): |
| """ Using the KASLR relocation address, reload symbols and breakpoints """ |
| print("Update symbols and breakpoints for KASLR") |
| |
| base_address = _read_symbol_address(_KERNEL_BASE_ADDRESS) |
| if not base_address: |
| return False |
| |
| load_start = _read_symbol_address("IMAGE_LOAD_START") |
| if not load_start: |
| return False |
| |
| if not kernel_relocated_base: |
| kernel_relocated_base = _read_symbol_address("kernel_relocated_base") |
| if not kernel_relocated_base: |
| return False |
| |
| relocated = _read_pointer(kernel_relocated_base) |
| if not relocated: |
| print("Failed to fetch KASLR base address") |
| return False |
| |
| # There is no api for symbol management. |
| # Everything has to be done by custom commands |
| sym = gdb.execute("info target", to_string=True) |
| m = re.match("^Symbols from \"([^\"]+)\"", sym) |
| if not m: |
| print("Error: Cannot find the target symbol") |
| return False |
| sym_path = m.group(1) |
| |
| # Identify all section addresses |
| x = gdb.execute("info target", to_string=True) |
| m = re.findall("\s0x([a-f0-9]+) - 0x[a-f0-9]+ is (.*)", x) |
| |
| offset = base_address - relocated |
| sections = dict([(name, int(addr, 16)) for addr, name in m]) |
| if len(sections) == 0: |
| print("Error: Failed to find sections in binary") |
| return False |
| |
| if ".text" not in sections: |
| print("Error: Failed to find .text section") |
| return False |
| |
| # Do not prompt the user |
| confirm_was_enabled = gdb.parameter("confirm") |
| if confirm_was_enabled: |
| gdb.execute("set confirm off", to_string=True) |
| |
| # Disable auto loading to prevent the script to be reloaded |
| auto_loading_enabled = gdb.parameter("auto-load python-scripts") |
| if auto_loading_enabled: |
| gdb.execute("set auto-load python-scripts off", to_string=True) |
| |
| # Remove all symbols |
| gdb.execute("symbol-file", to_string=True) |
| |
| # Update all addresses to the relocated address |
| sections = dict( |
| [(name, addr - offset) for name, addr in sections.items()]) |
| |
| text_addr = sections[".text"] |
| del sections[".text"] |
| |
| args = ["-s %s 0x%x" % (name, addr) for name, addr in sections.items()] |
| |
| # Reload the ELF with all sections set |
| gdb.execute("add-symbol-file \"%s\" 0x%x -readnow %s" \ |
| % (sym_path, text_addr, " ".join(args)), to_string=True) |
| |
| if auto_loading_enabled: |
| gdb.execute("set auto-load python-scripts on", to_string=True) |
| |
| if confirm_was_enabled: |
| gdb.execute("set confirm on", to_string=True) |
| |
| # Verify it works as expected |
| code_start = _read_symbol_address("__code_start") |
| if not kernel_relocated_base: |
| return False |
| |
| expected = relocated + (load_start - base_address) |
| if code_start != expected: |
| print("Error: Incorrect relocation for __code_start 0x%x vs 0x%x" \ |
| % (expected, code_start)) |
| return False |
| |
| print("KASLR: Correctly reloaded kernel at 0x%x" % relocated) |
| return True |
| |
| |
| class KASLRBreakpoint(gdb.Breakpoint): |
| """ Helper class to setup breakpoints to load symbols for KASLR. """ |
| |
| def __init__(self): |
| self._is_valid = False |
| |
| def is_valid(self): |
| if gdb.Breakpoint.is_valid(self) == False: |
| return False |
| return self._is_valid |
| |
| def stop(self): |
| # A breakpoint cannot change anything so get callback on the following stop |
| gdb.events.stop.connect(self._stop_callback_internal) |
| return True |
| |
| # Callback through events so the state can be changed |
| def _stop_callback_internal(self, event): |
| gdb.events.stop.disconnect(self._stop_callback_internal) |
| self.delete() |
| |
| # Call the top callback |
| self._stop_callback(event) |
| |
| |
| class KASLRBootWatchpoint(KASLRBreakpoint): |
| """ Watchpoint to catch read access to KASLR relocated address |
| |
| The assumption is the address was written before it is read. It is an |
| architecture independent way to start at the right moment if the relocated |
| KASLR symbol name is the same. |
| """ |
| |
| def __init__(self, pc): |
| KASLRBreakpoint.__init__(self) |
| |
| base_address = _read_symbol_address(_KERNEL_BASE_ADDRESS) |
| if not base_address: |
| return |
| |
| kernel_relocated_base = _read_symbol_address("kernel_relocated_base") |
| if not kernel_relocated_base: |
| return |
| |
| self._relocated_base_offset = kernel_relocated_base |
| |
| if _is_x86_64(): |
| # x86_64 uses the physical address |
| self._relocated_base_offset -= base_address |
| elif _is_arm64(): |
| # Search from pc to find BOOT tag |
| found = False |
| for addr in range(pc, pc + 0x10000000, 0x10000): |
| data = _read_uint(addr) |
| if data == _BOOT_MAGIC: |
| self._relocated_base_offset -= base_address |
| self._relocated_base_offset += addr |
| found = True |
| break |
| |
| if found == False: |
| print("Error: Could not found BOOT_MAGIC") |
| return |
| |
| self._is_valid = True |
| gdb.Breakpoint.__init__( |
| self, |
| "*0x%x" % self._relocated_base_offset, |
| gdb.BP_WATCHPOINT, |
| gdb.WP_READ, |
| internal=True) |
| |
| self.silence = True |
| |
| # Callback from KASLRBreakpoint when it is safe to change state |
| def _stop_callback(self, event): |
| # Load symbols using load_offset |
| _offset_symbols_and_breakpoints(self._relocated_base_offset) |
| |
| |
| class KASLRStartBreakpoint(KASLRBreakpoint): |
| """ Breakpoint to catch _start |
| |
| The ZBI boot path result with memory being moved before _start on x86_64. |
| In this case, break on _start before setting up the watchpoint. |
| """ |
| |
| def __init__(self): |
| KASLRBreakpoint.__init__(self) |
| |
| base_address = _read_symbol_address(_KERNEL_BASE_ADDRESS) |
| if not base_address: |
| return |
| |
| _start = _read_symbol_address("_start") |
| if not _start: |
| return |
| |
| # Get the physical address |
| _start -= base_address |
| |
| self._is_valid = True |
| gdb.Breakpoint.__init__(self, "*0x%x" % _start, internal=True) |
| self.silence = True |
| |
| # Callback from KASLRBreakpoint when it is safe to change state |
| def _stop_callback(self, event): |
| pc = _cast_ull(gdb.parse_and_eval("$pc")) |
| x = KASLRBootWatchpoint(pc) |
| if not x.is_valid(): |
| print("Error: Failed create KASLR boot watchpoint after _stat") |
| return |
| |
| print("Watchpoint set on KASLR relocated base variable") |
| gdb.execute("continue") |
| |
| |
| def _align(addr, shift): |
| b64 = (1 << 64) - 1 |
| mask = (1 << shift) - 1 |
| return addr & (~mask & b64) |
| |
| |
| def _identify_offset_to_reload(pc): |
| """ From $pc, search the base address to the page size |
| Used when attaching to an existing debugging session |
| """ |
| print("Search KASLR base address based on $pc") |
| if (pc >> 63) != 1: |
| print("Error: Didn't break into kernel code") |
| return False |
| |
| base_address = _read_symbol_address(_KERNEL_BASE_ADDRESS) |
| if not base_address: |
| return False |
| |
| end = _read_symbol_address("_end") |
| if not end: |
| return False |
| |
| kernel_relocated_base = _read_symbol_address("kernel_relocated_base") |
| if not kernel_relocated_base: |
| return False |
| |
| offset = kernel_relocated_base - base_address |
| max_size = end - base_address |
| |
| # Search base+offset == base for each previous page until the max size |
| found = False |
| addr = pc |
| while addr > (pc - max_size): |
| addr = _align(addr - 1, 12) |
| target = addr + offset |
| value = _read_pointer(target) |
| if value == None: |
| break |
| if addr == value: |
| found = True |
| break |
| |
| if found == False: |
| print("Error: Failed to find the KASLR relocation from the $pc") |
| return False |
| |
| return _offset_symbols_and_breakpoints(target) |
| |
| |
| def _is_earlyboot_pc(pc): |
| """ Early boot if the top 32-bit is zero """ |
| return not (pc >> 32) |
| |
| |
| def _KASLR_stop_event(event): |
| """ Called on first stop after debugger started """ |
| gdb.events.stop.disconnect(_KASLR_stop_event) |
| pc = _cast_ull(gdb.parse_and_eval("$pc")) |
| if not _is_earlyboot_pc(pc): |
| # If not early boot, try to find the kernel base and adapt |
| _identify_offset_to_reload(pc) |
| return |
| |
| x = None |
| |
| # ARM64 map _start at random place in memory, directly use the watchpoint |
| if _is_arm64(): |
| x = KASLRBootWatchpoint(pc) |
| else: |
| x = KASLRStartBreakpoint() |
| |
| if not x.is_valid(): |
| print("Error: Failed create KASLR boot first breakpoint/watchpoint") |
| return |
| |
| gdb.execute("continue") |
| |
| |
| def _install(): |
| current_objfile = gdb.current_objfile() |
| # gdb.current_objfile() is only set when autoloading; |
| # otherwise we can try to assume the first loaded objfile is the kernel. |
| if not current_objfile and gdb.objfiles(): |
| current_objfile = gdb.objfiles()[0] |
| if not current_objfile: |
| print("Warning: no object file set") |
| return |
| |
| register_zircon_pretty_printers(current_objfile) |
| if current_objfile is not None and _is_x86_64(): |
| gdb.unwinder.register_unwinder( |
| current_objfile, _Amd64KernelExceptionUnwinder(), True) |
| print( |
| "Zircon extensions installed for {}".format( |
| current_objfile.filename)) |
| |
| if not _is_x86_64() and not _is_arm64(): |
| print( |
| "Warning: Unsupported architecture, KASLR support will be experimental" |
| ) |
| gdb.events.stop.connect(_KASLR_stop_event) |
| |
| |
| _install() |