1#
2# gdb helper commands and functions for Linux kernel debugging
3#
4#  load kernel and module symbols
5#
6# Copyright (c) Siemens AG, 2011-2013
7#
8# Authors:
9#  Jan Kiszka <jan.kiszka@siemens.com>
10#
11# This work is licensed under the terms of the GNU GPL version 2.
12#
13
14import gdb
15import os
16import re
17
18from linux import modules, utils, constants
19
20
21if hasattr(gdb, 'Breakpoint'):
22    class LoadModuleBreakpoint(gdb.Breakpoint):
23        def __init__(self, spec, gdb_command):
24            super(LoadModuleBreakpoint, self).__init__(spec, internal=True)
25            self.silent = True
26            self.gdb_command = gdb_command
27
28        def stop(self):
29            module = gdb.parse_and_eval("mod")
30            module_name = module['name'].string()
31            cmd = self.gdb_command
32
33            # enforce update if object file is not found
34            cmd.module_files_updated = False
35
36            # Disable pagination while reporting symbol (re-)loading.
37            # The console input is blocked in this context so that we would
38            # get stuck waiting for the user to acknowledge paged output.
39            show_pagination = gdb.execute("show pagination", to_string=True)
40            pagination = show_pagination.endswith("on.\n")
41            gdb.execute("set pagination off")
42
43            if module_name in cmd.loaded_modules:
44                gdb.write("refreshing all symbols to reload module "
45                          "'{0}'\n".format(module_name))
46                cmd.load_all_symbols()
47            else:
48                cmd.load_module_symbols(module)
49
50            # restore pagination state
51            gdb.execute("set pagination %s" % ("on" if pagination else "off"))
52
53            return False
54
55
56class LxSymbols(gdb.Command):
57    """(Re-)load symbols of Linux kernel and currently loaded modules.
58
59The kernel (vmlinux) is taken from the current working directly. Modules (.ko)
60are scanned recursively, starting in the same directory. Optionally, the module
61search path can be extended by a space separated list of paths passed to the
62lx-symbols command."""
63
64    module_paths = []
65    module_files = []
66    module_files_updated = False
67    loaded_modules = []
68    breakpoint = None
69
70    def __init__(self):
71        super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES,
72                                        gdb.COMPLETE_FILENAME)
73
74    def _update_module_files(self):
75        self.module_files = []
76        for path in self.module_paths:
77            gdb.write("scanning for modules in {0}\n".format(path))
78            for root, dirs, files in os.walk(path):
79                for name in files:
80                    if name.endswith(".ko") or name.endswith(".ko.debug"):
81                        self.module_files.append(root + "/" + name)
82        self.module_files_updated = True
83
84    def _get_module_file(self, module_name):
85        module_pattern = r".*/{0}\.ko(?:.debug)?$".format(
86            module_name.replace("_", r"[_\-]"))
87        for name in self.module_files:
88            if re.match(module_pattern, name) and os.path.exists(name):
89                return name
90        return None
91
92    def _section_arguments(self, module, module_addr):
93        try:
94            sect_attrs = module['sect_attrs'].dereference()
95        except gdb.error:
96            return str(module_addr)
97
98        attrs = sect_attrs['attrs']
99        section_name_to_address = {
100            attrs[n]['battr']['attr']['name'].string(): attrs[n]['address']
101            for n in range(int(sect_attrs['nsections']))}
102
103        textaddr = section_name_to_address.get(".text", module_addr)
104        args = []
105        for section_name in [".data", ".data..read_mostly", ".rodata", ".bss",
106                             ".text.hot", ".text.unlikely"]:
107            address = section_name_to_address.get(section_name)
108            if address:
109                args.append(" -s {name} {addr}".format(
110                    name=section_name, addr=str(address)))
111        return "{textaddr} {sections}".format(
112            textaddr=textaddr, sections="".join(args))
113
114    def load_module_symbols(self, module):
115        module_name = module['name'].string()
116        module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0]
117
118        module_file = self._get_module_file(module_name)
119        if not module_file and not self.module_files_updated:
120            self._update_module_files()
121            module_file = self._get_module_file(module_name)
122
123        if module_file:
124            if utils.is_target_arch('s390'):
125                # Module text is preceded by PLT stubs on s390.
126                module_arch = module['arch']
127                plt_offset = int(module_arch['plt_offset'])
128                plt_size = int(module_arch['plt_size'])
129                module_addr = hex(int(module_addr, 0) + plt_offset + plt_size)
130            gdb.write("loading @{addr}: {filename}\n".format(
131                addr=module_addr, filename=module_file))
132            cmdline = "add-symbol-file {filename} {sections}".format(
133                filename=module_file,
134                sections=self._section_arguments(module, module_addr))
135            gdb.execute(cmdline, to_string=True)
136            if module_name not in self.loaded_modules:
137                self.loaded_modules.append(module_name)
138        else:
139            gdb.write("no module object found for '{0}'\n".format(module_name))
140
141    def load_all_symbols(self):
142        gdb.write("loading vmlinux\n")
143
144        # Dropping symbols will disable all breakpoints. So save their states
145        # and restore them afterward.
146        saved_states = []
147        if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None:
148            for bp in gdb.breakpoints():
149                saved_states.append({'breakpoint': bp, 'enabled': bp.enabled})
150
151        # drop all current symbols and reload vmlinux
152        orig_vmlinux = 'vmlinux'
153        for obj in gdb.objfiles():
154            if (obj.filename.endswith('vmlinux') or
155                obj.filename.endswith('vmlinux.debug')):
156                orig_vmlinux = obj.filename
157        gdb.execute("symbol-file", to_string=True)
158        gdb.execute("symbol-file {0}".format(orig_vmlinux))
159
160        self.loaded_modules = []
161        module_list = modules.module_list()
162        if not module_list:
163            gdb.write("no modules found\n")
164        else:
165            [self.load_module_symbols(module) for module in module_list]
166
167        for saved_state in saved_states:
168            saved_state['breakpoint'].enabled = saved_state['enabled']
169
170    def invoke(self, arg, from_tty):
171        self.module_paths = [os.path.abspath(os.path.expanduser(p))
172                             for p in arg.split()]
173        self.module_paths.append(os.getcwd())
174
175        # enforce update
176        self.module_files = []
177        self.module_files_updated = False
178
179        self.load_all_symbols()
180
181        if hasattr(gdb, 'Breakpoint'):
182            if self.breakpoint is not None:
183                self.breakpoint.delete()
184                self.breakpoint = None
185            self.breakpoint = LoadModuleBreakpoint(
186                "kernel/module/main.c:do_init_module", self)
187        else:
188            gdb.write("Note: symbol update on module loading not supported "
189                      "with this gdb version\n")
190
191
192LxSymbols()
193