xref: /linux-6.15/scripts/gdb/linux/symbols.py (revision 61ba93b4)
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 <[email protected]>
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 = ".*/{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, module_file=None):
115        module_name = module['name'].string()
116        module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0]
117
118        if not module_file:
119            module_file = self._get_module_file(module_name)
120        if not module_file and not self.module_files_updated:
121            self._update_module_files()
122            module_file = self._get_module_file(module_name)
123
124        if module_file:
125            if utils.is_target_arch('s390'):
126                # Module text is preceded by PLT stubs on s390.
127                module_arch = module['arch']
128                plt_offset = int(module_arch['plt_offset'])
129                plt_size = int(module_arch['plt_size'])
130                module_addr = hex(int(module_addr, 0) + plt_offset + plt_size)
131            gdb.write("loading @{addr}: {filename}\n".format(
132                addr=module_addr, filename=module_file))
133            cmdline = "add-symbol-file {filename} {sections}".format(
134                filename=module_file,
135                sections=self._section_arguments(module, module_addr))
136            gdb.execute(cmdline, to_string=True)
137            if module_name not in self.loaded_modules:
138                self.loaded_modules.append(module_name)
139        else:
140            gdb.write("no module object found for '{0}'\n".format(module_name))
141
142    def load_ko_symbols(self, mod_path):
143        self.loaded_modules = []
144        module_list = modules.module_list()
145
146        for module in module_list:
147            module_name = module['name'].string()
148            module_pattern = ".*/{0}\.ko(?:.debug)?$".format(
149                module_name.replace("_", r"[_\-]"))
150            if re.match(module_pattern, mod_path) and os.path.exists(mod_path):
151                self.load_module_symbols(module, mod_path)
152                return
153        raise gdb.GdbError("%s is not a valid .ko\n" % mod_path)
154
155    def load_all_symbols(self):
156        gdb.write("loading vmlinux\n")
157
158        # Dropping symbols will disable all breakpoints. So save their states
159        # and restore them afterward.
160        saved_states = []
161        if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None:
162            for bp in gdb.breakpoints():
163                saved_states.append({'breakpoint': bp, 'enabled': bp.enabled})
164
165        # drop all current symbols and reload vmlinux
166        orig_vmlinux = 'vmlinux'
167        for obj in gdb.objfiles():
168            if (obj.filename.endswith('vmlinux') or
169                obj.filename.endswith('vmlinux.debug')):
170                orig_vmlinux = obj.filename
171        gdb.execute("symbol-file", to_string=True)
172        gdb.execute("symbol-file {0}".format(orig_vmlinux))
173
174        self.loaded_modules = []
175        module_list = modules.module_list()
176        if not module_list:
177            gdb.write("no modules found\n")
178        else:
179            [self.load_module_symbols(module) for module in module_list]
180
181        for saved_state in saved_states:
182            saved_state['breakpoint'].enabled = saved_state['enabled']
183
184    def invoke(self, arg, from_tty):
185        self.module_paths = [os.path.abspath(os.path.expanduser(p))
186                             for p in arg.split()]
187        self.module_paths.append(os.getcwd())
188
189        # enforce update
190        self.module_files = []
191        self.module_files_updated = False
192
193        argv = gdb.string_to_argv(arg)
194        if len(argv) == 1:
195            self.load_ko_symbols(argv[0])
196            return
197
198        self.load_all_symbols()
199
200        if hasattr(gdb, 'Breakpoint'):
201            if self.breakpoint is not None:
202                self.breakpoint.delete()
203                self.breakpoint = None
204            self.breakpoint = LoadModuleBreakpoint(
205                "kernel/module/main.c:do_init_module", self)
206        else:
207            gdb.write("Note: symbol update on module loading not supported "
208                      "with this gdb version\n")
209
210
211LxSymbols()
212