Kieran Bingham | 2d061d9 | 2016-03-22 14:27:33 -0700 | [diff] [blame] | 1 | # |
| 2 | # gdb helper commands and functions for Linux kernel debugging |
| 3 | # |
| 4 | # Kernel proc information reader |
| 5 | # |
| 6 | # Copyright (c) 2016 Linaro Ltd |
| 7 | # |
| 8 | # Authors: |
| 9 | # Kieran Bingham <kieran.bingham@linaro.org> |
| 10 | # |
| 11 | # This work is licensed under the terms of the GNU GPL version 2. |
| 12 | # |
| 13 | |
| 14 | import gdb |
Kieran Bingham | c1a1539 | 2016-05-23 16:24:59 -0700 | [diff] [blame] | 15 | from linux import constants |
| 16 | from linux import utils |
| 17 | from linux import tasks |
| 18 | from linux import lists |
Peter Griffin | 821f744 | 2017-07-12 14:34:13 -0700 | [diff] [blame] | 19 | from struct import * |
Kieran Bingham | 2d061d9 | 2016-03-22 14:27:33 -0700 | [diff] [blame] | 20 | |
| 21 | |
Kieran Bingham | 72bf92e | 2016-03-22 14:27:36 -0700 | [diff] [blame] | 22 | class LxCmdLine(gdb.Command): |
| 23 | """ Report the Linux Commandline used in the current kernel. |
| 24 | Equivalent to cat /proc/cmdline on a running target""" |
| 25 | |
| 26 | def __init__(self): |
| 27 | super(LxCmdLine, self).__init__("lx-cmdline", gdb.COMMAND_DATA) |
| 28 | |
| 29 | def invoke(self, arg, from_tty): |
| 30 | gdb.write(gdb.parse_and_eval("saved_command_line").string() + "\n") |
| 31 | |
| 32 | LxCmdLine() |
| 33 | |
| 34 | |
Kieran Bingham | 2d061d9 | 2016-03-22 14:27:33 -0700 | [diff] [blame] | 35 | class LxVersion(gdb.Command): |
| 36 | """ Report the Linux Version of the current kernel. |
| 37 | Equivalent to cat /proc/version on a running target""" |
| 38 | |
| 39 | def __init__(self): |
| 40 | super(LxVersion, self).__init__("lx-version", gdb.COMMAND_DATA) |
| 41 | |
| 42 | def invoke(self, arg, from_tty): |
| 43 | # linux_banner should contain a newline |
| 44 | gdb.write(gdb.parse_and_eval("linux_banner").string()) |
| 45 | |
| 46 | LxVersion() |
Kieran Bingham | e7165a2 | 2016-05-23 16:24:56 -0700 | [diff] [blame] | 47 | |
| 48 | |
| 49 | # Resource Structure Printers |
| 50 | # /proc/iomem |
| 51 | # /proc/ioports |
| 52 | |
| 53 | def get_resources(resource, depth): |
| 54 | while resource: |
| 55 | yield resource, depth |
| 56 | |
| 57 | child = resource['child'] |
| 58 | if child: |
| 59 | for res, deep in get_resources(child, depth + 1): |
| 60 | yield res, deep |
| 61 | |
| 62 | resource = resource['sibling'] |
| 63 | |
| 64 | |
| 65 | def show_lx_resources(resource_str): |
| 66 | resource = gdb.parse_and_eval(resource_str) |
| 67 | width = 4 if resource['end'] < 0x10000 else 8 |
| 68 | # Iterate straight to the first child |
| 69 | for res, depth in get_resources(resource['child'], 0): |
| 70 | start = int(res['start']) |
| 71 | end = int(res['end']) |
| 72 | gdb.write(" " * depth * 2 + |
| 73 | "{0:0{1}x}-".format(start, width) + |
| 74 | "{0:0{1}x} : ".format(end, width) + |
| 75 | res['name'].string() + "\n") |
| 76 | |
| 77 | |
| 78 | class LxIOMem(gdb.Command): |
| 79 | """Identify the IO memory resource locations defined by the kernel |
| 80 | |
| 81 | Equivalent to cat /proc/iomem on a running target""" |
| 82 | |
| 83 | def __init__(self): |
| 84 | super(LxIOMem, self).__init__("lx-iomem", gdb.COMMAND_DATA) |
| 85 | |
| 86 | def invoke(self, arg, from_tty): |
| 87 | return show_lx_resources("iomem_resource") |
| 88 | |
| 89 | LxIOMem() |
| 90 | |
| 91 | |
| 92 | class LxIOPorts(gdb.Command): |
| 93 | """Identify the IO port resource locations defined by the kernel |
| 94 | |
| 95 | Equivalent to cat /proc/ioports on a running target""" |
| 96 | |
| 97 | def __init__(self): |
| 98 | super(LxIOPorts, self).__init__("lx-ioports", gdb.COMMAND_DATA) |
| 99 | |
| 100 | def invoke(self, arg, from_tty): |
| 101 | return show_lx_resources("ioport_resource") |
| 102 | |
| 103 | LxIOPorts() |
Kieran Bingham | c1a1539 | 2016-05-23 16:24:59 -0700 | [diff] [blame] | 104 | |
| 105 | |
| 106 | # Mount namespace viewer |
| 107 | # /proc/mounts |
| 108 | |
| 109 | def info_opts(lst, opt): |
| 110 | opts = "" |
| 111 | for key, string in lst.items(): |
| 112 | if opt & key: |
| 113 | opts += string |
| 114 | return opts |
| 115 | |
| 116 | |
| 117 | FS_INFO = {constants.LX_MS_SYNCHRONOUS: ",sync", |
| 118 | constants.LX_MS_MANDLOCK: ",mand", |
| 119 | constants.LX_MS_DIRSYNC: ",dirsync", |
| 120 | constants.LX_MS_NOATIME: ",noatime", |
| 121 | constants.LX_MS_NODIRATIME: ",nodiratime"} |
| 122 | |
| 123 | MNT_INFO = {constants.LX_MNT_NOSUID: ",nosuid", |
| 124 | constants.LX_MNT_NODEV: ",nodev", |
| 125 | constants.LX_MNT_NOEXEC: ",noexec", |
| 126 | constants.LX_MNT_NOATIME: ",noatime", |
| 127 | constants.LX_MNT_NODIRATIME: ",nodiratime", |
| 128 | constants.LX_MNT_RELATIME: ",relatime"} |
| 129 | |
| 130 | mount_type = utils.CachedType("struct mount") |
| 131 | mount_ptr_type = mount_type.get_type().pointer() |
| 132 | |
| 133 | |
| 134 | class LxMounts(gdb.Command): |
| 135 | """Report the VFS mounts of the current process namespace. |
| 136 | |
| 137 | Equivalent to cat /proc/mounts on a running target |
| 138 | An integer value can be supplied to display the mount |
| 139 | values of that process namespace""" |
| 140 | |
| 141 | def __init__(self): |
| 142 | super(LxMounts, self).__init__("lx-mounts", gdb.COMMAND_DATA) |
| 143 | |
| 144 | # Equivalent to proc_namespace.c:show_vfsmnt |
| 145 | # However, that has the ability to call into s_op functions |
| 146 | # whereas we cannot and must make do with the information we can obtain. |
| 147 | def invoke(self, arg, from_tty): |
| 148 | argv = gdb.string_to_argv(arg) |
| 149 | if len(argv) >= 1: |
| 150 | try: |
| 151 | pid = int(argv[0]) |
| 152 | except: |
| 153 | raise gdb.GdbError("Provide a PID as integer value") |
| 154 | else: |
| 155 | pid = 1 |
| 156 | |
| 157 | task = tasks.get_task_by_pid(pid) |
| 158 | if not task: |
| 159 | raise gdb.GdbError("Couldn't find a process with PID {}" |
| 160 | .format(pid)) |
| 161 | |
| 162 | namespace = task['nsproxy']['mnt_ns'] |
| 163 | if not namespace: |
| 164 | raise gdb.GdbError("No namespace for current process") |
| 165 | |
| 166 | for vfs in lists.list_for_each_entry(namespace['list'], |
| 167 | mount_ptr_type, "mnt_list"): |
| 168 | devname = vfs['mnt_devname'].string() |
| 169 | devname = devname if devname else "none" |
| 170 | |
| 171 | pathname = "" |
| 172 | parent = vfs |
| 173 | while True: |
| 174 | mntpoint = parent['mnt_mountpoint'] |
| 175 | pathname = utils.dentry_name(mntpoint) + pathname |
| 176 | if (parent == parent['mnt_parent']): |
| 177 | break |
| 178 | parent = parent['mnt_parent'] |
| 179 | |
| 180 | if (pathname == ""): |
| 181 | pathname = "/" |
| 182 | |
| 183 | superblock = vfs['mnt']['mnt_sb'] |
| 184 | fstype = superblock['s_type']['name'].string() |
| 185 | s_flags = int(superblock['s_flags']) |
| 186 | m_flags = int(vfs['mnt']['mnt_flags']) |
| 187 | rd = "ro" if (s_flags & constants.LX_MS_RDONLY) else "rw" |
| 188 | |
| 189 | gdb.write( |
| 190 | "{} {} {} {}{}{} 0 0\n" |
| 191 | .format(devname, |
| 192 | pathname, |
| 193 | fstype, |
| 194 | rd, |
| 195 | info_opts(FS_INFO, s_flags), |
| 196 | info_opts(MNT_INFO, m_flags))) |
| 197 | |
| 198 | LxMounts() |
Peter Griffin | 821f744 | 2017-07-12 14:34:13 -0700 | [diff] [blame] | 199 | |
| 200 | |
| 201 | class LxFdtDump(gdb.Command): |
| 202 | """Output Flattened Device Tree header and dump FDT blob to the filename |
| 203 | specified as the command argument. Equivalent to |
| 204 | 'cat /proc/fdt > fdtdump.dtb' on a running target""" |
| 205 | |
| 206 | def __init__(self): |
| 207 | super(LxFdtDump, self).__init__("lx-fdtdump", gdb.COMMAND_DATA, |
| 208 | gdb.COMPLETE_FILENAME) |
| 209 | |
| 210 | def fdthdr_to_cpu(self, fdt_header): |
| 211 | |
| 212 | fdt_header_be = ">IIIIIII" |
| 213 | fdt_header_le = "<IIIIIII" |
| 214 | |
| 215 | if utils.get_target_endianness() == 1: |
| 216 | output_fmt = fdt_header_le |
| 217 | else: |
| 218 | output_fmt = fdt_header_be |
| 219 | |
| 220 | return unpack(output_fmt, pack(fdt_header_be, |
| 221 | fdt_header['magic'], |
| 222 | fdt_header['totalsize'], |
| 223 | fdt_header['off_dt_struct'], |
| 224 | fdt_header['off_dt_strings'], |
| 225 | fdt_header['off_mem_rsvmap'], |
| 226 | fdt_header['version'], |
| 227 | fdt_header['last_comp_version'])) |
| 228 | |
| 229 | def invoke(self, arg, from_tty): |
| 230 | |
| 231 | if not constants.LX_CONFIG_OF: |
| 232 | raise gdb.GdbError("Kernel not compiled with CONFIG_OF\n") |
| 233 | |
| 234 | if len(arg) == 0: |
| 235 | filename = "fdtdump.dtb" |
| 236 | else: |
| 237 | filename = arg |
| 238 | |
| 239 | py_fdt_header_ptr = gdb.parse_and_eval( |
| 240 | "(const struct fdt_header *) initial_boot_params") |
| 241 | py_fdt_header = py_fdt_header_ptr.dereference() |
| 242 | |
| 243 | fdt_header = self.fdthdr_to_cpu(py_fdt_header) |
| 244 | |
| 245 | if fdt_header[0] != constants.LX_OF_DT_HEADER: |
| 246 | raise gdb.GdbError("No flattened device tree magic found\n") |
| 247 | |
| 248 | gdb.write("fdt_magic: 0x{:02X}\n".format(fdt_header[0])) |
| 249 | gdb.write("fdt_totalsize: 0x{:02X}\n".format(fdt_header[1])) |
| 250 | gdb.write("off_dt_struct: 0x{:02X}\n".format(fdt_header[2])) |
| 251 | gdb.write("off_dt_strings: 0x{:02X}\n".format(fdt_header[3])) |
| 252 | gdb.write("off_mem_rsvmap: 0x{:02X}\n".format(fdt_header[4])) |
| 253 | gdb.write("version: {}\n".format(fdt_header[5])) |
| 254 | gdb.write("last_comp_version: {}\n".format(fdt_header[6])) |
| 255 | |
| 256 | inf = gdb.inferiors()[0] |
| 257 | fdt_buf = utils.read_memoryview(inf, py_fdt_header_ptr, |
| 258 | fdt_header[1]).tobytes() |
| 259 | |
| 260 | try: |
| 261 | f = open(filename, 'wb') |
| 262 | except: |
| 263 | raise gdb.GdbError("Could not open file to dump fdt") |
| 264 | |
| 265 | f.write(fdt_buf) |
| 266 | f.close() |
| 267 | |
| 268 | gdb.write("Dumped fdt blob to " + filename + "\n") |
| 269 | |
| 270 | LxFdtDump() |