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