Commit | Line | Data |
---|---|---|
2d061d99 KB |
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 | |
c1a15399 KB |
15 | from linux import constants |
16 | from linux import utils | |
17 | from linux import tasks | |
18 | from linux import lists | |
821f7440 | 19 | from struct import * |
2d061d99 KB |
20 | |
21 | ||
72bf92ec KB |
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 | ||
2d061d99 KB |
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 | |
b058809b | 44 | gdb.write(gdb.parse_and_eval("(char *)linux_banner").string()) |
2d061d99 KB |
45 | |
46 | LxVersion() | |
e7165a2d KB |
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() | |
c1a15399 KB |
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() | |
821f7440 PG |
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() |