xref: /linux/scripts/gdb/linux/symbols.py (revision 509d3f45847627f4c5cdce004c3ec79262b5239c)
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 atexit
15import gdb
16import os
17import re
18import struct
19
20from itertools import count
21from linux import bpf, constants, modules, utils
22
23
24if hasattr(gdb, 'Breakpoint'):
25    class LoadModuleBreakpoint(gdb.Breakpoint):
26        def __init__(self, spec, gdb_command):
27            super(LoadModuleBreakpoint, self).__init__(spec, internal=True)
28            self.silent = True
29            self.gdb_command = gdb_command
30
31        def stop(self):
32            module = gdb.parse_and_eval("mod")
33            module_name = module['name'].string()
34            cmd = self.gdb_command
35
36            # enforce update if object file is not found
37            cmd.module_files_updated = False
38
39            # Disable pagination while reporting symbol (re-)loading.
40            # The console input is blocked in this context so that we would
41            # get stuck waiting for the user to acknowledge paged output.
42            with utils.pagination_off():
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            return False
51
52
53def get_vmcore_s390():
54    with utils.qemu_phy_mem_mode():
55        vmcore_info = 0x0e0c
56        paddr_vmcoreinfo_note = gdb.parse_and_eval("*(unsigned long long *)" +
57                                                   hex(vmcore_info))
58        if paddr_vmcoreinfo_note == 0 or paddr_vmcoreinfo_note & 1:
59            # In the early boot case, extract vm_layout.kaslr_offset from the
60            # vmlinux image in physical memory.
61            if paddr_vmcoreinfo_note == 0:
62                kaslr_offset_phys = 0
63            else:
64                kaslr_offset_phys = paddr_vmcoreinfo_note - 1
65            with utils.pagination_off():
66                gdb.execute("symbol-file {0} -o {1}".format(
67                    utils.get_vmlinux(), hex(kaslr_offset_phys)))
68            kaslr_offset = gdb.parse_and_eval("vm_layout.kaslr_offset")
69            return "KERNELOFFSET=" + hex(kaslr_offset)[2:]
70        inferior = gdb.selected_inferior()
71        elf_note = inferior.read_memory(paddr_vmcoreinfo_note, 12)
72        n_namesz, n_descsz, n_type = struct.unpack(">III", elf_note)
73        desc_paddr = paddr_vmcoreinfo_note + len(elf_note) + n_namesz + 1
74        return gdb.parse_and_eval("(char *)" + hex(desc_paddr)).string()
75
76
77def get_kerneloffset():
78    if utils.is_target_arch('s390'):
79        try:
80            vmcore_str = get_vmcore_s390()
81        except gdb.error as e:
82            gdb.write("{}\n".format(e))
83            return None
84        return utils.parse_vmcore(vmcore_str).kerneloffset
85    return None
86
87
88def is_in_s390_decompressor():
89    # DAT is always off in decompressor. Use this as an indicator.
90    # Note that in the kernel, DAT can be off during kexec() or restart.
91    # Accept this imprecision in order to avoid complicating things.
92    # It is unlikely that someone will run lx-symbols at these points.
93    pswm = int(gdb.parse_and_eval("$pswm"))
94    return (pswm & 0x0400000000000000) == 0
95
96
97def skip_decompressor():
98    if utils.is_target_arch("s390"):
99        if is_in_s390_decompressor():
100            # The address of the jump_to_kernel function is statically placed
101            # into svc_old_psw.addr (see ipl_data.c); read it from there. DAT
102            # is off, so we do not need to care about lowcore relocation.
103            svc_old_pswa = 0x148
104            jump_to_kernel = int(gdb.parse_and_eval("*(unsigned long long *)" +
105                                                    hex(svc_old_pswa)))
106            gdb.execute("tbreak *" + hex(jump_to_kernel))
107            gdb.execute("continue")
108            while is_in_s390_decompressor():
109                gdb.execute("stepi")
110
111
112class LxSymbols(gdb.Command):
113    """(Re-)load symbols of Linux kernel and currently loaded modules.
114
115The kernel (vmlinux) is taken from the current working directly. Modules (.ko)
116are scanned recursively, starting in the same directory. Optionally, the module
117search path can be extended by a space separated list of paths passed to the
118lx-symbols command.
119
120When the -bpf flag is specified, symbols from the currently loaded BPF programs
121are loaded as well."""
122
123    module_paths = []
124    module_files = []
125    module_files_updated = False
126    loaded_modules = []
127    breakpoint = None
128    bpf_prog_monitor = None
129    bpf_ksym_monitor = None
130    bpf_progs = {}
131    # The remove-symbol-file command, even when invoked with -a, requires the
132    # respective object file to exist, so keep them around.
133    bpf_debug_objs = {}
134
135    def __init__(self):
136        super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES,
137                                        gdb.COMPLETE_FILENAME)
138        atexit.register(self.cleanup_bpf)
139
140    def _update_module_files(self):
141        self.module_files = []
142        for path in self.module_paths:
143            gdb.write("scanning for modules in {0}\n".format(path))
144            for root, dirs, files in os.walk(path):
145                for name in files:
146                    if name.endswith(".ko") or name.endswith(".ko.debug"):
147                        self.module_files.append(root + "/" + name)
148        self.module_files_updated = True
149
150    def _get_module_file(self, module_name):
151        module_pattern = r".*/{0}\.ko(?:.debug)?$".format(
152            module_name.replace("_", r"[_\-]"))
153        for name in self.module_files:
154            if re.match(module_pattern, name) and os.path.exists(name):
155                return name
156        return None
157
158    def _section_arguments(self, module, module_addr):
159        try:
160            sect_attrs = module['sect_attrs'].dereference()
161        except gdb.error:
162            return str(module_addr)
163
164        section_name_to_address = {}
165        for i in count():
166            # this is a NULL terminated array
167            if sect_attrs['grp']['bin_attrs'][i] == 0x0:
168                break
169
170            attr = sect_attrs['grp']['bin_attrs'][i].dereference()
171            section_name_to_address[attr['attr']['name'].string()] = attr['private']
172
173        textaddr = section_name_to_address.get(".text", module_addr)
174        args = []
175        for section_name in [".data", ".data..read_mostly", ".rodata", ".bss",
176                             ".text.hot", ".text.unlikely"]:
177            address = section_name_to_address.get(section_name)
178            if address:
179                args.append(" -s {name} {addr}".format(
180                    name=section_name, addr=str(address)))
181        return "{textaddr} {sections}".format(
182            textaddr=textaddr, sections="".join(args))
183
184    def load_module_symbols(self, module):
185        module_name = module['name'].string()
186        module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0]
187
188        module_file = self._get_module_file(module_name)
189        if not module_file and not self.module_files_updated:
190            self._update_module_files()
191            module_file = self._get_module_file(module_name)
192
193        if module_file:
194            if utils.is_target_arch('s390'):
195                # Module text is preceded by PLT stubs on s390.
196                module_arch = module['arch']
197                plt_offset = int(module_arch['plt_offset'])
198                plt_size = int(module_arch['plt_size'])
199                module_addr = hex(int(module_addr, 0) + plt_offset + plt_size)
200            gdb.write("loading @{addr}: {filename}\n".format(
201                addr=module_addr, filename=module_file))
202            cmdline = "add-symbol-file {filename} {sections}".format(
203                filename=module_file,
204                sections=self._section_arguments(module, module_addr))
205            gdb.execute(cmdline, to_string=True)
206            if module_name not in self.loaded_modules:
207                self.loaded_modules.append(module_name)
208        else:
209            gdb.write("no module object found for '{0}'\n".format(module_name))
210
211    def add_bpf_prog(self, prog):
212        if prog["jited"]:
213            self.bpf_progs[int(prog["bpf_func"])] = prog
214
215    def remove_bpf_prog(self, prog):
216        self.bpf_progs.pop(int(prog["bpf_func"]), None)
217
218    def add_bpf_ksym(self, ksym):
219        addr = int(ksym["start"])
220        name = bpf.get_ksym_name(ksym)
221        with utils.pagination_off():
222            gdb.write("loading @{addr}: {name}\n".format(
223                addr=hex(addr), name=name))
224            debug_obj = bpf.generate_debug_obj(ksym, self.bpf_progs.get(addr))
225            if debug_obj is None:
226                return
227            try:
228                cmdline = "add-symbol-file {obj} {addr}".format(
229                    obj=debug_obj.name, addr=hex(addr))
230                gdb.execute(cmdline, to_string=True)
231            except:
232                debug_obj.close()
233                raise
234            self.bpf_debug_objs[addr] = debug_obj
235
236    def remove_bpf_ksym(self, ksym):
237        addr = int(ksym["start"])
238        debug_obj = self.bpf_debug_objs.pop(addr, None)
239        if debug_obj is None:
240            return
241        try:
242            name = bpf.get_ksym_name(ksym)
243            gdb.write("unloading @{addr}: {name}\n".format(
244                addr=hex(addr), name=name))
245            cmdline = "remove-symbol-file {path}".format(path=debug_obj.name)
246            gdb.execute(cmdline, to_string=True)
247        finally:
248            debug_obj.close()
249
250    def cleanup_bpf(self):
251        self.bpf_progs = {}
252        while len(self.bpf_debug_objs) > 0:
253            self.bpf_debug_objs.popitem()[1].close()
254
255
256    def load_all_symbols(self):
257        gdb.write("loading vmlinux\n")
258
259        # Dropping symbols will disable all breakpoints. So save their states
260        # and restore them afterward.
261        saved_states = []
262        if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None:
263            for bp in gdb.breakpoints():
264                saved_states.append({'breakpoint': bp, 'enabled': bp.enabled})
265
266        # drop all current symbols and reload vmlinux
267        orig_vmlinux = utils.get_vmlinux()
268        gdb.execute("symbol-file", to_string=True)
269        kerneloffset = get_kerneloffset()
270        if kerneloffset is None:
271            offset_arg = ""
272        else:
273            offset_arg = " -o " + hex(kerneloffset)
274        gdb.execute("symbol-file {0}{1}".format(orig_vmlinux, offset_arg))
275
276        self.loaded_modules = []
277        module_list = modules.module_list()
278        if not module_list:
279            gdb.write("no modules found\n")
280        else:
281            [self.load_module_symbols(module) for module in module_list]
282
283        self.cleanup_bpf()
284        if self.bpf_prog_monitor is not None:
285            self.bpf_prog_monitor.notify_initial()
286        if self.bpf_ksym_monitor is not None:
287            self.bpf_ksym_monitor.notify_initial()
288
289        for saved_state in saved_states:
290            saved_state['breakpoint'].enabled = saved_state['enabled']
291
292    def invoke(self, arg, from_tty):
293        skip_decompressor()
294
295        monitor_bpf = False
296        self.module_paths = []
297        for p in arg.split():
298            if p == "-bpf":
299                monitor_bpf = True
300            else:
301                p.append(os.path.abspath(os.path.expanduser(p)))
302        self.module_paths.append(os.getcwd())
303
304        if self.breakpoint is not None:
305            self.breakpoint.delete()
306            self.breakpoint = None
307        if self.bpf_prog_monitor is not None:
308            self.bpf_prog_monitor.delete()
309            self.bpf_prog_monitor = None
310        if self.bpf_ksym_monitor is not None:
311            self.bpf_ksym_monitor.delete()
312            self.bpf_ksym_monitor = None
313
314        # enforce update
315        self.module_files = []
316        self.module_files_updated = False
317
318        self.load_all_symbols()
319
320        if not hasattr(gdb, 'Breakpoint'):
321            gdb.write("Note: symbol update on module and BPF loading not "
322                      "supported with this gdb version\n")
323            return
324
325        if modules.has_modules():
326            self.breakpoint = LoadModuleBreakpoint(
327                "kernel/module/main.c:do_init_module", self)
328
329        if monitor_bpf:
330            if constants.LX_CONFIG_BPF_SYSCALL:
331                self.bpf_prog_monitor = bpf.ProgMonitor(self.add_bpf_prog,
332                                                        self.remove_bpf_prog)
333            if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT:
334                self.bpf_ksym_monitor = bpf.KsymMonitor(self.add_bpf_ksym,
335                                                        self.remove_bpf_ksym)
336
337
338LxSymbols()
339