diff options
| author | Dmitrii Bundin <dmitrii.bundin.a@gmail.com> | 2023-01-02 20:10:14 +0300 | 
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2023-02-02 22:50:03 -0800 | 
| commit | e36903b0c19fc6e4cfd84a55840ac9559c3f2831 (patch) | |
| tree | 1fe1fefacb3942e025d9b301632f8bc9c07b8a2e /scripts/gdb/linux/mm.py | |
| parent | 9456d539acde9f92a52ffe477b4b86e35d214d1a (diff) | |
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>
Diffstat (limited to 'scripts/gdb/linux/mm.py')
| -rw-r--r-- | scripts/gdb/linux/mm.py | 222 | 
1 files changed, 222 insertions, 0 deletions
| diff --git a/scripts/gdb/linux/mm.py b/scripts/gdb/linux/mm.py new file mode 100644 index 000000000000..30d837f3dfae --- /dev/null +++ b/scripts/gdb/linux/mm.py @@ -0,0 +1,222 @@ +# 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() | 
