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 | 37 | import linux.clk |
| 38 | 38 | import linux.genpd |
| 39 | 39 | import linux.device |
| 40 | + import linux.mm | |