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