scripts/gdb: add mm introspection utils
[linux-block.git] / scripts / gdb / linux / mm.py
CommitLineData
e36903b0
DB
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()