Commit | Line | Data |
---|---|---|
e36903b0 DB |
1 | # SPDX-License-Identifier: GPL-2.0-only |
2 | # | |
3 | # gdb helper commands and functions for Linux kernel debugging | |
4 | # | |
5 | # routines to introspect page table | |
6 | # | |
7 | # Authors: | |
8 | # Dmitrii Bundin <dmitrii.bundin.a@gmail.com> | |
9 | # | |
10 | ||
11 | import gdb | |
12 | ||
13 | from linux import utils | |
14 | ||
15 | PHYSICAL_ADDRESS_MASK = gdb.parse_and_eval('0xfffffffffffff') | |
16 | ||
17 | ||
18 | def page_mask(level=1): | |
19 | # 4KB | |
20 | if level == 1: | |
21 | return gdb.parse_and_eval('(u64) ~0xfff') | |
22 | # 2MB | |
23 | elif level == 2: | |
24 | return gdb.parse_and_eval('(u64) ~0x1fffff') | |
25 | # 1GB | |
26 | elif level == 3: | |
27 | return gdb.parse_and_eval('(u64) ~0x3fffffff') | |
28 | else: | |
29 | raise Exception(f'Unknown page level: {level}') | |
30 | ||
31 | ||
32 | #page_offset_base in case CONFIG_DYNAMIC_MEMORY_LAYOUT is disabled | |
33 | POB_NO_DYNAMIC_MEM_LAYOUT = '0xffff888000000000' | |
34 | def _page_offset_base(): | |
35 | pob_symbol = gdb.lookup_global_symbol('page_offset_base') | |
36 | pob = pob_symbol.name if pob_symbol else POB_NO_DYNAMIC_MEM_LAYOUT | |
37 | return gdb.parse_and_eval(pob) | |
38 | ||
39 | ||
40 | def is_bit_defined_tupled(data, offset): | |
41 | return offset, bool(data >> offset & 1) | |
42 | ||
43 | def content_tupled(data, bit_start, bit_end): | |
44 | return (bit_start, bit_end), data >> bit_start & ((1 << (1 + bit_end - bit_start)) - 1) | |
45 | ||
46 | def entry_va(level, phys_addr, translating_va): | |
47 | def start_bit(level): | |
48 | if level == 5: | |
49 | return 48 | |
50 | elif level == 4: | |
51 | return 39 | |
52 | elif level == 3: | |
53 | return 30 | |
54 | elif level == 2: | |
55 | return 21 | |
56 | elif level == 1: | |
57 | return 12 | |
58 | else: | |
59 | raise Exception(f'Unknown level {level}') | |
60 | ||
61 | entry_offset = ((translating_va >> start_bit(level)) & 511) * 8 | |
62 | entry_va = _page_offset_base() + phys_addr + entry_offset | |
63 | return entry_va | |
64 | ||
65 | class Cr3(): | |
66 | def __init__(self, cr3, page_levels): | |
67 | self.cr3 = cr3 | |
68 | self.page_levels = page_levels | |
69 | self.page_level_write_through = is_bit_defined_tupled(cr3, 3) | |
70 | self.page_level_cache_disabled = is_bit_defined_tupled(cr3, 4) | |
71 | self.next_entry_physical_address = cr3 & PHYSICAL_ADDRESS_MASK & page_mask() | |
72 | ||
73 | def next_entry(self, va): | |
74 | next_level = self.page_levels | |
75 | return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level) | |
76 | ||
77 | def mk_string(self): | |
78 | return f"""\ | |
79 | cr3: | |
80 | {'cr3 binary data': <30} {hex(self.cr3)} | |
81 | {'next entry physical address': <30} {hex(self.next_entry_physical_address)} | |
82 | --- | |
83 | {'bit' : <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} | |
84 | {'bit' : <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} | |
85 | """ | |
86 | ||
87 | ||
88 | class PageHierarchyEntry(): | |
89 | def __init__(self, address, level): | |
90 | data = int.from_bytes( | |
91 | memoryview(gdb.selected_inferior().read_memory(address, 8)), | |
92 | "little" | |
93 | ) | |
94 | if level == 1: | |
95 | self.is_page = True | |
96 | self.entry_present = is_bit_defined_tupled(data, 0) | |
97 | self.read_write = is_bit_defined_tupled(data, 1) | |
98 | self.user_access_allowed = is_bit_defined_tupled(data, 2) | |
99 | self.page_level_write_through = is_bit_defined_tupled(data, 3) | |
100 | self.page_level_cache_disabled = is_bit_defined_tupled(data, 4) | |
101 | self.entry_was_accessed = is_bit_defined_tupled(data, 5) | |
102 | self.dirty = is_bit_defined_tupled(data, 6) | |
103 | self.pat = is_bit_defined_tupled(data, 7) | |
104 | self.global_translation = is_bit_defined_tupled(data, 8) | |
105 | self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) | |
106 | self.next_entry_physical_address = None | |
107 | self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11) | |
108 | self.protection_key = content_tupled(data, 59, 62) | |
109 | self.executed_disable = is_bit_defined_tupled(data, 63) | |
110 | else: | |
111 | page_size = is_bit_defined_tupled(data, 7) | |
112 | page_size_bit = page_size[1] | |
113 | self.is_page = page_size_bit | |
114 | self.entry_present = is_bit_defined_tupled(data, 0) | |
115 | self.read_write = is_bit_defined_tupled(data, 1) | |
116 | self.user_access_allowed = is_bit_defined_tupled(data, 2) | |
117 | self.page_level_write_through = is_bit_defined_tupled(data, 3) | |
118 | self.page_level_cache_disabled = is_bit_defined_tupled(data, 4) | |
119 | self.entry_was_accessed = is_bit_defined_tupled(data, 5) | |
120 | self.page_size = page_size | |
121 | self.dirty = is_bit_defined_tupled( | |
122 | data, 6) if page_size_bit else None | |
123 | self.global_translation = is_bit_defined_tupled( | |
124 | data, 8) if page_size_bit else None | |
125 | self.pat = is_bit_defined_tupled( | |
126 | data, 12) if page_size_bit else None | |
127 | self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) if page_size_bit else None | |
128 | self.next_entry_physical_address = None if page_size_bit else data & PHYSICAL_ADDRESS_MASK & page_mask() | |
129 | self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11) | |
130 | self.protection_key = content_tupled(data, 59, 62) if page_size_bit else None | |
131 | self.executed_disable = is_bit_defined_tupled(data, 63) | |
132 | self.address = address | |
133 | self.page_entry_binary_data = data | |
134 | self.page_hierarchy_level = level | |
135 | ||
136 | def next_entry(self, va): | |
137 | if self.is_page or not self.entry_present[1]: | |
138 | return None | |
139 | ||
140 | next_level = self.page_hierarchy_level - 1 | |
141 | return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level) | |
142 | ||
143 | ||
144 | def mk_string(self): | |
145 | if not self.entry_present[1]: | |
146 | return f"""\ | |
147 | level {self.page_hierarchy_level}: | |
148 | {'entry address': <30} {hex(self.address)} | |
149 | {'page entry binary data': <30} {hex(self.page_entry_binary_data)} | |
150 | --- | |
151 | PAGE ENTRY IS NOT PRESENT! | |
152 | """ | |
153 | elif self.is_page: | |
154 | def page_size_line(ps_bit, ps, level): | |
155 | return "" if level == 1 else f"{'bit': <3} {ps_bit: <5} {'page size': <30} {ps}" | |
156 | ||
157 | return f"""\ | |
158 | level {self.page_hierarchy_level}: | |
159 | {'entry address': <30} {hex(self.address)} | |
160 | {'page entry binary data': <30} {hex(self.page_entry_binary_data)} | |
161 | {'page size': <30} {'1GB' if self.page_hierarchy_level == 3 else '2MB' if self.page_hierarchy_level == 2 else '4KB' if self.page_hierarchy_level == 1 else 'Unknown page size for level:' + self.page_hierarchy_level} | |
162 | {'page physical address': <30} {hex(self.page_physical_address)} | |
163 | --- | |
164 | {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]} | |
165 | {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]} | |
166 | {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]} | |
167 | {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} | |
168 | {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} | |
169 | {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]} | |
170 | {"" if self.page_hierarchy_level == 1 else f"{'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}"} | |
171 | {'bit': <4} {self.dirty[0]: <10} {'page dirty': <30} {self.dirty[1]} | |
172 | {'bit': <4} {self.global_translation[0]: <10} {'global translation': <30} {self.global_translation[1]} | |
173 | {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]} | |
174 | {'bit': <4} {self.pat[0]: <10} {'pat': <30} {self.pat[1]} | |
175 | {'bits': <4} {str(self.protection_key[0]): <10} {'protection key': <30} {self.protection_key[1]} | |
176 | {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]} | |
177 | """ | |
178 | else: | |
179 | return f"""\ | |
180 | level {self.page_hierarchy_level}: | |
181 | {'entry address': <30} {hex(self.address)} | |
182 | {'page entry binary data': <30} {hex(self.page_entry_binary_data)} | |
183 | {'next entry physical address': <30} {hex(self.next_entry_physical_address)} | |
184 | --- | |
185 | {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]} | |
186 | {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]} | |
187 | {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]} | |
188 | {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]} | |
189 | {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]} | |
190 | {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]} | |
191 | {'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]} | |
192 | {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]} | |
193 | {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]} | |
194 | """ | |
195 | ||
196 | ||
197 | class TranslateVM(gdb.Command): | |
198 | """Prints the entire paging structure used to translate a given virtual address. | |
199 | ||
200 | Having an address space of the currently executed process translates the virtual address | |
201 | and prints detailed information of all paging structure levels used for the transaltion. | |
202 | Currently supported arch: x86""" | |
203 | ||
204 | def __init__(self): | |
205 | super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER) | |
206 | ||
207 | def invoke(self, arg, from_tty): | |
208 | if utils.is_target_arch("x86"): | |
209 | vm_address = gdb.parse_and_eval(f'{arg}') | |
210 | cr3_data = gdb.parse_and_eval('$cr3') | |
211 | cr4 = gdb.parse_and_eval('$cr4') | |
212 | page_levels = 5 if cr4 & (1 << 12) else 4 | |
213 | page_entry = Cr3(cr3_data, page_levels) | |
214 | while page_entry: | |
215 | gdb.write(page_entry.mk_string()) | |
216 | page_entry = page_entry.next_entry(vm_address) | |
217 | else: | |
218 | gdb.GdbError("Virtual address translation is not" | |
219 | "supported for this arch") | |
220 | ||
221 | ||
222 | TranslateVM() |