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