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()
223