This command provides a way to traverse the entire page hierarchy by a given virtual address on x86. In addition to qemu's commands info tlb/info mem it provides the complete information about the paging structure for an arbitrary virtual address. It supports 4KB/2MB/1GB and 5 level paging. Here is an example output for 2MB success translation: (gdb) translate-vm address cr3: cr3 binary data 0x1085be003 next entry physical address 0x1085be000 --- bit 3 page level write through False bit 4 page level cache disabled False level 4: entry address 0xffff8881085be7f8 page entry binary data 0x800000010ac83067 next entry physical address 0x10ac83000 --- bit 0 entry present True bit 1 read/write access allowed True bit 2 user access allowed True bit 3 page level write through False bit 4 page level cache disabled False bit 5 entry has been accessed True bit 7 page size False bit 11 restart to ordinary False bit 63 execute disable True level 3: entry address 0xffff88810ac83a48 page entry binary data 0x101af7067 next entry physical address 0x101af7000 --- bit 0 entry present True bit 1 read/write access allowed True bit 2 user access allowed True bit 3 page level write through False bit 4 page level cache disabled False bit 5 entry has been accessed True bit 7 page size False bit 11 restart to ordinary False bit 63 execute disable False level 2: entry address 0xffff888101af7368 page entry binary data 0x80000001634008e7 page size 2MB page physical address 0x163400000 --- bit 0 entry present True bit 1 read/write access allowed True bit 2 user access allowed True bit 3 page level write through False bit 4 page level cache disabled False bit 5 entry has been accessed True bit 7 page size True bit 6 page dirty True bit 8 global translation False bit 11 restart to ordinary True bit 12 pat False bits (59, 62) protection key 0 bit 63 execute disable True [dmitrii.bundin.a@gmail.com: add SPDX line, other tweaks] Link: https://lkml.kernel.org/r/20230113175151.22278-1-dmitrii.bundin.a@gmail.com [akpm@linux-foundation.org: s/physicall/physical/] Link: https://lkml.kernel.org/r/20230102171014.31408-1-dmitrii.bundin.a@gmail.com Signed-off-by: Dmitrii Bundin <dmitrii.bundin.a@gmail.com> Acked by: Mike Rapoport (IBM) <rppt@kernel.org> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jan Kiszka <jan.kiszka@siemens.com> Cc: Kieran Bingham <kbingham@kernel.org> Cc: Vlastimil Babka <vbabka@suse.cz> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Unified · Split
| @@ -0,0 +1,222 @@ | |||
| 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() | ||
| @@ -37,3 +37,4 @@ else: · 36 unmodified lines | |||
| 37 | import linux.clk | 37 | import linux.clk |
| 38 | import linux.genpd | 38 | import linux.genpd |
| 39 | import linux.device | 39 | import linux.device |
| 40 | import linux.mm | ||