Subject: [PATCH 0/8] Add GDB memory helper commands

Hi all,

I've created some GDB commands I think useful when I debug
some memory issues and kernel module issue.

For memory issue, we would like to get slabinfo, slabtrace,
page_owner and vmallocinfo to debug the memory issues.

For module issue, we would like to query kernel module name
when we get a module text address and load module
symbol by specific path.

This patchset is based on v6.5-rc3.

Patch 1-2:
- Add kernel module releated command.
Patch 3-5:
- Prepares for the memory-releated command.
Patch 6-8:
- Add memory-related commands.

Kuan-Ying Lee (8):
scripts/gdb/symbols: add specific ko module load command
scripts/gdb/modules: add get module text support
scripts/gdb/utils: add common type usage
scripts/gdb/aarch64: add aarch64 page operation helper commands and
configs
scripts/gdb/stackdepot: Add stackdepot support
scripts/gdb/page_owner: add page owner support
scripts/gdb/slab: Add slab support
scripts/gdb/vmalloc: add vmallocinfo support

scripts/gdb/linux/constants.py.in | 59 +++
scripts/gdb/linux/mm.py | 579 +++++++++++++++++++-----------
scripts/gdb/linux/modules.py | 32 +-
scripts/gdb/linux/page_owner.py | 189 ++++++++++
scripts/gdb/linux/pgtable.py | 222 ++++++++++++
scripts/gdb/linux/slab.py | 322 +++++++++++++++++
scripts/gdb/linux/stackdepot.py | 54 +++
scripts/gdb/linux/symbols.py | 23 +-
scripts/gdb/linux/utils.py | 20 ++
scripts/gdb/linux/vmalloc.py | 56 +++
scripts/gdb/vmlinux-gdb.py | 7 +-
11 files changed, 1356 insertions(+), 207 deletions(-)
create mode 100644 scripts/gdb/linux/page_owner.py
create mode 100644 scripts/gdb/linux/pgtable.py
create mode 100644 scripts/gdb/linux/slab.py
create mode 100644 scripts/gdb/linux/stackdepot.py
create mode 100644 scripts/gdb/linux/vmalloc.py

--
2.18.0



Subject: [PATCH 3/8] scripts/gdb/utils: add common type usage

Since we often use 'unsigned long', 'size_t', 'usigned int'
and 'struct page', we add these common types to utils.

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/utils.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)

diff --git a/scripts/gdb/linux/utils.py b/scripts/gdb/linux/utils.py
index 9f44df13761e..7d5278d815fa 100644
--- a/scripts/gdb/linux/utils.py
+++ b/scripts/gdb/linux/utils.py
@@ -35,12 +35,32 @@ class CachedType:


long_type = CachedType("long")
+ulong_type = CachedType("unsigned long")
+uint_type = CachedType("unsigned int")
atomic_long_type = CachedType("atomic_long_t")
+size_t_type = CachedType("size_t")
+struct_page_type = CachedType("struct page")
+
+def get_uint_type():
+ global uint_type
+ return uint_type.get_type()
+
+def get_page_type():
+ global struct_page_type
+ return struct_page_type.get_type()

def get_long_type():
global long_type
return long_type.get_type()

+def get_ulong_type():
+ global ulong_type
+ return ulong_type.get_type()
+
+def get_size_t_type():
+ global size_t_type
+ return size_t_type.get_type()
+
def offset_of(typeobj, field):
element = gdb.Value(0).cast(typeobj)
return int(str(element[field].address).split()[0], 16)
--
2.18.0


Subject: [PATCH 1/8] scripts/gdb/symbols: add specific ko module load command

Add lx-symbols <ko_path> command to support add specific
ko module.

Example output like below:
(gdb) lx-symbols mm/kasan/kasan_test.ko
loading @0xffff800002d30000: mm/kasan/kasan_test.ko

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/symbols.py | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/scripts/gdb/linux/symbols.py b/scripts/gdb/linux/symbols.py
index fdad3f32c747..b21ae62fdd7d 100644
--- a/scripts/gdb/linux/symbols.py
+++ b/scripts/gdb/linux/symbols.py
@@ -107,11 +107,12 @@ lx-symbols command."""
name=section_name, addr=str(address)))
return "".join(args)

- def load_module_symbols(self, module):
+ def load_module_symbols(self, module, module_file=None):
module_name = module['name'].string()
module_addr = str(module['mem'][constants.LX_MOD_TEXT]['base']).split()[0]

- module_file = self._get_module_file(module_name)
+ if not module_file:
+ module_file = self._get_module_file(module_name)
if not module_file and not self.module_files_updated:
self._update_module_files()
module_file = self._get_module_file(module_name)
@@ -135,6 +136,19 @@ lx-symbols command."""
else:
gdb.write("no module object found for '{0}'\n".format(module_name))

+ def load_ko_symbols(self, mod_path):
+ self.loaded_modules = []
+ module_list = modules.module_list()
+
+ for module in module_list:
+ module_name = module['name'].string()
+ module_pattern = ".*/{0}\.ko(?:.debug)?$".format(
+ module_name.replace("_", r"[_\-]"))
+ if re.match(module_pattern, mod_path) and os.path.exists(mod_path):
+ self.load_module_symbols(module, mod_path)
+ return
+ raise gdb.GdbError("%s is not a valid .ko\n" % mod_path)
+
def load_all_symbols(self):
gdb.write("loading vmlinux\n")

