1 # SPDX-License-Identifier: GPL-2.0-only
3 # gdb helper commands and functions for Linux kernel debugging
5 # routines to introspect page table
8 # Dmitrii Bundin <dmitrii.bundin.a@gmail.com>
13 from linux import utils
15 PHYSICAL_ADDRESS_MASK = gdb.parse_and_eval('0xfffffffffffff')
18 def page_mask(level=1):
21 return gdb.parse_and_eval('(u64) ~0xfff')
24 return gdb.parse_and_eval('(u64) ~0x1fffff')
27 return gdb.parse_and_eval('(u64) ~0x3fffffff')
29 raise Exception(f'Unknown page level: {level}')
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)
40 def is_bit_defined_tupled(data, offset):
41 return offset, bool(data >> offset & 1)
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)
46 def entry_va(level, phys_addr, translating_va):
59 raise Exception(f'Unknown level {level}')
61 entry_offset = ((translating_va >> start_bit(level)) & 511) * 8
62 entry_va = _page_offset_base() + phys_addr + entry_offset
66 def __init__(self, cr3, page_levels):
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()
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)
80 {'cr3 binary data': <30} {hex(self.cr3)}
81 {'next entry physical address': <30} {hex(self.next_entry_physical_address)}
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]}
88 class PageHierarchyEntry():
89 def __init__(self, address, level):
90 data = int.from_bytes(
91 memoryview(gdb.selected_inferior().read_memory(address, 8)),
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)
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
136 def next_entry(self, va):
137 if self.is_page or not self.entry_present[1]:
140 next_level = self.page_hierarchy_level - 1
141 return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level)
145 if not self.entry_present[1]:
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)}
151 PAGE ENTRY IS NOT PRESENT!
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}"
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)}
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]}
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)}
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]}
197 class TranslateVM(gdb.Command):
198 """Prints the entire paging structure used to translate a given virtual address.
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"""
205 super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER)
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)
215 gdb.write(page_entry.mk_string())
216 page_entry = page_entry.next_entry(vm_address)
218 gdb.GdbError("Virtual address translation is not"
219 "supported for this arch")