OpenCloudOS-Kernel/scripts/gdb/linux/mm.py

223 lines
10 KiB
Python
Raw Normal View 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>
2023-01-03 01:10:14 +08:00
# SPDX-License-Identifier: GPL-2.0-only
#
# gdb helper commands and functions for Linux kernel debugging
#
# routines to introspect page table
#
# Authors:
# Dmitrii Bundin <dmitrii.bundin.a@gmail.com>
#
import gdb
from linux import utils
PHYSICAL_ADDRESS_MASK = gdb.parse_and_eval('0xfffffffffffff')
def page_mask(level=1):
# 4KB
if level == 1:
return gdb.parse_and_eval('(u64) ~0xfff')
# 2MB
elif level == 2:
return gdb.parse_and_eval('(u64) ~0x1fffff')
# 1GB
elif level == 3:
return gdb.parse_and_eval('(u64) ~0x3fffffff')
else:
raise Exception(f'Unknown page level: {level}')
#page_offset_base in case CONFIG_DYNAMIC_MEMORY_LAYOUT is disabled
POB_NO_DYNAMIC_MEM_LAYOUT = '0xffff888000000000'
def _page_offset_base():
pob_symbol = gdb.lookup_global_symbol('page_offset_base')
pob = pob_symbol.name if pob_symbol else POB_NO_DYNAMIC_MEM_LAYOUT
return gdb.parse_and_eval(pob)
def is_bit_defined_tupled(data, offset):
return offset, bool(data >> offset & 1)
def content_tupled(data, bit_start, bit_end):
return (bit_start, bit_end), data >> bit_start & ((1 << (1 + bit_end - bit_start)) - 1)
def entry_va(level, phys_addr, translating_va):
def start_bit(level):
if level == 5:
return 48
elif level == 4:
return 39
elif level == 3:
return 30
elif level == 2:
return 21
elif level == 1:
return 12
else:
raise Exception(f'Unknown level {level}')
entry_offset = ((translating_va >> start_bit(level)) & 511) * 8
entry_va = _page_offset_base() + phys_addr + entry_offset
return entry_va
class Cr3():
def __init__(self, cr3, page_levels):
self.cr3 = cr3
self.page_levels = page_levels
self.page_level_write_through = is_bit_defined_tupled(cr3, 3)
self.page_level_cache_disabled = is_bit_defined_tupled(cr3, 4)
self.next_entry_physical_address = cr3 & PHYSICAL_ADDRESS_MASK & page_mask()
def next_entry(self, va):
next_level = self.page_levels
return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level)
def mk_string(self):
return f"""\
cr3:
{'cr3 binary data': <30} {hex(self.cr3)}
{'next entry physical address': <30} {hex(self.next_entry_physical_address)}
---
{'bit' : <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
{'bit' : <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
"""
class PageHierarchyEntry():
def __init__(self, address, level):
data = int.from_bytes(
memoryview(gdb.selected_inferior().read_memory(address, 8)),
"little"
)
if level == 1:
self.is_page = True
self.entry_present = is_bit_defined_tupled(data, 0)
self.read_write = is_bit_defined_tupled(data, 1)
self.user_access_allowed = is_bit_defined_tupled(data, 2)
self.page_level_write_through = is_bit_defined_tupled(data, 3)
self.page_level_cache_disabled = is_bit_defined_tupled(data, 4)
self.entry_was_accessed = is_bit_defined_tupled(data, 5)
self.dirty = is_bit_defined_tupled(data, 6)
self.pat = is_bit_defined_tupled(data, 7)
self.global_translation = is_bit_defined_tupled(data, 8)
self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level)
self.next_entry_physical_address = None
self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11)
self.protection_key = content_tupled(data, 59, 62)
self.executed_disable = is_bit_defined_tupled(data, 63)
else:
page_size = is_bit_defined_tupled(data, 7)
page_size_bit = page_size[1]
self.is_page = page_size_bit
self.entry_present = is_bit_defined_tupled(data, 0)
self.read_write = is_bit_defined_tupled(data, 1)
self.user_access_allowed = is_bit_defined_tupled(data, 2)
self.page_level_write_through = is_bit_defined_tupled(data, 3)
self.page_level_cache_disabled = is_bit_defined_tupled(data, 4)
self.entry_was_accessed = is_bit_defined_tupled(data, 5)
self.page_size = page_size
self.dirty = is_bit_defined_tupled(
data, 6) if page_size_bit else None
self.global_translation = is_bit_defined_tupled(
data, 8) if page_size_bit else None
self.pat = is_bit_defined_tupled(
data, 12) if page_size_bit else None
self.page_physical_address = data & PHYSICAL_ADDRESS_MASK & page_mask(level) if page_size_bit else None
self.next_entry_physical_address = None if page_size_bit else data & PHYSICAL_ADDRESS_MASK & page_mask()
self.hlat_restart_with_ordinary = is_bit_defined_tupled(data, 11)
self.protection_key = content_tupled(data, 59, 62) if page_size_bit else None
self.executed_disable = is_bit_defined_tupled(data, 63)
self.address = address
self.page_entry_binary_data = data
self.page_hierarchy_level = level
def next_entry(self, va):
if self.is_page or not self.entry_present[1]:
return None
next_level = self.page_hierarchy_level - 1
return PageHierarchyEntry(entry_va(next_level, self.next_entry_physical_address, va), next_level)
def mk_string(self):
if not self.entry_present[1]:
return f"""\
level {self.page_hierarchy_level}:
{'entry address': <30} {hex(self.address)}
{'page entry binary data': <30} {hex(self.page_entry_binary_data)}
---
PAGE ENTRY IS NOT PRESENT!
"""
elif self.is_page:
def page_size_line(ps_bit, ps, level):
return "" if level == 1 else f"{'bit': <3} {ps_bit: <5} {'page size': <30} {ps}"
return f"""\
level {self.page_hierarchy_level}:
{'entry address': <30} {hex(self.address)}
{'page entry binary data': <30} {hex(self.page_entry_binary_data)}
{'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}
{'page physical address': <30} {hex(self.page_physical_address)}
---
{'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]}
{'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]}
{'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]}
{'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
{'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
{'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]}
{"" if self.page_hierarchy_level == 1 else f"{'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}"}
{'bit': <4} {self.dirty[0]: <10} {'page dirty': <30} {self.dirty[1]}
{'bit': <4} {self.global_translation[0]: <10} {'global translation': <30} {self.global_translation[1]}
{'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]}
{'bit': <4} {self.pat[0]: <10} {'pat': <30} {self.pat[1]}
{'bits': <4} {str(self.protection_key[0]): <10} {'protection key': <30} {self.protection_key[1]}
{'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]}
"""
else:
return f"""\
level {self.page_hierarchy_level}:
{'entry address': <30} {hex(self.address)}
{'page entry binary data': <30} {hex(self.page_entry_binary_data)}
{'next entry physical address': <30} {hex(self.next_entry_physical_address)}
---
{'bit': <4} {self.entry_present[0]: <10} {'entry present': <30} {self.entry_present[1]}
{'bit': <4} {self.read_write[0]: <10} {'read/write access allowed': <30} {self.read_write[1]}
{'bit': <4} {self.user_access_allowed[0]: <10} {'user access allowed': <30} {self.user_access_allowed[1]}
{'bit': <4} {self.page_level_write_through[0]: <10} {'page level write through': <30} {self.page_level_write_through[1]}
{'bit': <4} {self.page_level_cache_disabled[0]: <10} {'page level cache disabled': <30} {self.page_level_cache_disabled[1]}
{'bit': <4} {self.entry_was_accessed[0]: <10} {'entry has been accessed': <30} {self.entry_was_accessed[1]}
{'bit': <4} {self.page_size[0]: <10} {'page size': <30} {self.page_size[1]}
{'bit': <4} {self.hlat_restart_with_ordinary[0]: <10} {'restart to ordinary': <30} {self.hlat_restart_with_ordinary[1]}
{'bit': <4} {self.executed_disable[0]: <10} {'execute disable': <30} {self.executed_disable[1]}
"""
class TranslateVM(gdb.Command):
"""Prints the entire paging structure used to translate a given virtual address.
Having an address space of the currently executed process translates the virtual address
and prints detailed information of all paging structure levels used for the transaltion.
Currently supported arch: x86"""
def __init__(self):
super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
if utils.is_target_arch("x86"):
vm_address = gdb.parse_and_eval(f'{arg}')
cr3_data = gdb.parse_and_eval('$cr3')
cr4 = gdb.parse_and_eval('$cr4')
page_levels = 5 if cr4 & (1 << 12) else 4
page_entry = Cr3(cr3_data, page_levels)
while page_entry:
gdb.write(page_entry.mk_string())
page_entry = page_entry.next_entry(vm_address)
else:
gdb.GdbError("Virtual address translation is not"
"supported for this arch")
TranslateVM()