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