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 32#page_offset_base in case CONFIG_DYNAMIC_MEMORY_LAYOUT is disabled 33POB_NO_DYNAMIC_MEM_LAYOUT = '0xffff888000000000' 34def _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 40def is_bit_defined_tupled(data, offset): 41 return offset, bool(data >> offset & 1) 42 43def content_tupled(data, bit_start, bit_end): 44 return (bit_start, bit_end), data >> bit_start & ((1 << (1 + bit_end - bit_start)) - 1) 45 46def 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 65class 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"""\ 79cr3: 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 88class 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"""\ 147level {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"""\ 158level {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"""\ 180level {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 197class TranslateVM(gdb.Command): 198 """Prints the entire paging structure used to translate a given virtual address. 199 200Having an address space of the currently executed process translates the virtual address 201and prints detailed information of all paging structure levels used for the transaltion. 202Currently 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 222TranslateVM() 223