@@ -173,6 +187,11 @@ lx-symbols command."""
self.module_files = []
self.module_files_updated = False

+ argv = gdb.string_to_argv(arg)
+ if len(argv) == 1:
+ self.load_ko_symbols(argv[0])
+ return
+
self.load_all_symbols()

if hasattr(gdb, 'Breakpoint'):
--
2.18.0


Subject: [PATCH 7/8] scripts/gdb/slab: Add slab support

Add 'lx-slabinfo' and 'lx-slabtrace' support.

This GDB scripts print slabinfo and slabtrace for user
to analyze slab memory usage.

Example output like below:
(gdb) lx-slabinfo
Pointer | name | active_objs | num_objs | objsize | objperslab | pagesperslab
------------------ | -------------------- | ------------ | ------------ | -------- | ----------- | -------------
0xffff0000c59df480 | p9_req_t | 0 | 0 | 280 | 29 | 2
0xffff0000c59df280 | isp1760_qh | 0 | 0 | 160 | 25 | 1
0xffff0000c59df080 | isp1760_qtd | 0 | 0 | 184 | 22 | 1
0xffff0000c59dee80 | isp1760_urb_listite | 0 | 0 | 136 | 30 | 1
0xffff0000c59dec80 | asd_sas_event | 0 | 0 | 256 | 32 | 2
0xffff0000c59dea80 | sas_task | 0 | 0 | 448 | 36 | 4
0xffff0000c59de880 | bio-120 | 18 | 21 | 384 | 21 | 2
0xffff0000c59de680 | io_kiocb | 0 | 0 | 448 | 36 | 4
0xffff0000c59de480 | bfq_io_cq | 0 | 0 | 1504 | 21 | 8
0xffff0000c59de280 | bfq_queue | 0 | 0 | 720 | 22 | 4
0xffff0000c59de080 | mqueue_inode_cache | 1 | 28 | 1152 | 28 | 8
0xffff0000c59dde80 | v9fs_inode_cache | 0 | 0 | 832 | 39 | 8
...

(gdb) lx-slabtrace --cache_name kmalloc-1k
63 <tty_register_device_attr+508> waste=16632/264 age=46856/46871/46888 pid=1 cpus=6,
0xffff800008720240 <__kmem_cache_alloc_node+236>: mov x22, x0
0xffff80000862a4fc <kmalloc_trace+64>: mov x21, x0
0xffff8000095d086c <tty_register_device_attr+508>: mov x19, x0
0xffff8000095d0f98 <tty_register_driver+704>: cmn x0, #0x1, lsl #12
0xffff80000c2677e8 <vty_init+620>: Cannot access memory at address 0xffff80000c2677e8
0xffff80000c265a10 <tty_init+276>: Cannot access memory at address 0xffff80000c265a10
0xffff80000c26d3c4 <chr_dev_init+204>: Cannot access memory at address 0xffff80000c26d3c4
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b58 <kernel_init_freeable+956>: Cannot access memory at address 0xffff80000c1c1b58
0xffff80000acf1334 <kernel_init+36>: bl 0xffff8000081ac040 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0

(gdb) lx-slabtrace --cache_name kmalloc-1k --free
428 <not-available> age=4294958600 pid=0 cpus=0,

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/constants.py.in | 13 ++
scripts/gdb/linux/slab.py | 322 ++++++++++++++++++++++++++++++
scripts/gdb/vmlinux-gdb.py | 1 +
3 files changed, 336 insertions(+)
create mode 100644 scripts/gdb/linux/slab.py

diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index 7e375b89fb99..fa23f4e3546a 100644
--- a/scripts/gdb/linux/constants.py.in
+++ b/scripts/gdb/linux/constants.py.in
@@ -20,6 +20,7 @@
#include <linux/of_fdt.h>
#include <linux/page_ext.h>
#include <linux/radix-tree.h>
+#include <linux/slab.h>
#include <linux/threads.h>
#include <asm/memory.h>

@@ -99,6 +100,16 @@ if IS_BUILTIN(CONFIG_ARM64):
LX_GDBPARSED(PAGE_EXT_OWNER)
LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED)

+/* linux/slab.h */
+LX_GDBPARSED(SLAB_RED_ZONE)
+LX_GDBPARSED(SLAB_POISON)
+LX_GDBPARSED(SLAB_KMALLOC)
+LX_GDBPARSED(SLAB_HWCACHE_ALIGN)
+LX_GDBPARSED(SLAB_CACHE_DMA)
+LX_GDBPARSED(SLAB_CACHE_DMA32)
+LX_GDBPARSED(SLAB_STORE_USER)
+LX_GDBPARSED(SLAB_PANIC)
+
/* Kernel Configs */
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS)
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
@@ -140,3 +151,5 @@ if IS_BUILTIN(CONFIG_NUMA):
LX_CONFIG(CONFIG_DEBUG_VIRTUAL)
LX_CONFIG(CONFIG_STACKDEPOT)
LX_CONFIG(CONFIG_PAGE_OWNER)
+LX_CONFIG(CONFIG_SLUB_DEBUG)
+LX_CONFIG(CONFIG_SLAB_FREELIST_HARDENED)
diff --git a/scripts/gdb/linux/slab.py b/scripts/gdb/linux/slab.py
new file mode 100644
index 000000000000..665bd249f2fb
--- /dev/null
+++ b/scripts/gdb/linux/slab.py
@@ -0,0 +1,322 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 MediaTek Inc.
+#
+# Authors:
+# Kuan-Ying Lee <[email protected]>
+#
+
+import gdb
+import re
+import traceback
+from linux import lists, utils, stackdepot, constants, mm
+
+SLAB_RED_ZONE = constants.LX_SLAB_RED_ZONE
+SLAB_POISON = constants.LX_SLAB_POISON
+SLAB_KMALLOC = constants.LX_SLAB_KMALLOC
+SLAB_HWCACHE_ALIGN = constants.LX_SLAB_HWCACHE_ALIGN
+SLAB_CACHE_DMA = constants.LX_SLAB_CACHE_DMA
+SLAB_CACHE_DMA32 = constants.LX_SLAB_CACHE_DMA32
+SLAB_STORE_USER = constants.LX_SLAB_STORE_USER
+SLAB_PANIC = constants.LX_SLAB_PANIC
+
+OO_SHIFT = 16
+OO_MASK = (1 << OO_SHIFT) - 1
+
+slab_type = utils.CachedType("struct slab")
+slab_ptr_type = slab_type.get_type().pointer()
+kmem_cache_type = utils.CachedType("struct kmem_cache")
+kmem_cache_ptr_type = kmem_cache_type.get_type().pointer()
+
+track_type = gdb.lookup_type('struct track')
+track_alloc = int(gdb.parse_and_eval('TRACK_ALLOC'))
+track_free = int(gdb.parse_and_eval('TRACK_FREE'))
+
+def slab_folio(slab):
+ return slab.cast(gdb.lookup_type("struct folio").pointer())
+
+def slab_address(slab):
+ p_ops = mm.page_ops().ops
+ folio = slab_folio(slab)
+ return p_ops.folio_address(folio)
+
+def for_each_object(cache, addr, slab_objects):
+ p = addr
+ if cache['flags'] & SLAB_RED_ZONE:
+ p += int(cache['red_left_pad'])
+ while p < addr + (slab_objects * cache['size']):
+ yield p
+ p = p + int(cache['size'])
+
+def get_info_end(cache):
+ if (cache['offset'] >= cache['inuse']):
+ return cache['inuse'] + gdb.lookup_type("void").pointer().sizeof
+ else:
+ return cache['inuse']
+
+def get_orig_size(cache, obj):
+ if cache['flags'] & SLAB_STORE_USER and cache['flags'] & SLAB_KMALLOC:
+ p = mm.page_ops().ops.kasan_reset_tag(obj)
+ p += get_info_end(cache)
+ p += gdb.lookup_type('struct track').sizeof * 2
+ p = p.cast(utils.get_uint_type().pointer())
+ return p.dereference()
+ else:
+ return cache['object_size']
+
+def get_track(cache, object_pointer, alloc):
+ p = object_pointer + get_info_end(cache)
+ p += (alloc * track_type.sizeof)
+ return p
+
+def oo_objects(x):
+ return int(x['x']) & OO_MASK
+
+def oo_order(x):
+ return int(x['x']) >> OO_SHIFT
+
+def reciprocal_divide(a, R):
+ t = (a * int(R['m'])) >> 32
+ return (t + ((a - t) >> int(R['sh1']))) >> int(R['sh2'])
+
+def __obj_to_index(cache, addr, obj):
+ return reciprocal_divide(int(mm.page_ops().ops.kasan_reset_tag(obj)) - addr, cache['reciprocal_size'])
+
+def swab64(x):
+ result = (((x & 0x00000000000000ff) << 56) | \
+ ((x & 0x000000000000ff00) << 40) | \
+ ((x & 0x0000000000ff0000) << 24) | \
+ ((x & 0x00000000ff000000) << 8) | \
+ ((x & 0x000000ff00000000) >> 8) | \
+ ((x & 0x0000ff0000000000) >> 24) | \
+ ((x & 0x00ff000000000000) >> 40) | \
+ ((x & 0xff00000000000000) >> 56))
+ return result
+
+def freelist_ptr(cache, ptr, ptr_addr):
+ if constants.LX_CONFIG_SLAB_FREELIST_HARDENED:
+ return ptr ^ cache['random'] ^ swab64(int(ptr_addr))
+ else:
+ return ptr
+
+def freelist_dereference(cache, ptr_addr):
+ ptr = ptr_addr.cast(utils.get_ulong_type().pointer()).dereference()
+ return freelist_ptr(cache, ptr, ptr_addr)
+
+def get_freepointer(cache, obj):
+ obj = mm.page_ops().ops.kasan_reset_tag(obj)
+ return freelist_dereference(cache, obj + cache['offset'])
+
+def loc_exist(loc_track, addr, handle, waste):
+ for loc in loc_track:
+ if loc['addr'] == addr and loc['handle'] == handle and loc['waste'] == waste:
+ return loc
+ return None
+
+def add_location(loc_track, cache, track, orig_size):
+ jiffies = gdb.parse_and_eval("jiffies_64")
+ age = jiffies - track['when']
+ handle = 0
+ waste = cache['object_size'] - int(orig_size)
+ pid = int(track['pid'])
+ cpuid = int(track['cpu'])
+ addr = track['addr']
+ if constants.LX_CONFIG_STACKDEPOT:
+ handle = track['handle']
+
+ loc = loc_exist(loc_track, addr, handle, waste)
+ if loc:
+ loc['count'] += 1
+ if track['when']:
+ loc['sum_time'] += age
+ loc['min_time'] = min(loc['min_time'], age)
+ loc['max_time'] = max(loc['max_time'], age)
+ loc['min_pid'] = min(loc['min_pid'], pid)
+ loc['max_pid'] = max(loc['max_pid'], pid)
+ loc['cpus'].add(cpuid)
+ else:
+ loc_track.append({
+ 'count' : 1,
+ 'addr' : addr,
+ 'sum_time' : age,
+ 'min_time' : age,
+ 'max_time' : age,
+ 'min_pid' : pid,
+ 'max_pid' : pid,
+ 'handle' : handle,
+ 'waste' : waste,
+ 'cpus' : {cpuid}
+ }
+ )
+
+def slabtrace(alloc, cache_name):
+
+ def __fill_map(obj_map, cache, slab):
+ p = slab['freelist']
+ addr = slab_address(slab)
+ while p != gdb.Value(0):
+ index = __obj_to_index(cache, addr, p)
+ obj_map[index] = True # free objects
+ p = get_freepointer(cache, p)
+
+ # process every slab page on the slab_list (partial and full list)
+ def process_slab(loc_track, slab_list, alloc, cache):
+ for slab in lists.list_for_each_entry(slab_list, slab_ptr_type, "slab_list"):
+ obj_map[:] = [False] * oo_objects(cache['oo'])
+ __fill_map(obj_map, cache, slab)
+ addr = slab_address(slab)
+ for object_pointer in for_each_object(cache, addr, slab['objects']):
+ if obj_map[__obj_to_index(cache, addr, object_pointer)] == True:
+ continue
+ p = get_track(cache, object_pointer, alloc)
+ track = gdb.Value(p).cast(track_type.pointer())
+ if alloc == track_alloc:
+ size = get_orig_size(cache, object_pointer)
+ else:
+ size = cache['object_size']
+ add_location(loc_track, cache, track, size)
+ continue
+
+ slab_caches = gdb.parse_and_eval("slab_caches")
+ if mm.page_ops().ops.MAX_NUMNODES > 1:
+ nr_node_ids = int(gdb.parse_and_eval("nr_node_ids"))
+ else:
+ nr_node_ids = 1
+
+ target_cache = None
+ loc_track = []
+
+ for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'):
+ if cache['name'].string() == cache_name:
+ target_cache = cache
+ break
+
+ obj_map = [False] * oo_objects(target_cache['oo'])
+
+ if target_cache['flags'] & SLAB_STORE_USER:
+ for i in range(0, nr_node_ids):
+ cache_node = target_cache['node'][i]
+ if cache_node['nr_slabs']['counter'] == 0:
+ continue
+ process_slab(loc_track, cache_node['partial'], alloc, target_cache)
+ process_slab(loc_track, cache_node['full'], alloc, target_cache)
+ else:
+ raise gdb.GdbError("SLAB_STORE_USER is not set in %s" % target_cache['name'].string())
+
+ for loc in sorted(loc_track, key=lambda x:x['count'], reverse=True):
+ if loc['addr']:
+ addr = loc['addr'].cast(utils.get_ulong_type().pointer())
+ gdb.write("%d %s" % (loc['count'], str(addr).split(' ')[-1]))
+ else:
+ gdb.write("%d <not-available>" % loc['count'])
+
+ if loc['waste']:
+ gdb.write(" waste=%d/%d" % (loc['count'] * loc['waste'], loc['waste']))
+
+ if loc['sum_time'] != loc['min_time']:
+ gdb.write(" age=%d/%d/%d" % (loc['min_time'], loc['sum_time']/loc['count'], loc['max_time']))
+ else:
+ gdb.write(" age=%d" % loc['min_time'])
+
+ if loc['min_pid'] != loc['max_pid']:
+ gdb.write(" pid=%d-%d" % (loc['min_pid'], loc['max_pid']))
+ else:
+ gdb.write(" pid=%d" % loc['min_pid'])
+
+ if constants.LX_NR_CPUS > 1:
+ nr_cpu = gdb.parse_and_eval('__num_online_cpus')['counter']
+ if nr_cpu > 1:
+ gdb.write(" cpus=")
+ for i in loc['cpus']:
+ gdb.write("%d," % i)
+ gdb.write("\n")
+ if constants.LX_CONFIG_STACKDEPOT:
+ if loc['handle']:
+ stackdepot.stack_depot_print(loc['handle'])
+ gdb.write("\n")
+
+def help():
+ t = """Usage: lx-slabtrace --cache_name [cache_name] [Options]
+ Options:
+ --alloc
+ print information of allocation trace of the allocated objects
+ --free
+ print information of freeing trace of the allocated objects
+ Example:
+ lx-slabtrace --cache_name kmalloc-1k --alloc
+ lx-slabtrace --cache_name kmalloc-1k --free\n"""
+ gdb.write("Unrecognized command\n")
+ raise gdb.GdbError(t)
+
+class LxSlabTrace(gdb.Command):
+ """Show specific cache slabtrace"""
+
+ def __init__(self):
+ super(LxSlabTrace, self).__init__("lx-slabtrace", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ alloc = track_alloc # default show alloc_traces
+
+ if len(argv) == 3:
+ if argv[2] == '--alloc':
+ alloc = track_alloc
+ elif argv[2] == '--free':
+ alloc = track_free
+ else:
+ help()
+ if len(argv) >= 2 and argv[0] == '--cache_name':
+ slabtrace(alloc, argv[1])
+ else:
+ help()
+LxSlabTrace()
+
+def slabinfo():
+ nr_node_ids = None
+
+ if not constants.LX_CONFIG_SLUB_DEBUG:
+ raise gdb.GdbError("CONFIG_SLUB_DEBUG is not enabled")
+
+ def count_free(slab):
+ total_free = 0
+ for slab in lists.list_for_each_entry(slab, slab_ptr_type, 'slab_list'):
+ total_free += int(slab['objects'] - slab['inuse'])
+ return total_free
+
+ gdb.write("{:^18} | {:^20} | {:^12} | {:^12} | {:^8} | {:^11} | {:^13}\n".format('Pointer', 'name', 'active_objs', 'num_objs', 'objsize', 'objperslab', 'pagesperslab'))
+ gdb.write("{:-^18} | {:-^20} | {:-^12} | {:-^12} | {:-^8} | {:-^11} | {:-^13}\n".format('', '', '', '', '', '', ''))
+
+ slab_caches = gdb.parse_and_eval("slab_caches")
+ if mm.page_ops().ops.MAX_NUMNODES > 1:
+ nr_node_ids = int(gdb.parse_and_eval("nr_node_ids"))
+ else:
+ nr_node_ids = 1
+
+ for cache in lists.list_for_each_entry(slab_caches, kmem_cache_ptr_type, 'list'):
+ nr_objs = 0
+ nr_free = 0
+ nr_slabs = 0
+ for i in range(0, nr_node_ids):
+ cache_node = cache['node'][i]
+ try:
+ nr_slabs += cache_node['nr_slabs']['counter']
+ nr_objs = int(cache_node['total_objects']['counter'])
+ nr_free = count_free(cache_node['partial'])
+ except:
+ raise gdb.GdbError(traceback.format_exc())
+ active_objs = nr_objs - nr_free
+ num_objs = nr_objs
+ active_slabs = nr_slabs
+ objects_per_slab = oo_objects(cache['oo'])
+ cache_order = oo_order(cache['oo'])
+ gdb.write("{:18s} | {:20.19s} | {:12} | {:12} | {:8} | {:11} | {:13}\n".format(hex(cache), cache['name'].string(), str(active_objs), str(num_objs), str(cache['size']), str(objects_per_slab), str(1 << cache_order)))
+
+class LxSlabInfo(gdb.Command):
+ """Show slabinfo"""
+
+ def __init__(self):
+ super(LxSlabInfo, self).__init__("lx-slabinfo", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ slabinfo()
+LxSlabInfo()
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index 89465f0de548..2526364f31fd 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -47,3 +47,4 @@ else:
import linux.mm
import linux.stackdepot
import linux.page_owner
+ import linux.slab
--
2.18.0


Subject: [PATCH 5/8] scripts/gdb/stackdepot: Add stackdepot support

Add support for printing the backtrace of stackdepot handle.

This is the preparation patch for dumping page_owner,
slabtrace usage.

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/constants.py.in | 1 +
scripts/gdb/linux/stackdepot.py | 54 +++++++++++++++++++++++++++++++
scripts/gdb/vmlinux-gdb.py | 1 +
3 files changed, 56 insertions(+)
create mode 100644 scripts/gdb/linux/stackdepot.py

diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index 7c44222bbf23..ec88c9affdd2 100644
--- a/scripts/gdb/linux/constants.py.in
+++ b/scripts/gdb/linux/constants.py.in
@@ -133,3 +133,4 @@ LX_CONFIG(CONFIG_VMAP_STACK)
if IS_BUILTIN(CONFIG_NUMA):
LX_VALUE(CONFIG_NODES_SHIFT)
LX_CONFIG(CONFIG_DEBUG_VIRTUAL)
+LX_CONFIG(CONFIG_STACKDEPOT)
diff --git a/scripts/gdb/linux/stackdepot.py b/scripts/gdb/linux/stackdepot.py
new file mode 100644
index 000000000000..03b5eb19f056
--- /dev/null
+++ b/scripts/gdb/linux/stackdepot.py
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 MediaTek Inc.
+#
+# Authors:
+# Kuan-Ying Lee <[email protected]>
+#
+
+import gdb
+from linux import utils, constants
+
+stack_record_type = utils.CachedType('struct stack_record')
+DEPOT_STACK_ALIGN = 4
+
+def stack_depot_fetch(handle):
+ global DEPOT_STACK_ALIGN
+ global stack_record_type
+
+ stack_depot_disabled = gdb.parse_and_eval('stack_depot_disabled')
+
+ if stack_depot_disabled:
+ raise gdb.GdbError("stack_depot_disabled\n")
+
+ handle_parts_t = gdb.lookup_type("union handle_parts")
+ parts = handle.cast(handle_parts_t)
+ offset = parts['offset'] << DEPOT_STACK_ALIGN
+ pool_index_cached = gdb.parse_and_eval('pool_index')
+
+ if parts['pool_index'] > pool_index_cached:
+ gdb.write("pool index %d out of bounds (%d) for stack id 0x%08x\n" % (parts['pool_index'], pool_index_cached, handle))
+ return gdb.Value(0), 0
+
+ stack_pools = gdb.parse_and_eval('stack_pools')
+
+ try:
+ pool = stack_pools[parts['pool_index']]
+ stack = (pool + gdb.Value(offset).cast(utils.get_size_t_type())).cast(stack_record_type.get_type().pointer())
+ size = int(stack['size'].cast(utils.get_ulong_type()))
+ return stack['entries'], size
+ except Exception as e:
+ gdb.write("%s\n" % e)
+ return gdb.Value(0), 0
+
+def stack_depot_print(handle):
+ if not constants.LX_CONFIG_STACKDEPOT:
+ raise gdb.GdbError("CONFIG_STACKDEPOT is not enabled")
+
+ entries, nr_entries = stack_depot_fetch(handle)
+ if nr_entries > 0:
+ for i in range(0, nr_entries):
+ try:
+ gdb.execute("x /i 0x%x" % (int(entries[i])))
+ except Exception as e:
+ gdb.write("%s\n" % e)
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index fd24574bcec9..24f149694b81 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -45,3 +45,4 @@ else:
import linux.radixtree
import linux.interrupts
import linux.mm
+ import linux.stackdepot
--
2.18.0


Subject: [PATCH 6/8] scripts/gdb/page_owner: add page owner support

This GDB scripts print page owner information for user to
analyze the memory usage or memory corruption issue.

Example output from an aarch64 system:

(gdb) lx-dump-page-owner --pfn 655360
page_owner tracks the page as allocated
Page last allocated via order 0, gfp_mask: 0x8, pid: 1, tgid: 1 ("swapper/0\000\000\000\000\000\000"), ts 1295948880 ns, free_ts 1011852016 ns
PFN: 655360, Flags: 0x3fffc0000000000
0xffff8000086ab964 <post_alloc_hook+452>: ldp x19, x20, [sp, #16]
0xffff80000862e4e0 <split_map_pages+344>: cbnz w22, 0xffff80000862e57c <split_map_pages+500>
0xffff8000086370c4 <isolate_freepages_range+556>: mov x0, x27
0xffff8000086bc1cc <alloc_contig_range+808>: mov x24, x0
0xffff80000877d6d8 <cma_alloc+772>: mov w1, w0
0xffff8000082c8d18 <dma_alloc_from_contiguous+104>: ldr x19, [sp, #16]
0xffff8000082ce0e8 <atomic_pool_expand+208>: mov x19, x0
0xffff80000c1e41b4 <__dma_atomic_pool_init+172>: Cannot access memory at address 0xffff80000c1e41b4
0xffff80000c1e4298 <dma_atomic_pool_init+92>: Cannot access memory at address 0xffff80000c1e4298
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b50 <kernel_init_freeable+952>: Cannot access memory at address 0xffff80000c1c1b50
0xffff80000acf87dc <kernel_init+36>: bl 0xffff8000081ab100 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0
page last free stack trace:
0xffff8000086a6e8c <free_unref_page_prepare+796>: mov w2, w23
0xffff8000086aee1c <free_unref_page+96>: tst w0, #0xff
0xffff8000086af3f8 <__free_pages+292>: ldp x19, x20, [sp, #16]
0xffff80000c1f3214 <init_cma_reserved_pageblock+220>: Cannot access memory at address 0xffff80000c1f3214
0xffff80000c20363c <cma_init_reserved_areas+1284>: Cannot access memory at address 0xffff80000c20363c
0xffff8000080161d4 <do_one_initcall+176>: mov w21, w0
0xffff80000c1c1b50 <kernel_init_freeable+952>: Cannot access memory at address 0xffff80000c1c1b50
0xffff80000acf87dc <kernel_init+36>: bl 0xffff8000081ab100 <async_synchronize_full>
0xffff800008018d00 <ret_from_fork+16>: mrs x28, sp_el0

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/constants.py.in | 6 +
scripts/gdb/linux/page_owner.py | 189 ++++++++++++++++++++++++++++++
scripts/gdb/vmlinux-gdb.py | 1 +
3 files changed, 196 insertions(+)
create mode 100644 scripts/gdb/linux/page_owner.py

diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index ec88c9affdd2..7e375b89fb99 100644
--- a/scripts/gdb/linux/constants.py.in
+++ b/scripts/gdb/linux/constants.py.in
@@ -18,6 +18,7 @@
#include <linux/irq.h>
#include <linux/mount.h>
#include <linux/of_fdt.h>
+#include <linux/page_ext.h>
#include <linux/radix-tree.h>
#include <linux/threads.h>
#include <asm/memory.h>
@@ -94,6 +95,10 @@ if IS_BUILTIN(CONFIG_ARM64):
LX_GDBPARSED(VA_BITS_MIN)
LX_GDBPARSED(MODULES_VSIZE)

+/* linux/page_ext.h */
+LX_GDBPARSED(PAGE_EXT_OWNER)
+LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED)
+
/* Kernel Configs */
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS)
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
@@ -134,3 +139,4 @@ if IS_BUILTIN(CONFIG_NUMA):
LX_VALUE(CONFIG_NODES_SHIFT)
LX_CONFIG(CONFIG_DEBUG_VIRTUAL)
LX_CONFIG(CONFIG_STACKDEPOT)
+LX_CONFIG(CONFIG_PAGE_OWNER)
diff --git a/scripts/gdb/linux/page_owner.py b/scripts/gdb/linux/page_owner.py
new file mode 100644
index 000000000000..8eb619adfa6e
--- /dev/null
+++ b/scripts/gdb/linux/page_owner.py
@@ -0,0 +1,189 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 MediaTek Inc.
+#
+# Authors:
+# Kuan-Ying Lee <[email protected]>
+#
+
+import gdb
+from linux import utils, stackdepot, constants, mm
+
+page_ext_t = utils.CachedType('struct page_ext')
+page_owner_t = utils.CachedType('struct page_owner')
+
+PAGE_OWNER_STACK_DEPTH = 16
+PAGE_EXT_OWNER = constants.LX_PAGE_EXT_OWNER
+PAGE_EXT_INVALID = 0x1
+PAGE_EXT_OWNER_ALLOCATED = constants.LX_PAGE_EXT_OWNER_ALLOCATED
+
+def help():
+ t = """Usage: lx-dump-page-owner [Option]
+ Option:
+ --pfn [Decimal pfn]
+ Example:
+ lx-dump-page-owner --pfn 655360\n"""
+ gdb.write("Unrecognized command\n")
+ raise gdb.GdbError(t)
+
+class DumpPageOwner(gdb.Command):
+ """Dump page owner"""
+
+ min_pfn = None
+ max_pfn = None
+ p_ops = None
+ migrate_reason_names = None
+
+ def __init__(self):
+ super(DumpPageOwner, self).__init__("lx-dump-page-owner", gdb.COMMAND_SUPPORT)
+
+ def invoke(self, args, from_tty):
+ if not constants.LX_CONFIG_PAGE_OWNER:
+ raise gdb.GdbError('CONFIG_PAGE_OWNER does not enable')
+
+ page_owner_inited = gdb.parse_and_eval('page_owner_inited')
+ if page_owner_inited['key']['enabled']['counter'] != 0x1:
+ raise gdb.GdbError('page_owner_inited is not enabled')
+
+ self.p_ops = mm.page_ops().ops
+ self.get_page_owner_info()
+ argv = gdb.string_to_argv(args)
+ if len(argv) == 0:
+ self.read_page_owner()
+ elif len(argv) == 2:
+ if argv[0] == "--pfn":
+ pfn = int(argv[1])
+ self.read_page_owner_by_addr(self.p_ops.pfn_to_page(pfn))
+ else:
+ help()
+ else:
+ help()
+
+ def get_page_owner_info(self):
+ self.min_pfn = int(gdb.parse_and_eval("min_low_pfn"))
+ self.max_pfn = int(gdb.parse_and_eval("max_pfn"))
+ self.page_ext_size = int(gdb.parse_and_eval("page_ext_size"))
+ self.migrate_reason_names = gdb.parse_and_eval('migrate_reason_names')
+
+ def page_ext_invalid(self, page_ext):
+ if page_ext == gdb.Value(0):
+ return True
+ if page_ext.cast(utils.get_ulong_type()) & PAGE_EXT_INVALID == PAGE_EXT_INVALID:
+ return True
+ return False
+
+ def get_entry(self, base, index):
+ return (base.cast(utils.get_ulong_type()) + self.page_ext_size * index).cast(page_ext_t.get_type().pointer())
+
+ def lookup_page_ext(self, page):
+ pfn = self.p_ops.page_to_pfn(page)
+ section = self.p_ops.pfn_to_section(pfn)
+ page_ext = section["page_ext"]
+ if self.page_ext_invalid(page_ext):
+ return gdb.Value(0)
+ return self.get_entry(page_ext, pfn)
+
+ def page_ext_get(self, page):
+ page_ext = self.lookup_page_ext(page)
+ if page_ext != gdb.Value(0):
+ return page_ext
+ else:
+ return gdb.Value(0)
+
+ def get_page_owner(self, page_ext):
+ addr = page_ext.cast(utils.get_ulong_type()) + gdb.parse_and_eval("page_owner_ops")["offset"].cast(utils.get_ulong_type())
+ return addr.cast(page_owner_t.get_type().pointer())
+
+ def read_page_owner_by_addr(self, struct_page_addr):
+ page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer())
+ pfn = self.p_ops.page_to_pfn(page)
+
+ if pfn < self.min_pfn or pfn > self.max_pfn or (not self.p_ops.pfn_valid(pfn)):
+ gdb.write("pfn is invalid\n")
+ return
+
+ page = self.p_ops.pfn_to_page(pfn)
+ page_ext = self.page_ext_get(page)
+
+ if page_ext == gdb.Value(0):
+ gdb.write("page_ext is null\n")
+ return
+
+ if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)):
+ gdb.write("page_owner flag is invalid\n")
+ raise gdb.GdbError('page_owner info is not present (never set?)\n')
+
+ if mm.test_bit(PAGE_EXT_OWNER_ALLOCATED, page_ext['flags'].address):
+ gdb.write('page_owner tracks the page as allocated\n')
+ else:
+ gdb.write('page_owner tracks the page as freed\n')
+
+ if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)):
+ gdb.write("page_owner is not allocated\n")
+
+ try:
+ page_owner = self.get_page_owner(page_ext)
+ gdb.write("Page last allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\
+ (page_owner["order"], page_owner["gfp_mask"],\
+ page_owner["pid"], page_owner["tgid"], page_owner["comm"],\
+ page_owner["ts_nsec"], page_owner["free_ts_nsec"]))
+ gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags']))
+ if page_owner["handle"] == 0:
+ gdb.write('page_owner allocation stack trace missing\n')
+ else:
+ stackdepot.stack_depot_print(page_owner["handle"])
+
+ if page_owner["free_handle"] == 0:
+ gdb.write('page_owner free stack trace missing\n')
+ else:
+ gdb.write('page last free stack trace:\n')
+ stackdepot.stack_depot_print(page_owner["free_handle"])
+ if page_owner['last_migrate_reason'] != -1:
+ gdb.write('page has been migrated, last migrate reason: %s\n' % self.migrate_reason_names[page_owner['last_migrate_reason']])
+ except:
+ gdb.write("\n")
+
+ def read_page_owner(self):
+ pfn = self.min_pfn
+
+ # Find a valid PFN or the start of a MAX_ORDER_NR_PAGES area
+ while ((not self.p_ops.pfn_valid(pfn)) and (pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1))) != 0:
+ pfn += 1
+
+ while pfn < self.max_pfn:
+ #
+ # If the new page is in a new MAX_ORDER_NR_PAGES area,
+ # validate the area as existing, skip it if not
+ #
+ if ((pfn & (self.p_ops.MAX_ORDER_NR_PAGES - 1)) == 0) and (not self.p_ops.pfn_valid(pfn)):
+ pfn += (self.p_ops.MAX_ORDER_NR_PAGES - 1)
+ continue;
+
+ page = self.p_ops.pfn_to_page(pfn)
+ page_ext = self.page_ext_get(page)
+ if page_ext == gdb.Value(0):
+ pfn += 1
+ continue
+
+ if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER)):
+ pfn += 1
+ continue
+ if not (page_ext['flags'] & (1 << PAGE_EXT_OWNER_ALLOCATED)):
+ pfn += 1
+ continue
+
+ try:
+ page_owner = self.get_page_owner(page_ext)
+ gdb.write("Page allocated via order %d, gfp_mask: 0x%x, pid: %d, tgid: %d (%s), ts %u ns, free_ts %u ns\n" %\
+ (page_owner["order"], page_owner["gfp_mask"],\
+ page_owner["pid"], page_owner["tgid"], page_owner["comm"],\
+ page_owner["ts_nsec"], page_owner["free_ts_nsec"]))
+ gdb.write("PFN: %d, Flags: 0x%x\n" % (pfn, page['flags']))
+ stackdepot.stack_depot_print(page_owner["handle"])
+ pfn += (1 << page_owner["order"])
+ continue
+ except:
+ gdb.write("\n")
+ pfn += 1
+
+DumpPageOwner()
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index 24f149694b81..89465f0de548 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -46,3 +46,4 @@ else:
import linux.interrupts
import linux.mm
import linux.stackdepot
+ import linux.page_owner
--
2.18.0


Subject: [PATCH 4/8] scripts/gdb/aarch64: add aarch64 page operation helper commands and configs

1. Move page table debugging from mm.py to pgtable.py.

2. Add aarch64 kernel config and memory constants value.

3. Add below aarch64 page operation helper commands.
page_to_pfn, page_to_phys, pfn_to_page, page_address,
virt_to_phys, sym_to_pfn, pfn_to_kaddr, virt_to_page.

4. Only support CONFIG_SPARSEMEM_VMEMMAP=y now.

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/constants.py.in | 31 ++
scripts/gdb/linux/mm.py | 579 +++++++++++++++++++-----------
scripts/gdb/linux/pgtable.py | 222 ++++++++++++
scripts/gdb/vmlinux-gdb.py | 3 +-
4 files changed, 631 insertions(+), 204 deletions(-)
create mode 100644 scripts/gdb/linux/pgtable.py

diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index 50a92c4e9984..7c44222bbf23 100644
--- a/scripts/gdb/linux/constants.py.in
+++ b/scripts/gdb/linux/constants.py.in
@@ -20,6 +20,7 @@
#include <linux/of_fdt.h>
#include <linux/radix-tree.h>
#include <linux/threads.h>
+#include <asm/memory.h>

/* We need to stringify expanded macros so that they can be parsed */

@@ -86,6 +87,13 @@ LX_GDBPARSED(RADIX_TREE_MAP_SIZE)
LX_GDBPARSED(RADIX_TREE_MAP_SHIFT)
LX_GDBPARSED(RADIX_TREE_MAP_MASK)

+/* asm/memory.h */
+if IS_BUILTIN(CONFIG_ARM64):
+ LX_GDBPARSED(PAGE_OFFSET)
+ LX_GDBPARSED(VA_BITS)
+ LX_GDBPARSED(VA_BITS_MIN)
+ LX_GDBPARSED(MODULES_VSIZE)
+
/* Kernel Configs */
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS)
LX_CONFIG(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST)
@@ -102,3 +110,26 @@ LX_CONFIG(CONFIG_X86_MCE_AMD)
LX_CONFIG(CONFIG_X86_MCE)
LX_CONFIG(CONFIG_X86_IO_APIC)
LX_CONFIG(CONFIG_HAVE_KVM)
+LX_CONFIG(CONFIG_NUMA)
+LX_CONFIG(CONFIG_ARM64)
+LX_CONFIG(CONFIG_ARM64_4K_PAGES)
+LX_CONFIG(CONFIG_ARM64_16K_PAGES)
+LX_CONFIG(CONFIG_ARM64_64K_PAGES)
+if IS_BUILTIN(CONFIG_ARM64):
+ LX_VALUE(CONFIG_ARM64_PA_BITS)
+ LX_VALUE(CONFIG_ARM64_VA_BITS)
+ LX_VALUE(CONFIG_ARM64_PAGE_SHIFT)
+ LX_VALUE(CONFIG_ARCH_FORCE_MAX_ORDER)
+LX_CONFIG(CONFIG_SPARSEMEM)
+LX_CONFIG(CONFIG_SPARSEMEM_EXTREME)
+LX_CONFIG(CONFIG_SPARSEMEM_VMEMMAP)
+LX_CONFIG(CONFIG_KASAN)
+LX_CONFIG(CONFIG_KASAN_GENERIC)
+LX_CONFIG(CONFIG_KASAN_SW_TAGS)
+LX_CONFIG(CONFIG_KASAN_HW_TAGS)
+if IS_BUILTIN(CONFIG_KASAN_GENERIC) or IS_BUILTIN(CONFIG_KASAN_SW_TAGS):
+ LX_VALUE(CONFIG_KASAN_SHADOW_OFFSET)
+LX_CONFIG(CONFIG_VMAP_STACK)
+if IS_BUILTIN(CONFIG_NUMA):
+ LX_VALUE(CONFIG_NODES_SHIFT)
+LX_CONFIG(CONFIG_DEBUG_VIRTUAL)
diff --git a/scripts/gdb/linux/mm.py b/scripts/gdb/linux/mm.py
index 30d837f3dfae..3c834c1b71ca 100644
--- a/scripts/gdb/linux/mm.py
+++ b/scripts/gdb/linux/mm.py
@@ -1,222 +1,395 @@
-# SPDX-License-Identifier: GPL-2.0-only
+# SPDX-License-Identifier: GPL-2.0
#
-# gdb helper commands and functions for Linux kernel debugging
-#
-# routines to introspect page table
+# Copyright (c) 2023 MediaTek Inc.
#
# Authors:
-# Dmitrii Bundin <[email protected]>
+# Kuan-Ying Lee <[email protected]>
#

import gdb
+import math
+from linux import utils, constants
+
+def DIV_ROUND_UP(n,d):
+ return ((n) + (d) - 1) // (d)

-from linux import utils
+def test_bit(nr, addr):
+ if addr.dereference() & (0x1 << nr):
+ return True
+ else:
+ return False

-PHYSICAL_ADDRESS_MASK = gdb.parse_and_eval('0xfffffffffffff')
+class page_ops():
+ ops = None
+ def __init__(self):
+ if not constants.LX_CONFIG_SPARSEMEM_VMEMMAP:
+ raise gdb.GdbError('Only support CONFIG_SPARSEMEM_VMEMMAP now')
+ if constants.LX_CONFIG_ARM64 and utils.is_target_arch('aarch64'):
+ self.ops = aarch64_page_ops()
+ else:
+ raise gdb.GdbError('Only support aarch64 now')

+class aarch64_page_ops():
+ def __init__(self):
+ self.SUBSECTION_SHIFT = 21
+ self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT
+ self.MODULES_VSIZE = 128 * 1024 * 1024

-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
+ if constants.LX_CONFIG_ARM64_64K_PAGES:
+ self.SECTION_SIZE_BITS = 29
+ else:
+ self.SECTION_SIZE_BITS = 27
+ self.MAX_PHYSMEM_BITS = constants.LX_CONFIG_ARM64_VA_BITS
+
+ self.PAGE_SHIFT = constants.LX_CONFIG_ARM64_PAGE_SHIFT
+ self.PAGE_SIZE = 1 << self.PAGE_SHIFT
+ self.PAGE_MASK = (~(self.PAGE_SIZE - 1)) & ((1 << 64) - 1)
+
+ self.VA_BITS = constants.LX_CONFIG_ARM64_VA_BITS
+ self.VA_BITS_MIN = constants.LX_VA_BITS_MIN
+ if self.VA_BITS > 48:
+ self.vabits_actual = gdb.parse_and_eval('vabits_actual')
+ else:
+ self.vabits_actual = self.VA_BITS
+ self.kimage_voffset = gdb.parse_and_eval('kimage_voffset') & ((1 << 64) - 1)
+
+ self.SECTIONS_SHIFT = self.MAX_PHYSMEM_BITS - self.SECTION_SIZE_BITS
+
+ if str(constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER).isdigit():
+ self.MAX_ORDER = constants.LX_CONFIG_ARCH_FORCE_MAX_ORDER
+ else:
+ self.MAX_ORDER = 11
+
+ self.MAX_ORDER_NR_PAGES = 1 << (self.MAX_ORDER - 1)
+ self.PFN_SECTION_SHIFT = self.SECTION_SIZE_BITS - self.PAGE_SHIFT
+ self.NR_MEM_SECTIONS = 1 << self.SECTIONS_SHIFT
+ self.PAGES_PER_SECTION = 1 << self.PFN_SECTION_SHIFT
+ self.PAGE_SECTION_MASK = (~(self.PAGES_PER_SECTION - 1)) & ((1 << 64) - 1)
+
+ if constants.LX_CONFIG_SPARSEMEM_EXTREME:
+ self.SECTIONS_PER_ROOT = self.PAGE_SIZE // gdb.lookup_type("struct mem_section").sizeof
+ else:
+ self.SECTIONS_PER_ROOT = 1
+
+ self.NR_SECTION_ROOTS = DIV_ROUND_UP(self.NR_MEM_SECTIONS, self.SECTIONS_PER_ROOT)
+ self.SECTION_ROOT_MASK = self.SECTIONS_PER_ROOT - 1
+ self.SUBSECTION_SHIFT = 21
+ self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT
+ self.PFN_SUBSECTION_SHIFT = self.SUBSECTION_SHIFT - self.PAGE_SHIFT
+ self.PAGES_PER_SUBSECTION = 1 << self.PFN_SUBSECTION_SHIFT
+
+ self.SECTION_HAS_MEM_MAP = 1 << int(gdb.parse_and_eval('SECTION_HAS_MEM_MAP_BIT'))
+ self.SECTION_IS_EARLY = 1 << int(gdb.parse_and_eval('SECTION_IS_EARLY_BIT'))
+
+ self.struct_page_size = utils.get_page_type().sizeof
+ self.STRUCT_PAGE_MAX_SHIFT = (int)(math.log(self.struct_page_size, 2))
+
+ self.PAGE_OFFSET = constants.LX_PAGE_OFFSET
+ self.MODULES_VSIZE = constants.LX_MODULES_VSIZE
+ self.MODULES_VADDR = self._PAGE_END(self.VA_BITS_MIN)
+ self.MODULES_END = self.MODULES_VADDR + self.MODULES_VSIZE
+
+ self.VMEMMAP_SHIFT = (self.PAGE_SHIFT - self.STRUCT_PAGE_MAX_SHIFT)
+ self.VMEMMAP_SIZE = ((self._PAGE_END(self.VA_BITS_MIN) - self.PAGE_OFFSET) >> self.VMEMMAP_SHIFT)
+ self.VMEMMAP_START = (-(1 << (self.VA_BITS - self.VMEMMAP_SHIFT))) & 0xffffffffffffffff
+ self.VMEMMAP_END = self.VMEMMAP_START + self.VMEMMAP_SIZE
+
+ self.VMALLOC_START = self.MODULES_END
+ self.VMALLOC_END = self.VMEMMAP_START - 256 * 1024 * 1024
+
+ self.memstart_addr = gdb.parse_and_eval("memstart_addr")
+ self.PHYS_OFFSET = self.memstart_addr
+ self.vmemmap = gdb.Value(self.VMEMMAP_START).cast(utils.get_page_type().pointer()) - (self.memstart_addr >> self.PAGE_SHIFT)
+
+ self.KERNEL_START = gdb.parse_and_eval("_text")
+ self.KERNEL_END = gdb.parse_and_eval("_end")
+
+ if constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS:
+ if constants.LX_CONFIG_KASAN_GENERIC:
+ self.KASAN_SHADOW_SCALE_SHIFT = 3
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)
+ self.KASAN_SHADOW_SCALE_SHIFT = 4
+ self.KASAN_SHADOW_OFFSET = constants.LX_CONFIG_KASAN_SHADOW_OFFSET
+ self.KASAN_SHADOW_END = (1 << (64 - self.KASAN_SHADOW_SCALE_SHIFT)) + self.KASAN_SHADOW_OFFSET
+ self.PAGE_END = self.KASAN_SHADOW_END - (1 << (self.vabits_actual - self.KASAN_SHADOW_SCALE_SHIFT))
+ else:
+ self.PAGE_END = self._PAGE_END(self.VA_BITS_MIN)
+
+ if constants.LX_CONFIG_NUMA and constants.LX_CONFIG_NODES_SHIFT:
+ self.NODE_SHIFT = constants.LX_CONFIG_NODES_SHIFT
+ else:
+ self.NODE_SHIFT = 0
+
+ self.MAX_NUMNODES = 1 << self.NODE_SHIFT
+
+ def SECTION_NR_TO_ROOT(self, sec):
+ return sec // self.SECTIONS_PER_ROOT
+
+ def __nr_to_section(self, nr):
+ root = self.SECTION_NR_TO_ROOT(nr)
+ mem_section = gdb.parse_and_eval("mem_section")
+ return mem_section[root][nr & self.SECTION_ROOT_MASK]
+
+ def pfn_to_section_nr(self, pfn):
+ return pfn >> self.PFN_SECTION_SHIFT
+
+ def section_nr_to_pfn(self, sec):
+ return sec << self.PFN_SECTION_SHIFT
+
+ def __pfn_to_section(self, pfn):
+ return self.__nr_to_section(self.pfn_to_section_nr(pfn))
+
+ def pfn_to_section(self, pfn):
+ return self.__pfn_to_section(pfn)
+
+ def subsection_map_index(self, pfn):
+ return (pfn & ~(self.PAGE_SECTION_MASK)) // self.PAGES_PER_SUBSECTION
+
+ def pfn_section_valid(self, ms, pfn):
+ if constants.LX_CONFIG_SPARSEMEM_VMEMMAP:
+ idx = self.subsection_map_index(pfn)
+ return test_bit(idx, ms['usage']['subsection_map'])
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]}
-"""
+ return True
+
+ def valid_section(self, mem_section):
+ if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_HAS_MEM_MAP):
+ return True
+ return False
+
+ def early_section(self, mem_section):
+ if mem_section != None and (mem_section['section_mem_map'] & self.SECTION_IS_EARLY):
+ return True
+ return False
+
+ def pfn_valid(self, pfn):
+ ms = None
+ if self.PHYS_PFN(self.PFN_PHYS(pfn)) != pfn:
+ return False
+ if self.pfn_to_section_nr(pfn) >= self.NR_MEM_SECTIONS:
+ return False
+ ms = self.__pfn_to_section(pfn)
+
+ if not self.valid_section(ms):
+ return False
+ return self.early_section(ms) or self.pfn_section_valid(ms, pfn)
+
+ def _PAGE_END(self, va):
+ return (-(1 << (va - 1))) & 0xffffffffffffffff
+
+ def kasan_reset_tag(self, addr):
+ if constants.LX_CONFIG_KASAN_SW_TAGS or constants.LX_CONFIG_KASAN_HW_TAGS:
+ return addr | (0xff << 56)
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"""
+ return addr
+
+ def __is_lm_address(self, addr):
+ if (addr - self.PAGE_OFFSET) < (self.PAGE_END - self.PAGE_OFFSET):
+ return True
+ else:
+ return False
+ def __lm_to_phys(self, addr):
+ return addr - self.PAGE_OFFSET + self.PHYS_OFFSET
+
+ def __kimg_to_phys(self, addr):
+ return addr - self.kimage_voffset
+
+ def __virt_to_phys_nodebug(self, va):
+ untagged_va = self.kasan_reset_tag(va)
+ if self.__is_lm_address(untagged_va):
+ return self.__lm_to_phys(untagged_va)
+ else:
+ return self.__kimg_to_phys(untagged_va)
+
+ def __virt_to_phys(self, va):
+ if constants.LX_CONFIG_DEBUG_VIRTUAL:
+ if not self.__is_lm_address(self.kasan_reset_tag(va)):
+ raise gdb.GdbError("Warning: virt_to_phys used for non-linear address: 0x%lx\n" % va)
+ return self.__virt_to_phys_nodebug(va)
+
+ def virt_to_phys(self, va):
+ return self.__virt_to_phys(va)
+
+ def PFN_PHYS(self, pfn):
+ return pfn << self.PAGE_SHIFT
+
+ def PHYS_PFN(self, phys):
+ return phys >> self.PAGE_SHIFT
+
+ def __phys_to_virt(self, pa):
+ return (pa - self.PHYS_OFFSET) | self.PAGE_OFFSET
+
+ def __phys_to_pfn(self, pa):
+ return self.PHYS_PFN(pa)
+
+ def __pfn_to_phys(self, pfn):
+ return self.PFN_PHYS(pfn)
+
+ def __pa_symbol_nodebug(self, x):
+ return self.__kimg_to_phys(x)
+
+ def __phys_addr_symbol(self, x):
+ if constants.LX_CONFIG_DEBUG_VIRTUAL:
+ if x < self.KERNEL_START or x > self.KERNEL_END:
+ raise gdb.GdbError("0x%x exceed kernel range" % x)
+ return self.__pa_symbol_nodebug(x)
+
+ def __pa_symbol(self, x):
+ return self.__phys_addr_symbol(x)
+
+ def __va(self, pa):
+ return self.__phys_to_virt(pa)
+
+ def pfn_to_kaddr(self, pfn):
+ return self.__va(pfn << self.PAGE_SHIFT)
+
+ def virt_to_pfn(self, va):
+ return self.__phys_to_pfn(self.__virt_to_phys(va))
+
+ def sym_to_pfn(self, x):
+ return self.__phys_to_pfn(self.__pa_symbol(x))
+
+ def page_to_pfn(self, page):
+ return int(page.cast(utils.get_page_type().pointer()) - self.vmemmap.cast(utils.get_page_type().pointer()))
+
+ def page_to_phys(self, page):
+ return self.__pfn_to_phys(self.page_to_pfn(page))
+
+ def pfn_to_page(self, pfn):
+ return (self.vmemmap + pfn).cast(utils.get_page_type().pointer())
+
+ def page_to_virt(self, page):
+ if constants.LX_CONFIG_DEBUG_VIRTUAL:
+ return self.__va(self.page_to_phys(page))
+ else:
+ __idx = int((page.cast(gdb.lookup_type("unsigned long")) - self.VMEMMAP_START).cast(utils.get_ulong_type())) // self.struct_page_size
+ return self.PAGE_OFFSET + (__idx * self.PAGE_SIZE)
+
+ def virt_to_page(self, va):
+ if constants.LX_CONFIG_DEBUG_VIRTUAL:
+ return self.pfn_to_page(self.virt_to_pfn(va))
+ else:
+ __idx = int(self.kasan_reset_tag(va) - self.PAGE_OFFSET) // self.PAGE_SIZE
+ addr = self.VMEMMAP_START + (__idx * self.struct_page_size)
+ return gdb.Value(addr).cast(utils.get_page_type().pointer())
+
+ def page_address(self, page):
+ return self.page_to_virt(page)
+
+ def folio_address(self, folio):
+ return self.page_address(folio['page'].address)
+
+class LxPFN2Page(gdb.Command):
+ """PFN to struct page"""

def __init__(self):
- super(TranslateVM, self).__init__('translate-vm', gdb.COMMAND_USER)
+ super(LxPFN2Page, self).__init__("lx-pfn_to_page", gdb.COMMAND_DATA)

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")
+ argv = gdb.string_to_argv(arg)
+ pfn = int(argv[0])
+ page = page_ops().ops.pfn_to_page(pfn)
+ gdb.write("pfn_to_page(0x%x) = 0x%x\n" % (pfn, page))
+
+LxPFN2Page()
+
+class LxPage2PFN(gdb.Command):
+ """struct page to PFN"""
+
+ def __init__(self):
+ super(LxPage2PFN, self).__init__("lx-page_to_pfn", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ struct_page_addr = int(argv[0], 16)
+ page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer())
+ pfn = page_ops().ops.page_to_pfn(page)
+ gdb.write("page_to_pfn(0x%x) = 0x%x\n" % (page, pfn))
+
+LxPage2PFN()
+
+class LxPageAddress(gdb.Command):
+ """struct page to linear mapping address"""
+
+ def __init__(self):
+ super(LxPageAddress, self).__init__("lx-page_address", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ struct_page_addr = int(argv[0], 16)
+ page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer())
+ addr = page_ops().ops.page_address(page)
+ gdb.write("page_address(0x%x) = 0x%x\n" % (page, addr))
+
+LxPageAddress()

+class LxPage2Phys(gdb.Command):
+ """struct page to physical address"""
+
+ def __init__(self):
+ super(LxPage2Phys, self).__init__("lx-page_to_phys", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ struct_page_addr = int(argv[0], 16)
+ page = gdb.Value(struct_page_addr).cast(utils.get_page_type().pointer())
+ phys_addr = page_ops().ops.page_to_phys(page)
+ gdb.write("page_to_phys(0x%x) = 0x%x\n" % (page, phys_addr))
+
+LxPage2Phys()
+
+class LxVirt2Phys(gdb.Command):
+ """virtual address to physical address"""
+
+ def __init__(self):
+ super(LxVirt2Phys, self).__init__("lx-virt_to_phys", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ linear_addr = int(argv[0], 16)
+ phys_addr = page_ops().ops.virt_to_phys(linear_addr)
+ gdb.write("virt_to_phys(0x%x) = 0x%x\n" % (linear_addr, phys_addr))
+
+LxVirt2Phys()
+
+class LxVirt2Page(gdb.Command):
+ """virtual address to struct page"""
+
+ def __init__(self):
+ super(LxVirt2Page, self).__init__("lx-virt_to_page", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ linear_addr = int(argv[0], 16)
+ page = page_ops().ops.virt_to_page(linear_addr)
+ gdb.write("virt_to_page(0x%x) = 0x%x\n" % (linear_addr, page))
+
+LxVirt2Page()
+
+class LxSym2PFN(gdb.Command):
+ """symbol address to PFN"""
+
+ def __init__(self):
+ super(LxSym2PFN, self).__init__("lx-sym_to_pfn", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ sym_addr = int(argv[0], 16)
+ pfn = page_ops().ops.sym_to_pfn(sym_addr)
+ gdb.write("sym_to_pfn(0x%x) = %d\n" % (sym_addr, pfn))
+
+LxSym2PFN()
+
+class LxPFN2Kaddr(gdb.Command):
+ """PFN to kernel address"""
+
+ def __init__(self):
+ super(LxPFN2Kaddr, self).__init__("lx-pfn_to_kaddr", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ argv = gdb.string_to_argv(arg)
+ pfn = int(argv[0])
+ kaddr = page_ops().ops.pfn_to_kaddr(pfn)
+ gdb.write("pfn_to_kaddr(%d) = 0x%x\n" % (pfn, kaddr))

-TranslateVM()
+LxPFN2Kaddr()
diff --git a/scripts/gdb/linux/pgtable.py b/scripts/gdb/linux/pgtable.py
new file mode 100644
index 000000000000..30d837f3dfae
--- /dev/null
+++ b/scripts/gdb/linux/pgtable.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 <[email protected]>
+#
+
+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()
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index 2d32308c3f7a..fd24574bcec9 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -41,6 +41,7 @@ else:
import linux.genpd
import linux.device
import linux.vfs
- import linux.mm
+ import linux.pgtable
import linux.radixtree
import linux.interrupts
+ import linux.mm
--
2.18.0


Subject: [PATCH 2/8] scripts/gdb/modules: add get module text support

When we get an text address from coredump and we cannot find
this address in vmlinux, it might located in kernel module.

We want to know which kernel module it located in.

This GDB scripts can help us to find the target kernel module.

(gdb) lx-getmod-by-textaddr 0xffff800002d305ac
0xffff800002d305ac is in kasan_test.ko

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/modules.py | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/scripts/gdb/linux/modules.py b/scripts/gdb/linux/modules.py
index 261f28640f4c..54bb7fcad41d 100644
--- a/scripts/gdb/linux/modules.py
+++ b/scripts/gdb/linux/modules.py
@@ -91,5 +91,35 @@ class LxLsmod(gdb.Command):

gdb.write("\n")

-
LxLsmod()
+
+def help():
+ t = """Usage: lx-getmod-by-textaddr [Heximal Address]
+ Example: lx-getmod-by-textaddr 0xffff800002d305ac\n"""
+ gdb.write("Unrecognized command\n")
+ raise gdb.GdbError(t)
+
+class LxFindTextAddrinMod(gdb.Command):
+ '''Look up loaded kernel module by text address.'''
+
+ def __init__(self):
+ super(LxFindTextAddrinMod, self).__init__('lx-getmod-by-textaddr', gdb.COMMAND_SUPPORT)
+
+ def invoke(self, arg, from_tty):
+ args = gdb.string_to_argv(arg)
+
+ if len(args) != 1:
+ help()
+
+ addr = gdb.Value(int(args[0], 16)).cast(utils.get_ulong_type())
+ for mod in module_list():
+ mod_text_start = mod['mem'][constants.LX_MOD_TEXT]['base']
+ mod_text_end = mod_text_start + mod['mem'][constants.LX_MOD_TEXT]['size'].cast(utils.get_ulong_type())
+
+ if addr >= mod_text_start and addr < mod_text_end:
+ s = "0x%x" % addr + " is in " + mod['name'].string() + ".ko\n"
+ gdb.write(s)
+ return
+ gdb.write("0x%x is not in any module text section\n" % addr)
+
+LxFindTextAddrinMod()
--
2.18.0


Subject: [PATCH 8/8] scripts/gdb/vmalloc: add vmallocinfo support

This GDB script shows the vmallocinfo for user to
analyze the vmalloc memory usage.

Example output:
0xffff800008000000-0xffff800008009000 36864 <start_kernel+372> pages=8 vmalloc
0xffff800008009000-0xffff80000800b000 8192 <gicv2m_init_one+400> phys=0x8020000 ioremap
0xffff80000800b000-0xffff80000800d000 8192 <bpf_prog_alloc_no_stats+72> pages=1 vmalloc
0xffff80000800d000-0xffff80000800f000 8192 <bpf_jit_alloc_exec+16> pages=1 vmalloc
0xffff800008010000-0xffff80000ad30000 47316992 <paging_init+452> phys=0x40210000 vmap
0xffff80000ad30000-0xffff80000c1c0000 21561344 <paging_init+556> phys=0x42f30000 vmap
0xffff80000c1c0000-0xffff80000c370000 1769472 <paging_init+592> phys=0x443c0000 vmap
0xffff80000c370000-0xffff80000de90000 28442624 <paging_init+692> phys=0x44570000 vmap
0xffff80000de90000-0xffff80000f4c1000 23269376 <paging_init+788> phys=0x46090000 vmap
0xffff80000f4c1000-0xffff80000f4c3000 8192 <gen_pool_add_owner+112> pages=1 vmalloc
0xffff80000f4c3000-0xffff80000f4c5000 8192 <gen_pool_add_owner+112> pages=1 vmalloc
0xffff80000f4c5000-0xffff80000f4c7000 8192 <gen_pool_add_owner+112> pages=1 vmalloc

Signed-off-by: Kuan-Ying Lee <[email protected]>
---
scripts/gdb/linux/constants.py.in | 8 +++++
scripts/gdb/linux/vmalloc.py | 56 +++++++++++++++++++++++++++++++
scripts/gdb/vmlinux-gdb.py | 1 +
3 files changed, 65 insertions(+)
create mode 100644 scripts/gdb/linux/vmalloc.py

diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index fa23f4e3546a..3cf3c0b9eaea 100644
--- a/scripts/gdb/linux/constants.py.in
+++ b/scripts/gdb/linux/constants.py.in
@@ -22,6 +22,7 @@
#include <linux/radix-tree.h>
#include <linux/slab.h>
#include <linux/threads.h>
+#include <linux/vmalloc.h>
#include <asm/memory.h>

/* We need to stringify expanded macros so that they can be parsed */
@@ -96,6 +97,13 @@ if IS_BUILTIN(CONFIG_ARM64):
LX_GDBPARSED(VA_BITS_MIN)
LX_GDBPARSED(MODULES_VSIZE)

+/* linux/vmalloc.h */
+LX_VALUE(VM_IOREMAP)
+LX_VALUE(VM_ALLOC)
+LX_VALUE(VM_MAP)
+LX_VALUE(VM_USERMAP)
+LX_VALUE(VM_DMA_COHERENT)
+
/* linux/page_ext.h */
LX_GDBPARSED(PAGE_EXT_OWNER)
LX_GDBPARSED(PAGE_EXT_OWNER_ALLOCATED)
diff --git a/scripts/gdb/linux/vmalloc.py b/scripts/gdb/linux/vmalloc.py
new file mode 100644
index 000000000000..48e4a4fae7bb
--- /dev/null
+++ b/scripts/gdb/linux/vmalloc.py
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2023 MediaTek Inc.
+#
+# Authors:
+# Kuan-Ying Lee <[email protected]>
+#
+
+import gdb
+import re
+from linux import lists, utils, stackdepot, constants, mm
+
+vmap_area_type = utils.CachedType('struct vmap_area')
+vmap_area_ptr_type = vmap_area_type.get_type().pointer()
+
+def is_vmalloc_addr(x):
+ pg_ops = mm.page_ops().ops
+ addr = pg_ops.kasan_reset_tag(x)
+ return addr >= pg_ops.VMALLOC_START and addr < pg_ops.VMALLOC_END
+
+class LxVmallocInfo(gdb.Command):
+ """Show vmallocinfo"""
+
+ def __init__(self):
+ super(LxVmallocInfo, self).__init__("lx-vmallocinfo", gdb.COMMAND_DATA)
+
+ def invoke(self, arg, from_tty):
+ vmap_area_list = gdb.parse_and_eval('vmap_area_list')
+ for vmap_area in lists.list_for_each_entry(vmap_area_list, vmap_area_ptr_type, "list"):
+ if not vmap_area['vm']:
+ gdb.write("0x%x-0x%x %10d vm_map_ram\n" % (vmap_area['va_start'], vmap_area['va_end'],
+ vmap_area['va_end'] - vmap_area['va_start']))
+ continue
+ v = vmap_area['vm']
+ gdb.write("0x%x-0x%x %10d" % (v['addr'], v['addr'] + v['size'], v['size']))
+ if v['caller']:
+ gdb.write(" %s" % str(v['caller']).split(' ')[-1])
+ if v['nr_pages']:
+ gdb.write(" pages=%d" % v['nr_pages'])
+ if v['phys_addr']:
+ gdb.write(" phys=0x%x" % v['phys_addr'])
+ if v['flags'] & constants.LX_VM_IOREMAP:
+ gdb.write(" ioremap")
+ if v['flags'] & constants.LX_VM_ALLOC:
+ gdb.write(" vmalloc")
+ if v['flags'] & constants.LX_VM_MAP:
+ gdb.write(" vmap")
+ if v['flags'] & constants.LX_VM_USERMAP:
+ gdb.write(" user")
+ if v['flags'] & constants.LX_VM_DMA_COHERENT:
+ gdb.write(" dma-coherent")
+ if is_vmalloc_addr(v['pages']):
+ gdb.write(" vpages")
+ gdb.write("\n")
+
+LxVmallocInfo()
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index 2526364f31fd..fc53cdf286f1 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -48,3 +48,4 @@ else:
import linux.stackdepot
import linux.page_owner
import linux.slab
+ import linux.vmalloc
--
2.18.0