History

scripts/gdb: add mm introspection utils

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>

Dmitrii Bundin committed 3 years ago

Commit e36903b1 parent 9456d532 files · +223 −0Browse files

Showing the changes to scripts (2 of 2 files). Show all

Unified · Split

2 files
  1. scripts/gdb/linux/mm.py +222 −0
  2. scripts/gdb/vmlinux-gdb.py +1 −0
scripts/gdb/linux/mm.py+222−0
@@ -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
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()
scripts/gdb/vmlinux-gdb.py+1−0
@@ -37,3 +37,4 @@ else: · 36 unmodified lines
37 import linux.clk37 import linux.clk
38 import linux.genpd38 import linux.genpd
39 import linux.device39 import linux.device
40 import linux.mm

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t