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