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