| # | |
| # gdb helper commands and functions for Linux kernel debugging | |
| # | |
| # Kernel proc information reader | |
| # | |
| # Copyright (c) 2016 Linaro Ltd | |
| # | |
| # Authors: | |
| # Kieran Bingham <kieran.bingham@linaro.org> | |
| # | |
| # This work is licensed under the terms of the GNU GPL version 2. | |
| # | |
| import gdb | |
| from linux import constants | |
| from linux import utils | |
| from linux import tasks | |
| from linux import lists | |
| from struct import * | |
| class LxCmdLine(gdb.Command): | |
| """ Report the Linux Commandline used in the current kernel. | |
| Equivalent to cat /proc/cmdline on a running target""" | |
| def __init__(self): | |
| super(LxCmdLine, self).__init__("lx-cmdline", gdb.COMMAND_DATA) | |
| def invoke(self, arg, from_tty): | |
| gdb.write(gdb.parse_and_eval("saved_command_line").string() + "\n") | |
| LxCmdLine() | |
| class LxVersion(gdb.Command): | |
| """ Report the Linux Version of the current kernel. | |
| Equivalent to cat /proc/version on a running target""" | |
| def __init__(self): | |
| super(LxVersion, self).__init__("lx-version", gdb.COMMAND_DATA) | |
| def invoke(self, arg, from_tty): | |
| # linux_banner should contain a newline | |
| gdb.write(gdb.parse_and_eval("(char *)linux_banner").string()) | |
| LxVersion() | |
| # Resource Structure Printers | |
| # /proc/iomem | |
| # /proc/ioports | |
| def get_resources(resource, depth): | |
| while resource: | |
| yield resource, depth | |
| child = resource['child'] | |
| if child: | |
| for res, deep in get_resources(child, depth + 1): | |
| yield res, deep | |
| resource = resource['sibling'] | |
| def show_lx_resources(resource_str): | |
| resource = gdb.parse_and_eval(resource_str) | |
| width = 4 if resource['end'] < 0x10000 else 8 | |
| # Iterate straight to the first child | |
| for res, depth in get_resources(resource['child'], 0): | |
| start = int(res['start']) | |
| end = int(res['end']) | |
| gdb.write(" " * depth * 2 + | |
| "{0:0{1}x}-".format(start, width) + | |
| "{0:0{1}x} : ".format(end, width) + | |
| res['name'].string() + "\n") | |
| class LxIOMem(gdb.Command): | |
| """Identify the IO memory resource locations defined by the kernel | |
| Equivalent to cat /proc/iomem on a running target""" | |
| def __init__(self): | |
| super(LxIOMem, self).__init__("lx-iomem", gdb.COMMAND_DATA) | |
| def invoke(self, arg, from_tty): | |
| return show_lx_resources("iomem_resource") | |
| LxIOMem() | |
| class LxIOPorts(gdb.Command): | |
| """Identify the IO port resource locations defined by the kernel | |
| Equivalent to cat /proc/ioports on a running target""" | |
| def __init__(self): | |
| super(LxIOPorts, self).__init__("lx-ioports", gdb.COMMAND_DATA) | |
| def invoke(self, arg, from_tty): | |
| return show_lx_resources("ioport_resource") | |
| LxIOPorts() | |
| # Mount namespace viewer | |
| # /proc/mounts | |
| def info_opts(lst, opt): | |
| opts = "" | |
| for key, string in lst.items(): | |
| if opt & key: | |
| opts += string | |
| return opts | |
| FS_INFO = {constants.LX_SB_SYNCHRONOUS: ",sync", | |
| constants.LX_SB_MANDLOCK: ",mand", | |
| constants.LX_SB_DIRSYNC: ",dirsync", | |
| constants.LX_SB_NOATIME: ",noatime", | |
| constants.LX_SB_NODIRATIME: ",nodiratime"} | |
| MNT_INFO = {constants.LX_MNT_NOSUID: ",nosuid", | |
| constants.LX_MNT_NODEV: ",nodev", | |
| constants.LX_MNT_NOEXEC: ",noexec", | |
| constants.LX_MNT_NOATIME: ",noatime", | |
| constants.LX_MNT_NODIRATIME: ",nodiratime", | |
| constants.LX_MNT_RELATIME: ",relatime"} | |
| mount_type = utils.CachedType("struct mount") | |
| mount_ptr_type = mount_type.get_type().pointer() | |
| class LxMounts(gdb.Command): | |
| """Report the VFS mounts of the current process namespace. | |
| Equivalent to cat /proc/mounts on a running target | |
| An integer value can be supplied to display the mount | |
| values of that process namespace""" | |
| def __init__(self): | |
| super(LxMounts, self).__init__("lx-mounts", gdb.COMMAND_DATA) | |
| # Equivalent to proc_namespace.c:show_vfsmnt | |
| # However, that has the ability to call into s_op functions | |
| # whereas we cannot and must make do with the information we can obtain. | |
| def invoke(self, arg, from_tty): | |
| argv = gdb.string_to_argv(arg) | |
| if len(argv) >= 1: | |
| try: | |
| pid = int(argv[0]) | |
| except gdb.error: | |
| raise gdb.GdbError("Provide a PID as integer value") | |
| else: | |
| pid = 1 | |
| task = tasks.get_task_by_pid(pid) | |
| if not task: | |
| raise gdb.GdbError("Couldn't find a process with PID {}" | |
| .format(pid)) | |
| namespace = task['nsproxy']['mnt_ns'] | |
| if not namespace: | |
| raise gdb.GdbError("No namespace for current process") | |
| gdb.write("{:^18} {:^15} {:>9} {} {} options\n".format( | |
| "mount", "super_block", "devname", "pathname", "fstype")) | |
| for vfs in lists.list_for_each_entry(namespace['list'], | |
| mount_ptr_type, "mnt_list"): | |
| devname = vfs['mnt_devname'].string() | |
| devname = devname if devname else "none" | |
| pathname = "" | |
| parent = vfs | |
| while True: | |
| mntpoint = parent['mnt_mountpoint'] | |
| pathname = utils.dentry_name(mntpoint) + pathname | |
| if (parent == parent['mnt_parent']): | |
| break | |
| parent = parent['mnt_parent'] | |
| if (pathname == ""): | |
| pathname = "/" | |
| superblock = vfs['mnt']['mnt_sb'] | |
| fstype = superblock['s_type']['name'].string() | |
| s_flags = int(superblock['s_flags']) | |
| m_flags = int(vfs['mnt']['mnt_flags']) | |
| rd = "ro" if (s_flags & constants.LX_SB_RDONLY) else "rw" | |
| gdb.write("{} {} {} {} {} {}{}{} 0 0\n".format( | |
| vfs.format_string(), superblock.format_string(), devname, | |
| pathname, fstype, rd, info_opts(FS_INFO, s_flags), | |
| info_opts(MNT_INFO, m_flags))) | |
| LxMounts() | |
| class LxFdtDump(gdb.Command): | |
| """Output Flattened Device Tree header and dump FDT blob to the filename | |
| specified as the command argument. Equivalent to | |
| 'cat /proc/fdt > fdtdump.dtb' on a running target""" | |
| def __init__(self): | |
| super(LxFdtDump, self).__init__("lx-fdtdump", gdb.COMMAND_DATA, | |
| gdb.COMPLETE_FILENAME) | |
| def fdthdr_to_cpu(self, fdt_header): | |
| fdt_header_be = ">IIIIIII" | |
| fdt_header_le = "<IIIIIII" | |
| if utils.get_target_endianness() == 1: | |
| output_fmt = fdt_header_le | |
| else: | |
| output_fmt = fdt_header_be | |
| return unpack(output_fmt, pack(fdt_header_be, | |
| fdt_header['magic'], | |
| fdt_header['totalsize'], | |
| fdt_header['off_dt_struct'], | |
| fdt_header['off_dt_strings'], | |
| fdt_header['off_mem_rsvmap'], | |
| fdt_header['version'], | |
| fdt_header['last_comp_version'])) | |
| def invoke(self, arg, from_tty): | |
| if not constants.LX_CONFIG_OF: | |
| raise gdb.GdbError("Kernel not compiled with CONFIG_OF\n") | |
| if len(arg) == 0: | |
| filename = "fdtdump.dtb" | |
| else: | |
| filename = arg | |
| py_fdt_header_ptr = gdb.parse_and_eval( | |
| "(const struct fdt_header *) initial_boot_params") | |
| py_fdt_header = py_fdt_header_ptr.dereference() | |
| fdt_header = self.fdthdr_to_cpu(py_fdt_header) | |
| if fdt_header[0] != constants.LX_OF_DT_HEADER: | |
| raise gdb.GdbError("No flattened device tree magic found\n") | |
| gdb.write("fdt_magic: 0x{:02X}\n".format(fdt_header[0])) | |
| gdb.write("fdt_totalsize: 0x{:02X}\n".format(fdt_header[1])) | |
| gdb.write("off_dt_struct: 0x{:02X}\n".format(fdt_header[2])) | |
| gdb.write("off_dt_strings: 0x{:02X}\n".format(fdt_header[3])) | |
| gdb.write("off_mem_rsvmap: 0x{:02X}\n".format(fdt_header[4])) | |
| gdb.write("version: {}\n".format(fdt_header[5])) | |
| gdb.write("last_comp_version: {}\n".format(fdt_header[6])) | |
| inf = gdb.inferiors()[0] | |
| fdt_buf = utils.read_memoryview(inf, py_fdt_header_ptr, | |
| fdt_header[1]).tobytes() | |
| try: | |
| f = open(filename, 'wb') | |
| except gdb.error: | |
| raise gdb.GdbError("Could not open file to dump fdt") | |
| f.write(fdt_buf) | |
| f.close() | |
| gdb.write("Dumped fdt blob to " + filename + "\n") | |
| LxFdtDump() |