class CrashUtsnameCache(CrashCache): symvals = Symvals(['init_uts_ns']) def __init__(self) -> None: self._utsname_cache_dict: Dict[str, str] = dict() @property def utsname(self) -> gdb.Value: return self.symvals.init_uts_ns['name'] def _init_utsname_cache(self) -> None: d = self._utsname_cache_dict for field in self.utsname.type.fields(): val = self.utsname[field.name].string() d[field.name] = val @property def _utsname_cache(self) -> Dict[str, str]: if not self._utsname_cache_dict: self._init_utsname_cache() return self._utsname_cache_dict def _utsname_field(self, name: str) -> str: try: return self._utsname_cache[name] except KeyError: raise DelayedAttributeError(name) from None @property def sysname(self) -> str: return self._utsname_field('sysname') @property def nodename(self) -> str: return self._utsname_field('nodename') @property def release(self) -> str: return self._utsname_field('release') @property def version(self) -> str: return self._utsname_field('version') @property def machine(self) -> str: return self._utsname_field('machine') @property def domainname(self) -> str: return self._utsname_field('domainname')
class CrashKernelCache(CrashCache): symvals = Symvals(['avenrun']) _adjust_jiffies = False _reset_uptime = True _jiffies_dv = DelayedValue('jiffies') def __init__(self, config_cache: CrashConfigCache) -> None: CrashCache.__init__(self) self.config = config_cache self._hz = -1 self._uptime = timedelta(seconds=0) self._loadavg = "" @property def jiffies(self) -> int: v = self._jiffies_dv.get() return v @property def hz(self) -> int: if self._hz == -1: self._hz = int(self.config['HZ']) return self._hz def get_uptime(self) -> timedelta: return self.uptime @property def uptime(self) -> timedelta: if self._uptime == 0 or self._reset_uptime: uptime = self._adjusted_jiffies() // self.hz self._uptime = timedelta(seconds=uptime) self._reset_uptime = False return self._uptime @property def loadavg(self) -> str: if not self._loadavg: try: metrics = self._get_loadavg_values() self._loadavg = self._format_loadavg(metrics) except DelayedAttributeError: return "Unknown" return self._loadavg def _calculate_loadavg(self, metric: int) -> float: # The kernel needs to do fixed point trickery to calculate # a floating point average. We can just return a float. return round(int(metric) / (1 << 11), 2) def _format_loadavg(self, metrics: List[float]) -> str: out = [] for metric in metrics: out.append(str(metric)) return " ".join(out) def _get_loadavg_values(self) -> List[float]: metrics = [] for index in range(0, array_size(self.symvals.avenrun)): metrics.append(self._calculate_loadavg( self.symvals.avenrun[index])) return metrics @classmethod def set_jiffies(cls, value: int) -> None: cls._jiffies_dv.value = None cls._jiffies_dv.callback(value) cls._reset_uptime = True @classmethod # pylint: disable=unused-argument def setup_jiffies(cls, symbol: gdb.Symbol) -> bool: jiffies_sym = gdb.lookup_global_symbol('jiffies_64') if jiffies_sym: try: jiffies = int(jiffies_sym.value()) except gdb.MemoryError: return False cls._adjust_jiffies = True else: jiffies_sym = gdb.lookup_global_symbol('jiffies') if not jiffies_sym: return False jiffies = int(jiffies_sym.value()) cls._adjust_jiffies = False cls.set_jiffies(jiffies) return True def _adjusted_jiffies(self) -> int: if self._adjust_jiffies: return self.jiffies - (int(0x100000000) - 300 * self.hz) return self.jiffies
SymbolOrValue = Union[gdb.Value, gdb.Symbol] class PerCPUError(TypeError): """The passed object does not respond to a percpu pointer.""" _fmt = "{} does not correspond to a percpu pointer." def __init__(self, var: SymbolOrValue) -> None: super().__init__(self._fmt.format(var)) types = Types( ['void *', 'char *', 'struct pcpu_chunk', 'struct percpu_counter']) symvals = Symvals([ '__per_cpu_offset', 'pcpu_base_addr', 'pcpu_slot', 'pcpu_nr_slots', 'pcpu_group_offsets' ]) msymvals = MinimalSymvals(['__per_cpu_start', '__per_cpu_end']) class PerCPUState: """ Per-cpus come in a few forms: - "Array" of objects - "Array" of pointers to objects - Pointers to either of those If we want to get the typing right, we need to recognize each one and figure out what type to pass back. We do want to dereference pointer to a percpu but we don't want to dereference a percpu pointer.
from crash.util import find_member_variant from crash.util.symbols import Types, Symvals, TypeCallbacks from crash.util.symbols import SymbolCallbacks, MinimalSymbolCallbacks from crash.cache.syscache import config from crash.exceptions import DelayedAttributeError import gdb #TODO debuginfo won't tell us, depends on version? PAGE_MAPPING_ANON = 1 types = Types([ 'unsigned long', 'struct page', 'enum pageflags', 'enum zone_type', 'struct mem_section' ]) symvals = Symvals(['mem_section', 'max_pfn']) PageType = TypeVar('PageType', bound='Page') class Page: slab_cache_name = None slab_page_name = None compound_head_name = None vmemmap_base = 0xffffea0000000000 vmemmap: gdb.Value directmap_base = 0xffff880000000000 pageflags: Dict[str, int] = dict() PG_tail = -1 PG_slab = -1
MNT_NOATIME : "MNT_NOATIME", MNT_NODIRATIME : "MNT_NODIRATIME", MNT_RELATIME : "MNT_RELATIME", MNT_READONLY : "MNT_READONLY", } MNT_FLAGS_HIDDEN = { MNT_SHRINKABLE : "[MNT_SHRINKABLE]", MNT_WRITE_HOLD : "[MNT_WRITE_HOLD]", MNT_SHARED : "[MNT_SHARED]", MNT_UNBINDABLE : "[MNT_UNBINDABLE]", } MNT_FLAGS_HIDDEN.update(MNT_FLAGS) types = Types(['struct mount', 'struct vfsmount']) symvals = Symvals(['init_task']) class Mount: _for_each_mount: Callable[[Any, gdb.Value], Iterator[gdb.Value]] _init_fs_root: gdb.Value def _for_each_mount_nsproxy(self, task: gdb.Value) -> Iterator[gdb.Value]: """ An implementation of for_each_mount that uses the task's nsproxy to locate the mount namespace. See :ref:`for_each_mount` for more details. """ return list_for_each_entry(task['nsproxy']['mnt_ns']['list'], types.mount_type, 'mnt_list') @classmethod
class CrashKernel: """ Initialize a basic kernel semantic debugging session. This means that we load the following: - Kernel image symbol table (and debuginfo, if not integrated) relocated to the base offset used by kASLR - Kernel modules that were loaded on the the crashed system (again, with debuginfo if not integrated) - Percpu ranges used by kernel module - Architecture-specific details - Linux tasks populated into the GDB thread table If kernel module files and debuginfo cannot be located, backtraces may be incomplete if the addresses used by the modules are crossed. Percpu ranges will be properly loaded regardless. For arguments that accept paths to specify a base directory to be used, the entire directory structure will be read and cached to speed up subsequent searches. Still, reading large directory trees is a time consuming operation and being exact as possible will improve startup time. Args: root (None for defaults): The roots of trees to search for debuginfo files. When specified, all roots will be searched using the following arguments (including the absolute paths in the defaults if unspecified). Defaults to: / vmlinux_debuginfo (None for defaults): The location of the separate debuginfo file corresponding to the kernel being debugged. Defaults to: - <loaded kernel path>.debug - ./vmlinux-<kernel version>.debug - /usr/lib/debug/.build-id/xx/<build-id>.debug - /usr/lib/debug/<loaded kernel path>.debug - /usr/lib/debug/boot/<loaded kernel name>.debug - /usr/lib/debug/boot/vmlinux-<kernel version> module_path (None for defaults): The base directory to be used to search for kernel modules (e.g. module.ko) to be used to load symbols for the kernel being debugged. Defaults to: - ./modules - /lib/modules/<kernel-version> module_debuginfo_path (None for defaults): The base directory to search for debuginfo matching the kernel modules already loaded. Defaults to: - ./modules.debug - /usr/lib/debug/.build-id/xx/<build-id>.debug - /usr/lib/debug/lib/modules/<kernel-version> Raises: CrashKernelError: If the kernel debuginfo cannot be loaded. InvalidArgumentError: If any of the arguments are not None, str, or list of str """ types = Types(['char *']) symvals = Symvals(['init_task']) symbols = Symbols(['runqueues']) # pylint: disable=unused-argument def __init__(self, roots: PathSpecifier = None, vmlinux_debuginfo: PathSpecifier = None, module_path: PathSpecifier = None, module_debuginfo_path: PathSpecifier = None, verbose: bool = False, debug: bool = False) -> None: self.findmap: Dict[str, Dict[Any, Any]] = dict() self.modules_order: Dict[str, Dict[str, str]] = dict() obj = gdb.objfiles()[0] if not obj.filename: raise RuntimeError("loaded objfile has no filename???") kernel = os.path.basename(obj.filename) self.kernel = kernel self.version = self.extract_version() self._setup_roots(roots, verbose) self._setup_vmlinux_debuginfo(vmlinux_debuginfo, verbose) self._setup_module_path(module_path, verbose) self._setup_module_debuginfo_path(module_debuginfo_path, verbose) # We need separate debuginfo. Let's go find it. path_list = [] build_id_path = self.build_id_path(obj) if build_id_path: path_list.append(build_id_path) path_list += self.vmlinux_debuginfo if not obj.has_symbols(): print("Loading debug symbols for vmlinux") for path in path_list: try: obj.add_separate_debug_file(path) if obj.has_symbols(): break except gdb.error: pass if not obj.has_symbols(): raise CrashKernelError( "Couldn't locate debuginfo for {}".format(kernel)) self.vermagic = self.extract_vermagic() archname = obj.architecture.name() try: archclass = crash.arch.get_architecture(archname) except RuntimeError as e: raise CrashKernelError(str(e)) self.arch = archclass() self.target = crash.current_target() self.vmcore = self.target.kdump self.crashing_thread: Optional[gdb.InferiorThread] = None def _setup_roots(self, roots: PathSpecifier = None, verbose: bool = False) -> None: if roots is None: self.roots = ["/"] elif isinstance(roots, list) and roots and isinstance(roots[0], str): x = None for root in roots: if os.path.exists(root): if x is None: x = [root] else: x.append(root) else: print("root {} does not exist".format(root)) if x is None: x = ["/"] self.roots = x elif isinstance(roots, str): x = None if os.path.exists(roots): if x is None: x = [roots] else: x.append(roots) if x is None: x = ["/"] self.roots = x else: raise InvalidArgumentError( "roots must be None, str, or list of str") if verbose: print("roots={}".format(self.roots)) def _find_debuginfo_paths(self, variants: List[str]) -> List[str]: x: List[str] = list() for root in self.roots: for debug_path in ["", "usr/lib/debug"]: for variant in variants: path = os.path.join(root, debug_path, variant) if os.path.exists(path): x.append(path) return x def _setup_vmlinux_debuginfo(self, vmlinux_debuginfo: PathSpecifier = None, verbose: bool = False) -> None: if vmlinux_debuginfo is None: defaults = [ "{}.debug".format(self.kernel), "vmlinux-{}.debug".format(self.version), "boot/{}.debug".format(os.path.basename(self.kernel)), "boot/vmlinux-{}.debug".format(self.version), ] self.vmlinux_debuginfo = self._find_debuginfo_paths(defaults) elif (isinstance(vmlinux_debuginfo, list) and vmlinux_debuginfo and isinstance(vmlinux_debuginfo[0], str)): self.vmlinux_debuginfo = vmlinux_debuginfo elif isinstance(vmlinux_debuginfo, str): self.vmlinux_debuginfo = [vmlinux_debuginfo] else: raise InvalidArgumentError( "vmlinux_debuginfo must be None, str, or list of str") if verbose: print("vmlinux_debuginfo={}".format(self.vmlinux_debuginfo)) def _setup_module_path(self, module_path: PathSpecifier = None, verbose: bool = False) -> None: x: List[str] = [] if module_path is None: path = "modules" if os.path.exists(path): x.append(path) for root in self.roots: path = "{}/lib/modules/{}".format(root, self.version) if os.path.exists(path): x.append(path) self.module_path = x elif (isinstance(module_path, list) and isinstance(module_path[0], str)): for root in self.roots: for mpath in module_path: path = "{}/{}".format(root, mpath) if os.path.exists(path): x.append(path) self.module_path = x elif isinstance(module_path, str): if os.path.exists(module_path): x.append(module_path) self.module_path = x else: raise InvalidArgumentError( "module_path must be None, str, or list of str") if verbose: print("module_path={}".format(self.module_path)) def _setup_module_debuginfo_path( self, module_debuginfo_path: PathSpecifier = None, verbose: bool = False) -> None: x: List[str] = [] if module_debuginfo_path is None: defaults = [ "modules.debug", "lib/modules/{}".format(self.version), ] self.module_debuginfo_path = self._find_debuginfo_paths(defaults) elif (isinstance(module_debuginfo_path, list) and isinstance(module_debuginfo_path[0], str)): for root in self.roots: for mpath in module_debuginfo_path: path = "{}/{}".format(root, mpath) if os.path.exists(path): x.append(path) self.module_debuginfo_path = x elif isinstance(module_debuginfo_path, str): for root in self.roots: path = "{}/{}".format(root, module_debuginfo_path) if os.path.exists(path): x.append(path) self.module_debuginfo_path = x else: raise InvalidArgumentError( "module_debuginfo_path must be None, str, or list of str") if verbose: print("module_debuginfo_path={}".format( self.module_debuginfo_path)) # When working without a symbol table, we still need to be able # to resolve version information. def _get_minsymbol_as_string(self, name: str) -> str: sym = gdb.lookup_minimal_symbol(name) if sym is None: raise MissingSymbolError(name) val = sym.value() return val.address.cast(self.types.char_p_type).string() def extract_version(self) -> str: """ Returns the version from the loaded vmlinux If debuginfo is available, ``init_uts_ns`` will be used. Otherwise, it will be extracted from the version banner. Returns: str: The version text. """ try: uts = get_symbol_value('init_uts_ns') return uts['name']['release'].string() except (AttributeError, NameError, MissingSymbolError): pass banner = self._get_minsymbol_as_string('linux_banner') return banner.split(' ')[2] def extract_vermagic(self) -> str: """ Returns the vermagic from the loaded vmlinux Returns: str: The version text. """ try: magic = get_symbol_value('vermagic') return magic.string() except (AttributeError, NameError): pass return self._get_minsymbol_as_string('vermagic') def extract_modinfo_from_module(self, modpath: str) -> Dict[str, str]: """ Returns the modinfo from a module file Args: modpath: The path to the module file. Returns: dict: A dictionary containing the names and values of the modinfo variables. """ f = open(modpath, 'rb') elf = ELFFile(f) modinfo = elf.get_section_by_name('.modinfo') d = {} for line in modinfo.data().split(b'\x00'): val = line.decode('utf-8') if val: eq = val.index('=') d[val[0:eq]] = val[eq + 1:] del elf f.close() return d def _get_module_sections(self, module: gdb.Value) -> str: out = [] for (name, addr) in for_each_module_section(module): out.append("-s {} {:#x}".format(name, addr)) return " ".join(out) def _check_module_version(self, modpath: str, module: gdb.Value) -> None: modinfo = self.extract_modinfo_from_module(modpath) vermagic = modinfo.get('vermagic', None) if vermagic != self.vermagic: raise _ModVersionMismatchError(modpath, vermagic, self.vermagic) mi_srcversion = modinfo.get('srcversion', None) mod_srcversion = None if 'srcversion' in module.type: mod_srcversion = module['srcversion'].string() if mi_srcversion != mod_srcversion: raise _ModSourceVersionMismatchError(modpath, mi_srcversion, mod_srcversion) def load_modules(self, verbose: bool = False, debug: bool = False) -> None: """ Load modules (including debuginfo) into the crash session. This routine will attempt to locate modules and the corresponding debuginfo files, if separate, using the parameters defined when the CrashKernel object was initialized. Args: verbose (default=False): enable verbose output debug (default=False): enable even more verbose debugging output Raises: CrashKernelError: An error was encountered while loading a module. This does not include a failure to locate a module or its debuginfo. """ import crash.cache.syscache # pylint: disable=redefined-outer-name version = crash.cache.syscache.utsname.release print("Loading modules for {}".format(version), end='') if verbose: print(":", flush=True) failed = 0 loaded = 0 for module in for_each_module(): modname = "{}".format(module['name'].string()) modfname = "{}.ko".format(modname) found = False for path in self.module_path: try: modpath = self._find_module_file(modfname, path) except _NoMatchingFileError: continue try: self._check_module_version(modpath, module) except _ModinfoMismatchError as e: if verbose: print(str(e)) continue found = True if 'module_core' in module.type: addr = int(module['module_core']) else: addr = int(module['core_layout']['base']) if debug: print("Loading {} at {:#x}".format(modpath, addr)) elif verbose: print("Loading {} at {:#x}".format(modname, addr)) else: print(".", end='') sys.stdout.flush() sections = self._get_module_sections(module) percpu = int(module['percpu']) if percpu > 0: sections += " -s .data..percpu {:#x}".format(percpu) try: result = gdb.execute("add-symbol-file {} {:#x} {}".format( modpath, addr, sections), to_string=True) except gdb.error as e: raise CrashKernelError( "Error while loading module `{}': {}".format( modname, str(e))) if debug: print(result) objfile = gdb.lookup_objfile(modpath) if not objfile.has_symbols(): self._load_module_debuginfo(objfile, modpath, verbose) elif debug: print(" + has debug symbols") break if not found: if failed == 0: print() print("Couldn't find module file for {}".format(modname)) failed += 1 else: if not objfile.has_symbols(): print("Couldn't find debuginfo for {}".format(modname)) loaded += 1 if (loaded + failed) % 10 == 10: print(".", end='') sys.stdout.flush() print(" done. ({} loaded".format(loaded), end='') if failed: print(", {} failed)".format(failed)) else: print(")") # We shouldn't need this again, so why keep it around? del self.findmap self.findmap = {} def _normalize_modname(self, mod: str) -> str: return mod.replace('-', '_') def _cache_modules_order(self, path: str) -> None: self.modules_order[path] = dict() order = os.path.join(path, "modules.order") try: f = open(order) for line in f.readlines(): modpath = line.rstrip() modname = self._normalize_modname(os.path.basename(modpath)) if modname[:7] == "kernel/": modname = modname[7:] modpath = os.path.join(path, modpath) if os.path.exists(modpath): self.modules_order[path][modname] = modpath f.close() except OSError: pass def _get_module_path_from_modules_order(self, path: str, name: str) -> str: if not path in self.modules_order: self._cache_modules_order(path) try: return self.modules_order[path][name] except KeyError: raise _NoMatchingFileError(name) def _cache_file_tree(self, path: str, regex: Pattern[str] = None) -> None: if not path in self.findmap: self.findmap[path] = { 'filters': [], 'files': {}, } # If we've walked this path with no filters, we have everything # already. if self.findmap[path]['filters'] is None: return if regex is None: self.findmap[path]['filters'] = None else: pattern = regex.pattern if pattern in self.findmap[path]['filters']: return self.findmap[path]['filters'].append(pattern) # pylint: disable=unused-variable for root, dirs, files in os.walk(path): for filename in files: modname = self._normalize_modname(filename) if regex and regex.match(modname) is None: continue modpath = os.path.join(root, filename) self.findmap[path]['files'][modname] = modpath def _get_file_path_from_tree_search(self, path: str, name: str, regex: Pattern[str] = None) -> str: self._cache_file_tree(path, regex) try: modname = self._normalize_modname(name) return self.findmap[path]['files'][modname] except KeyError: raise _NoMatchingFileError(name) def _find_module_file(self, name: str, path: str) -> str: try: return self._get_module_path_from_modules_order(path, name) except _NoMatchingFileError: pass regex = re.compile(fnmatch.translate("*.ko")) return self._get_file_path_from_tree_search(path, name, regex) def _find_module_debuginfo_file(self, name: str, path: str) -> str: regex = re.compile(fnmatch.translate("*.ko.debug")) return self._get_file_path_from_tree_search(path, name, regex) @staticmethod def build_id_path(objfile: gdb.Objfile) -> Optional[str]: """ Returns the relative path for debuginfo using the objfile's build-id. Args: objfile: The objfile for which to return the path """ build_id = objfile.build_id if build_id is None: return None return ".build_id/{}/{}.debug".format(build_id[0:2], build_id[2:]) def _try_load_debuginfo(self, objfile: gdb.Objfile, path: str, verbose: bool = False) -> bool: if not os.path.exists(path): return False try: if verbose: print(" + Loading debuginfo: {}".format(path)) objfile.add_separate_debug_file(path) if objfile.has_symbols(): return True except gdb.error as e: print(e) return False def _load_module_debuginfo(self, objfile: gdb.Objfile, modpath: str = None, verbose: bool = False) -> None: if modpath is None: modpath = objfile.filename if modpath is None: raise RuntimeError("loaded objfile has no filename???") if ".gz" in modpath: modpath = modpath.replace(".gz", "") filename = "{}.debug".format(os.path.basename(modpath)) build_id_path = self.build_id_path(objfile) for path in self.module_debuginfo_path: if build_id_path: filepath = "{}/{}".format(path, build_id_path) if self._try_load_debuginfo(objfile, filepath, verbose): break try: filepath = self._find_module_debuginfo_file(filename, path) except _NoMatchingFileError: continue if self._try_load_debuginfo(objfile, filepath, verbose): break def setup_tasks(self) -> None: """ Populate GDB's thread list using the kernel's task lists This method will iterate over the kernel's task lists, create a LinuxTask object, and create a gdb thread for each one. The threads will be built so that the registers are ready to be populated, which allows symbolic stack traces to be made available. """ from crash.types.percpu import get_percpu_vars from crash.types.task import LinuxTask, for_each_all_tasks import crash.cache.tasks # pylint: disable=redefined-outer-name gdb.execute('set print thread-events 0') rqs = get_percpu_vars(self.symbols.runqueues) rqscurrs = {int(x["curr"]): k for (k, x) in rqs.items()} print("Loading tasks...", end='') sys.stdout.flush() task_count = 0 try: crashing_cpu = int(get_symbol_value('crashing_cpu')) except MissingSymbolError: crashing_cpu = -1 for task in for_each_all_tasks(): ltask = LinuxTask(task) active = int(task.address) in rqscurrs if active: cpu = rqscurrs[int(task.address)] regs = self.vmcore.attr.cpu[cpu].reg ltask.set_active(cpu, regs) ptid = (LINUX_KERNEL_PID, task['pid'], 0) try: thread = gdb.selected_inferior().new_thread(ptid, ltask) except gdb.error: print("Failed to setup task @{:#x}".format(int(task.address))) continue thread.name = task['comm'].string() if active and cpu == crashing_cpu: self.crashing_thread = thread self.arch.setup_thread_info(thread) ltask.attach_thread(thread) ltask.set_get_stack_pointer(self.arch.get_stack_pointer) crash.cache.tasks.cache_task(ltask) task_count += 1 if task_count % 100 == 0: print(".", end='') sys.stdout.flush() print(" done. ({} tasks total)".format(task_count)) gdb.selected_inferior().executing = False
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: from typing import Iterator, Callable, Dict, List from crash.exceptions import InvalidArgumentError, ArgumentTypeError from crash.exceptions import UnexpectedGDBTypeError from crash.util import array_size, struct_has_member from crash.util.symbols import Types, Symvals, SymbolCallbacks from crash.types.list import list_for_each_entry import gdb PF_EXITING = 0x4 types = Types(['struct task_struct', 'struct mm_struct', 'atomic_long_t']) symvals = Symvals(['init_task', 'init_mm']) # This is pretty painful. These are all #defines so none of them end # up with symbols in the kernel. The best approximation we have is # task_state_array which doesn't include all of them. All we can do # is make some assumptions based on the changes upstream. This will # be fragile. class TaskStateFlags: """ A class to contain state related to discovering task flag values. Not meant to be instantiated. The initial values below are overridden once symbols are available to resolve them properly. """
class Test(object): symvals = Symvals(['test_struct'])
# -*- coding: utf-8 -*- # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: from typing import Iterable, Tuple import gdb from crash.types.list import list_for_each_entry from crash.util.symbols import Symvals, Types symvals = Symvals(['modules']) types = Types(['struct module']) def for_each_module() -> Iterable[gdb.Value]: """ Iterate over each module in the modules list Yields: :obj:`gdb.Value`: The next module on the list. The value is of type ``struct module``. """ for module in list_for_each_entry(symvals.modules, types.module_type, 'list'): yield module def for_each_module_section(module: gdb.Value) -> Iterable[Tuple[str, int]]: """ Iterate over each ELF section in a loaded module This routine iterates over the ``sect_attrs`` member of the
# -*- coding: utf-8 -*- # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: from typing import Iterable, Union from crash.util import container_of, get_typed_pointer, decode_flags from crash.util.symbols import Types, Symvals from crash.infra.lookup import DelayedSymval, DelayedType from crash.types.list import list_for_each_entry from crash.subsystem.storage import block_device_name import gdb types = Types('struct super_block') symvals = Symvals('super_blocks') AddressSpecifier = Union[int, str, gdb.Value] MS_RDONLY = 1 MS_NOSUID = 2 MS_NODEV = 4 MS_NOEXEC = 8 MS_SYNCHRONOUS = 16 MS_REMOUNT = 32 MS_MANDLOCK = 64 MS_DIRSYNC = 128 MS_NOATIME = 1024 MS_NODIRATIME = 2048 MS_BIND = 4096 MS_MOVE = 8192 MS_REC = 16384
The crash.types.node module offers helpers to work with NUMA nodes. """ from typing import Iterable, List, Type, TypeVar import crash from crash.util.symbols import Symbols, Symvals, Types, SymbolCallbacks from crash.types.percpu import get_percpu_var from crash.types.bitmap import for_each_set_bit from crash.exceptions import DelayedAttributeError import crash.types.zone import gdb symbols = Symbols(['numa_node']) symvals = Symvals(['numa_cpu_lookup_table', 'node_data']) types = Types(['pg_data_t', 'struct zone']) def numa_node_id(cpu: int) -> int: """ Return the NUMA node ID for a given CPU Args: cpu: The CPU number to obtain the NUMA node ID Returns: :obj:`int`: The NUMA node ID for the specified CPU. """ if crash.current_target().arch.name() == "powerpc:common64": return int(symvals.numa_cpu_lookup_table[cpu]) return int(get_percpu_var(symbols.numa_node, cpu))
#!/usr/bin/python3 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: from typing import Tuple import gdb from crash.util import get_minsymbol_value, get_minsymbol_pointer, \ get_minsymbol_addr, get_typed_pointer from crash.util.symbols import Types, Symvals from crash.types.module import for_each_module from crash.cache.syscache import config_enabled symvals = Symvals(['mod_tree']) types = Types(['unsigned long', 'unsigned int', 'int', 'u8', 'u16', 'char *']) class Kallsyms: _config_setup_done = False _CONFIG_KALLSYMS_BASE_RELATIVE: bool _CONFIG_KALLSYMS_ABSOLUTE_PERCPU: bool _stext_addr: int _end_addr: int module_addr_min: int module_addr_max: int kallsyms_num_syms: int kallsyms_relative_base: int
""" from typing import Dict, Iterable, Any import re import argparse import gdb from crash.commands import Command, ArgumentParser, CommandError from crash.exceptions import DelayedAttributeError from crash.util.symbols import Types, Symvals types = Types(['struct printk_log *', 'char *']) symvals = Symvals([ 'log_buf', 'log_buf_len', 'log_first_idx', 'log_next_idx', 'clear_seq', 'log_first_seq', 'log_next_seq' ]) class LogTypeException(Exception): pass class LogInvalidOption(Exception): pass class LogCommand(Command): """dump system message buffer""" def __init__(self, name: str) -> None: parser = ArgumentParser(prog=name)
from typing import Iterable from crash.util import container_of from crash.util.symbols import Types, Symvals, SymbolCallbacks, TypeCallbacks from crash.types.classdev import for_each_class_device from crash.exceptions import DelayedAttributeError, InvalidArgumentError import gdb from gdb.types import get_basic_type types = Types([ 'struct gendisk', 'struct hd_struct', 'struct device', 'struct device_type', 'struct bdev_inode' ]) symvals = Symvals( ['block_class', 'blockdev_superblock', 'disk_type', 'part_type']) def dev_to_gendisk(dev: gdb.Value) -> gdb.Value: """ Converts a ``struct device`` that is embedded in a ``struct gendisk`` back to the ``struct gendisk``. Args: dev: A ``struct device`` contained within a ``struct gendisk``. The value must be of type ``struct device``. Returns: :obj:`gdb.Value`: The converted gendisk. The value is of type ``struct gendisk``. """
#!/usr/bin/python3 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: import gdb from crash.util.symbols import Types, Symvals from crash.types.kallsyms import kallsyms_lookup types = Types(['union handle_parts', 'struct stack_record']) symvals = Symvals(['stack_slabs']) # TODO not sure how to determine this from the dump STACK_ALLOC_ALIGN = 4 class StackTrace: def __init__(self, nr_entries: int, entries: gdb.Value) -> None: self.nr_entries = nr_entries self.entries = entries def dump(self, prefix: str = "") -> None: for i in range(self.nr_entries): addr = int(self.entries[i]) sym = kallsyms_lookup(addr) print(f"{prefix}0x{addr:x} {sym}") @classmethod def from_handle(cls, handle: gdb.Value) -> 'StackTrace': parts = handle.address.cast(types.union_handle_parts_type.pointer())
class CrashConfigCache(CrashCache): types = Types(['char *']) symvals = Symvals(['kernel_config_data']) msymvals = MinimalSymvals(['kernel_config_data', 'kernel_config_data_end']) def __init__(self) -> None: self._config_buffer = "" self._ikconfig_cache: Dict[str, str] = dict() @property def config_buffer(self) -> str: if not self._config_buffer: self._config_buffer = self._decompress_config_buffer() return self._config_buffer @property def ikconfig_cache(self) -> Dict[str, str]: if not self._ikconfig_cache: self._parse_config() return self._ikconfig_cache def __getitem__(self, name: str) -> Any: try: return self.ikconfig_cache[name] except KeyError: return None @staticmethod def _read_buf_bytes(address: int, size: int) -> bytes: return gdb.selected_inferior().read_memory(address, size).tobytes() def _locate_config_buffer_section(self) -> ImageLocation: data_start = int(self.msymvals.kernel_config_data) data_end = int(self.msymvals.kernel_config_data_end) return { 'data': { 'start': data_start, 'size': data_end - data_start, }, 'magic': { 'start': data_start - 8, 'end': data_end, }, } def _locate_config_buffer_typed(self) -> ImageLocation: start = int(self.symvals.kernel_config_data.address) end = start + self.symvals.kernel_config_data.type.sizeof return { 'data': { 'start': start + 8, 'size': end - start - 2 * 8 - 1, }, 'magic': { 'start': start, 'end': end - 8 - 1, }, } def _verify_image(self, location: ImageLocation) -> None: magic_start = b'IKCFG_ST' magic_end = b'IKCFG_ED' buf_len = len(magic_start) buf = self._read_buf_bytes(location['magic']['start'], buf_len) if buf != magic_start: raise IOError( f"Missing magic_start in kernel_config_data. Got `{buf!r}'") buf_len = len(magic_end) buf = self._read_buf_bytes(location['magic']['end'], buf_len) if buf != magic_end: raise IOError( "Missing magic_end in kernel_config_data. Got `{buf}'") def _decompress_config_buffer(self) -> str: try: location = self._locate_config_buffer_section() except DelayedAttributeError: location = self._locate_config_buffer_typed() self._verify_image(location) # Read the compressed data buf = self._read_buf_bytes(location['data']['start'], location['data']['size']) return zlib.decompress(buf, 16 + zlib.MAX_WBITS).decode('utf-8') def __str__(self) -> str: return self.config_buffer def _parse_config(self) -> None: for line in self.config_buffer.splitlines(): # bin comments line = re.sub("#.*$", "", line).strip() if not line: continue m = re.match("CONFIG_([^=]*)=(.*)", line) if m: self._ikconfig_cache[m.group(1)] = m.group(2)
from crash.util.symbols import Types, Symvals, TypeCallbacks from crash.util.symbols import SymbolCallbacks, MinimalSymbolCallbacks from crash.cache.syscache import config from crash.exceptions import DelayedAttributeError from crash.types.stack_depot import StackTrace from crash.util import get_symbol_value #TODO debuginfo won't tell us, depends on version? PAGE_MAPPING_ANON = 1 types = Types([ 'unsigned long', 'struct page', 'enum pageflags', 'enum zone_type', 'struct mem_section', 'enum page_ext_flags', 'struct page_owner', 'struct page_ext' ]) symvals = Symvals( ['mem_section', 'max_pfn', 'page_ext_size', 'extra_mem', 'page_owner_ops']) PageType = TypeVar('PageType', bound='Page') _PAGE_FLAGS_CHECK_AT_FREE = \ ["PG_lru", "PG_locked", "PG_private", "PG_private_2", "PG_writeback", "PG_reserved", "PG_slab", "PG_active", "PG_unevictable", "PG_mlocked"] gfpflag_names = list() pageflag_names = list() pagetype_names = [(0x00000080, 'buddy'), (0x00000100, 'offline'), (0x00000200, 'kmemcg'), (0x00000400, 'pgtable'), (0x00000800, 'guard')] class Page: