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+
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()
scripts/gdb/vmlinux-gdb.py+1−0
@@ -37,3 +37,4 @@ else: · 36 unmodified lines
3737 import linux.clk
3838 import linux.genpd
3939 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