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() 221