xref: /linux/scripts/gdb/linux/pgtable.py (revision ab93e0dd72c37d378dd936f031ffb83ff2bd87ce)
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
32def _page_offset_base():
33    pob_symbol = gdb.lookup_global_symbol('page_offset_base')
34    pob = pob_symbol.name
35    return gdb.parse_and_eval(pob)
36
37
38def is_bit_defined_tupled(data, offset):
39    return offset, bool(data >> offset & 1)
40
41def content_tupled(data, bit_start, bit_end):
42    return (bit_start, bit_end), data >> bit_start & ((1 << (1 + bit_end - bit_start)) - 1)
43
44def entry_va(level, phys_addr, translating_va):
45        def start_bit(level):
46            if level == 5:
47                return 48
48            elif level == 4:
49                return 39
50            elif level == 3:
51                return 30
52            elif level == 2:
53                return 21
54            elif level == 1:
55                return 12
56            else:
57                raise Exception(f'Unknown level {level}')
58
59        entry_offset =  ((translating_va >> start_bit(level)) & 511) * 8
60        entry_va = _page_offset_base() + phys_addr + entry_offset
61        return entry_va
62
63class Cr3():
64    def __init__(self, cr3, page_levels):
65        self.cr3 = cr3
66        self.page_levels = page_levels
67        self.page_level_write_through = is_bit_defined_tupled(cr3, 3)
68        self.page_level_cache_disabled = is_bit_defined_tupled(cr3, 4)
69        self.next_entry_physical_address = cr3 & PHYSICAL_ADDRESS_MASK & page_mask()
70
71    def next_entry(self, va):
72        next_level = self.page_levels
73        return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level)
74
75    def mk_string(self):
76            return f"""\
77cr3:
78    {'cr3 binary data': <30} {hex(self.cr3)}
79    {'next entry physical address': <30} {hex(self.next_entry_physical_address)}
80    ---
81    {'bit' : <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
82    {'bit' : <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
83"""
84
85
86class PageHierarchyEntry():
87    def __init__(self, address, level):
88        data = int.from_bytes(
89            memoryview(gdb.selected_inferior().read_memory(address, 8)),
90            "little"
91        )
92        if level == 1:
93            self.is_page = True
94            self.entry_present = is_bit_defined_tupled(data, 0)
95            self.read_write = is_bit_defined_tupled(data, 1)
96            self.user_access_allowed = is_bit_defined_tupled(data, 2)
97            self.page_level_write_through = is_bit_defined_tupled(data, 3)
98            self.page_level_cache_disabled = is_bit_defined_tupled(data, 4)
99            self.entry_was_accessed = is_bit_defined_tupled(data, 5)
100            self.dirty = is_bit_defined_tupled(data, 6)
101            self.pat = is_bit_defined_tupled(data, 7)
102            self.global_translation = is_bit_defined_tupled(data, 8)
103            self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level)
104            self.next_entry_physical_address = None
105            self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11)
106            self.protection_key = content_tupled(data, 59, 62)
107            self.executed_disable = is_bit_defined_tupled(data, 63)
108        else:
109            page_size = is_bit_defined_tupled(data, 7)
110            page_size_bit = page_size[1]
111            self.is_page = page_size_bit
112            self.entry_present = is_bit_defined_tupled(data, 0)
113            self.read_write = is_bit_defined_tupled(data, 1)
114            self.user_access_allowed = is_bit_defined_tupled(data, 2)
115            self.page_level_write_through = is_bit_defined_tupled(data, 3)
116            self.page_level_cache_disabled = is_bit_defined_tupled(data, 4)
117            self.entry_was_accessed = is_bit_defined_tupled(data, 5)
118            self.page_size = page_size
119            self.dirty = is_bit_defined_tupled(
120                data, 6) if page_size_bit else None
121            self.global_translation = is_bit_defined_tupled(
122                data, 8) if page_size_bit else None
123            self.pat = is_bit_defined_tupled(
124                data, 12) if page_size_bit else None
125            self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) if page_size_bit else None
126            self.next_entry_physical_address = None if page_size_bit else data & PHYSICAL_ADDRESS_MASK & page_mask()
127            self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11)
128            self.protection_key = content_tupled(data, 59, 62) if page_size_bit else None
129            self.executed_disable = is_bit_defined_tupled(data, 63)
130        self.address = address
131        self.page_entry_binary_data = data
132        self.page_hierarchy_level = level
133
134    def next_entry(self, va):
135        if self.is_page or not self.entry_present[1]:
136            return None
137
138        next_level = self.page_hierarchy_level - 1
139        return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level)
140
141
142    def mk_string(self):
143        if not self.entry_present[1]:
144            return f"""\
145level {self.page_hierarchy_level}:
146    {'entry address': <30} {hex(self.address)}
147    {'page entry binary data': <30} {hex(self.page_entry_binary_data)}
148    ---
149    PAGE ENTRY IS NOT PRESENT!
150"""
151        elif self.is_page:
152            def page_size_line(ps_bit, ps, level):
153                return "" if level == 1 else f"{'bit': <3} {ps_bit: <5} {'page size': <30} {ps}"
154
155            return f"""\
156level {self.page_hierarchy_level}:
157    {'entry address': <30} {hex(self.address)}
158    {'page entry binary data': <30} {hex(self.page_entry_binary_data)}
159    {'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}
160    {'page physical address': <30} {hex(self.page_physical_address)}
161    ---
162    {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]}
163    {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]}
164    {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]}
165    {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
166    {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
167    {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]}
168    {"" if self.page_hierarchy_level == 1 else f"{'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}"}
169    {'bit': <4} {self.dirty[0]: <10} {'page dirty': <30} {self.dirty[1]}
170    {'bit': <4} {self.global_translation[0]: <10} {'global translation': <30} {self.global_translation[1]}
171    {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]}
172    {'bit': <4} {self.pat[0]: <10} {'pat': <30} {self.pat[1]}
173    {'bits': <4} {str(self.protection_key[0]): <10} {'protection key': <30} {self.protection_key[1]}
174    {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]}
175"""
176        else:
177            return f"""\
178level {self.page_hierarchy_level}:
179    {'entry address': <30} {hex(self.address)}
180    {'page entry binary data': <30} {hex(self.page_entry_binary_data)}
181    {'next entry physical address': <30} {hex(self.next_entry_physical_address)}
182    ---
183    {'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]}
184    {'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]}
185    {'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]}
186    {'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
187    {'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
188    {'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]}
189    {'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}
190    {'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]}
191    {'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]}
192"""
193
194
195class TranslateVM(gdb.Command):
196    """Prints the entire paging structure used to translate a given virtual address.
197
198Having an address space of the currently executed process translates the virtual address
199and prints detailed information of all paging structure levels used for the transaltion.
200Currently supported arch: x86"""
201
202    def __init__(self):
203        super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER)
204
205    def invoke(self, arg, from_tty):
206        if utils.is_target_arch("x86"):
207            vm_address = gdb.parse_and_eval(f'{arg}')
208            cr3_data = gdb.parse_and_eval('$cr3')
209            cr4 = gdb.parse_and_eval('$cr4')
210            page_levels = 5 if cr4 & (1 << 12) else 4
211            page_entry = Cr3(cr3_data, page_levels)
212            while page_entry:
213                gdb.write(page_entry.mk_string())
214                page_entry = page_entry.next_entry(vm_address)
215        else:
216            gdb.GdbError("Virtual address translation is not"
217                         "supported for this arch")
218
219
220TranslateVM()
221