| # |
| # gdb helper commands and functions for Linux kernel debugging |
| # |
| # load kernel and module symbols |
| # |
| # Copyright (c) Siemens AG, 2011-2013 |
| # |
| # Authors: |
| # Jan Kiszka <jan.kiszka@siemens.com> |
| # |
| # This work is licensed under the terms of the GNU GPL version 2. |
| # |
| |
| import gdb |
| import os |
| import re |
| |
| from linux import modules |
| |
| |
| if hasattr(gdb, 'Breakpoint'): |
| class LoadModuleBreakpoint(gdb.Breakpoint): |
| def __init__(self, spec, gdb_command): |
| super(LoadModuleBreakpoint, self).__init__(spec, internal=True) |
| self.silent = True |
| self.gdb_command = gdb_command |
| |
| def stop(self): |
| module = gdb.parse_and_eval("mod") |
| module_name = module['name'].string() |
| cmd = self.gdb_command |
| |
| # enforce update if object file is not found |
| cmd.module_files_updated = False |
| |
| # Disable pagination while reporting symbol (re-)loading. |
| # The console input is blocked in this context so that we would |
| # get stuck waiting for the user to acknowledge paged output. |
| show_pagination = gdb.execute("show pagination", to_string=True) |
| pagination = show_pagination.endswith("on.\n") |
| gdb.execute("set pagination off") |
| |
| if module_name in cmd.loaded_modules: |
| gdb.write("refreshing all symbols to reload module " |
| "'{0}'\n".format(module_name)) |
| cmd.load_all_symbols() |
| else: |
| cmd.load_module_symbols(module) |
| |
| # restore pagination state |
| gdb.execute("set pagination %s" % ("on" if pagination else "off")) |
| |
| return False |
| |
| |
| class LxSymbols(gdb.Command): |
| """(Re-)load symbols of Linux kernel and currently loaded modules. |
| |
| The kernel (vmlinux) is taken from the current working directly. Modules (.ko) |
| are scanned recursively, starting in the same directory. Optionally, the module |
| search path can be extended by a space separated list of paths passed to the |
| lx-symbols command.""" |
| |
| module_paths = [] |
| module_files = [] |
| module_files_updated = False |
| loaded_modules = [] |
| breakpoint = None |
| |
| def __init__(self): |
| super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES, |
| gdb.COMPLETE_FILENAME) |
| |
| def _update_module_files(self): |
| self.module_files = [] |
| for path in self.module_paths: |
| gdb.write("scanning for modules in {0}\n".format(path)) |
| for root, dirs, files in os.walk(path): |
| for name in files: |
| if name.endswith(".ko"): |
| self.module_files.append(root + "/" + name) |
| self.module_files_updated = True |
| |
| def _get_module_file(self, module_name): |
| module_pattern = ".*/{0}\.ko$".format( |
| module_name.replace("_", r"[_\-]")) |
| for name in self.module_files: |
| if re.match(module_pattern, name) and os.path.exists(name): |
| return name |
| return None |
| |
| def _section_arguments(self, module): |
| try: |
| sect_attrs = module['sect_attrs'].dereference() |
| except gdb.error: |
| return "" |
| attrs = sect_attrs['attrs'] |
| section_name_to_address = { |
| attrs[n]['name'].string(): attrs[n]['address'] |
| for n in range(int(sect_attrs['nsections']))} |
| args = [] |
| for section_name in [".data", ".data..read_mostly", ".rodata", ".bss", |
| ".text", ".text.hot", ".text.unlikely"]: |
| address = section_name_to_address.get(section_name) |
| if address: |
| args.append(" -s {name} {addr}".format( |
| name=section_name, addr=str(address))) |
| return "".join(args) |
| |
| def load_module_symbols(self, module): |
| module_name = module['name'].string() |
| module_addr = str(module['core_layout']['base']).split()[0] |
| |
| module_file = self._get_module_file(module_name) |
| if not module_file and not self.module_files_updated: |
| self._update_module_files() |
| module_file = self._get_module_file(module_name) |
| |
| if module_file: |
| gdb.write("loading @{addr}: {filename}\n".format( |
| addr=module_addr, filename=module_file)) |
| cmdline = "add-symbol-file {filename} {addr}{sections}".format( |
| filename=module_file, |
| addr=module_addr, |
| sections=self._section_arguments(module)) |
| gdb.execute(cmdline, to_string=True) |
| if module_name not in self.loaded_modules: |
| self.loaded_modules.append(module_name) |
| else: |
| gdb.write("no module object found for '{0}'\n".format(module_name)) |
| |
| def load_all_symbols(self): |
| gdb.write("loading vmlinux\n") |
| |
| # Dropping symbols will disable all breakpoints. So save their states |
| # and restore them afterward. |
| saved_states = [] |
| if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None: |
| for bp in gdb.breakpoints(): |
| saved_states.append({'breakpoint': bp, 'enabled': bp.enabled}) |
| |
| # drop all current symbols and reload vmlinux |
| gdb.execute("symbol-file", to_string=True) |
| gdb.execute("symbol-file vmlinux") |
| |
| self.loaded_modules = [] |
| module_list = modules.module_list() |
| if not module_list: |
| gdb.write("no modules found\n") |
| else: |
| [self.load_module_symbols(module) for module in module_list] |
| |
| for saved_state in saved_states: |
| saved_state['breakpoint'].enabled = saved_state['enabled'] |
| |
| def invoke(self, arg, from_tty): |
| self.module_paths = [os.path.expanduser(p) for p in arg.split()] |
| self.module_paths.append(os.getcwd()) |
| |
| # enforce update |
| self.module_files = [] |
| self.module_files_updated = False |
| |
| self.load_all_symbols() |
| |
| if hasattr(gdb, 'Breakpoint'): |
| if self.breakpoint is not None: |
| self.breakpoint.delete() |
| self.breakpoint = None |
| self.breakpoint = LoadModuleBreakpoint( |
| "kernel/module.c:do_init_module", self) |
| else: |
| gdb.write("Note: symbol update on module loading not supported " |
| "with this gdb version\n") |
| |
| |
| LxSymbols() |