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
17import struct
18
19from itertools import count
20from linux import modules, utils, constants
21
22
23if hasattr(gdb, 'Breakpoint'):
24    class LoadModuleBreakpoint(gdb.Breakpoint):
25        def __init__(self, spec, gdb_command):
26            super(LoadModuleBreakpoint, self).__init__(spec, internal=True)
27            self.silent = True
28            self.gdb_command = gdb_command
29
30        def stop(self):
31            module = gdb.parse_and_eval("mod")
32            module_name = module['name'].string()
33            cmd = self.gdb_command
34
35            # enforce update if object file is not found
36            cmd.module_files_updated = False
37
38            # Disable pagination while reporting symbol (re-)loading.
39            # The console input is blocked in this context so that we would
40            # get stuck waiting for the user to acknowledge paged output.
41            show_pagination = gdb.execute("show pagination", to_string=True)
42            pagination = show_pagination.endswith("on.\n")
43            gdb.execute("set pagination off")
44
45            if module_name in cmd.loaded_modules:
46                gdb.write("refreshing all symbols to reload module "
47                          "'{0}'\n".format(module_name))
48                cmd.load_all_symbols()
49            else:
50                cmd.load_module_symbols(module)
51
52            # restore pagination state
53            gdb.execute("set pagination %s" % ("on" if pagination else "off"))
54
55            return False
56
57
58def get_vmcore_s390():
59    with utils.qemu_phy_mem_mode():
60        vmcore_info = 0x0e0c
61        paddr_vmcoreinfo_note = gdb.parse_and_eval("*(unsigned long long *)" +
62                                                   hex(vmcore_info))
63        inferior = gdb.selected_inferior()
64        elf_note = inferior.read_memory(paddr_vmcoreinfo_note, 12)
65        n_namesz, n_descsz, n_type = struct.unpack(">III", elf_note)
66        desc_paddr = paddr_vmcoreinfo_note + len(elf_note) + n_namesz + 1
67        return gdb.parse_and_eval("(char *)" + hex(desc_paddr)).string()
68
69
70def get_kerneloffset():
71    if utils.is_target_arch('s390'):
72        try:
73            vmcore_str = get_vmcore_s390()
74        except gdb.error as e:
75            gdb.write("{}\n".format(e))
76            return None
77        return utils.parse_vmcore(vmcore_str).kerneloffset
78    return None
79
80
81class LxSymbols(gdb.Command):
82    """(Re-)load symbols of Linux kernel and currently loaded modules.
83
84The kernel (vmlinux) is taken from the current working directly. Modules (.ko)
85are scanned recursively, starting in the same directory. Optionally, the module
86search path can be extended by a space separated list of paths passed to the
87lx-symbols command."""
88
89    module_paths = []
90    module_files = []
91    module_files_updated = False
92    loaded_modules = []
93    breakpoint = None
94
95    def __init__(self):
96        super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES,
97                                        gdb.COMPLETE_FILENAME)
98
99    def _update_module_files(self):
100        self.module_files = []
101        for path in self.module_paths:
102            gdb.write("scanning for modules in {0}\n".format(path))
103            for root, dirs, files in os.walk(path):
104                for name in files:
105                    if name.endswith(".ko") or name.endswith(".ko.debug"):
106                        self.module_files.append(root + "/" + name)
107        self.module_files_updated = True
108
109    def _get_module_file(self, module_name):
110        module_pattern = r".*/{0}\.ko(?:.debug)?$".format(
111            module_name.replace("_", r"[_\-]"))
112        for name in self.module_files:
113            if re.match(module_pattern, name) and os.path.exists(name):
114                return name
115        return None
116
117    def _section_arguments(self, module, module_addr):
118        try:
119            sect_attrs = module['sect_attrs'].dereference()
120        except gdb.error:
121            return str(module_addr)
122
123        section_name_to_address = {}
124        for i in count():
125            # this is a NULL terminated array
126            if sect_attrs['grp']['bin_attrs'][i] == 0x0:
127                break
128
129            attr = sect_attrs['grp']['bin_attrs'][i].dereference()
130            section_name_to_address[attr['attr']['name'].string()] = attr['private']
131
132        textaddr = section_name_to_address.get(".text", module_addr)
133        args = []
134        for section_name in [".data", ".data..read_mostly", ".rodata", ".bss",
135                             ".text.hot", ".text.unlikely"]:
136            address = section_name_to_address.get(section_name)
137            if address:
138                args.append(" -s {name} {addr}".format(
139                    name=section_name, addr=str(address)))
140        return "{textaddr} {sections}".format(
141            textaddr=textaddr, sections="".join(args))
142
143    def load_module_symbols(self, module):
144        module_name = module['name'].string()
145        module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0]
146
147        module_file = self._get_module_file(module_name)
148        if not module_file and not self.module_files_updated:
149            self._update_module_files()
150            module_file = self._get_module_file(module_name)
151
152        if module_file:
153            if utils.is_target_arch('s390'):
154                # Module text is preceded by PLT stubs on s390.
155                module_arch = module['arch']
156                plt_offset = int(module_arch['plt_offset'])
157                plt_size = int(module_arch['plt_size'])
158                module_addr = hex(int(module_addr, 0) + plt_offset + plt_size)
159            gdb.write("loading @{addr}: {filename}\n".format(
160                addr=module_addr, filename=module_file))
161            cmdline = "add-symbol-file {filename} {sections}".format(
162                filename=module_file,
163                sections=self._section_arguments(module, module_addr))
164            gdb.execute(cmdline, to_string=True)
165            if module_name not in self.loaded_modules:
166                self.loaded_modules.append(module_name)
167        else:
168            gdb.write("no module object found for '{0}'\n".format(module_name))
169
170    def load_all_symbols(self):
171        gdb.write("loading vmlinux\n")
172
173        # Dropping symbols will disable all breakpoints. So save their states
174        # and restore them afterward.
175        saved_states = []
176        if hasattr(gdb, 'breakpoints') and not gdb.breakpoints() is None:
177            for bp in gdb.breakpoints():
178                saved_states.append({'breakpoint': bp, 'enabled': bp.enabled})
179
180        # drop all current symbols and reload vmlinux
181        orig_vmlinux = 'vmlinux'
182        for obj in gdb.objfiles():
183            if (obj.filename.endswith('vmlinux') or
184                obj.filename.endswith('vmlinux.debug')):
185                orig_vmlinux = obj.filename
186        gdb.execute("symbol-file", to_string=True)
187        kerneloffset = get_kerneloffset()
188        if kerneloffset is None:
189            offset_arg = ""
190        else:
191            offset_arg = " -o " + hex(kerneloffset)
192        gdb.execute("symbol-file {0}{1}".format(orig_vmlinux, offset_arg))
193
194        self.loaded_modules = []
195        module_list = modules.module_list()
196        if not module_list:
197            gdb.write("no modules found\n")
198        else:
199            [self.load_module_symbols(module) for module in module_list]
200
201        for saved_state in saved_states:
202            saved_state['breakpoint'].enabled = saved_state['enabled']
203
204    def invoke(self, arg, from_tty):
205        self.module_paths = [os.path.abspath(os.path.expanduser(p))
206                             for p in arg.split()]
207        self.module_paths.append(os.getcwd())
208
209        # enforce update
210        self.module_files = []
211        self.module_files_updated = False
212
213        self.load_all_symbols()
214
215        if not modules.has_modules():
216            return
217
218        if hasattr(gdb, 'Breakpoint'):
219            if self.breakpoint is not None:
220                self.breakpoint.delete()
221                self.breakpoint = None
222            self.breakpoint = LoadModuleBreakpoint(
223                "kernel/module/main.c:do_init_module", self)
224        else:
225            gdb.write("Note: symbol update on module loading not supported "
226                      "with this gdb version\n")
227
228
229LxSymbols()
230