summaryrefslogtreecommitdiff
path: root/scripts/gdb
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/gdb')
-rw-r--r--scripts/gdb/linux/bpf.py253
-rw-r--r--scripts/gdb/linux/constants.py.in22
-rw-r--r--scripts/gdb/linux/cpus.py28
-rw-r--r--scripts/gdb/linux/interrupts.py16
-rw-r--r--scripts/gdb/linux/kasan.py44
-rw-r--r--scripts/gdb/linux/mapletree.py252
-rw-r--r--scripts/gdb/linux/mm.py23
-rw-r--r--scripts/gdb/linux/modules.py3
-rw-r--r--scripts/gdb/linux/pgtable.py4
-rw-r--r--scripts/gdb/linux/proc.py4
-rw-r--r--scripts/gdb/linux/radixtree.py139
-rw-r--r--scripts/gdb/linux/rbtree.py12
-rw-r--r--scripts/gdb/linux/stackdepot.py35
-rw-r--r--scripts/gdb/linux/symbols.py214
-rw-r--r--scripts/gdb/linux/timerlist.py33
-rw-r--r--scripts/gdb/linux/utils.py57
-rw-r--r--scripts/gdb/linux/vfs.py2
-rw-r--r--scripts/gdb/linux/xarray.py28
-rw-r--r--scripts/gdb/vmlinux-gdb.py1
19 files changed, 1075 insertions, 95 deletions
diff --git a/scripts/gdb/linux/bpf.py b/scripts/gdb/linux/bpf.py
new file mode 100644
index 000000000000..1870534ef6f9
--- /dev/null
+++ b/scripts/gdb/linux/bpf.py
@@ -0,0 +1,253 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import json
+import subprocess
+import tempfile
+
+import gdb
+
+from linux import constants, lists, radixtree, utils
+
+
+if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT:
+ bpf_ksym_type = utils.CachedType("struct bpf_ksym")
+if constants.LX_CONFIG_BPF_SYSCALL:
+ bpf_prog_type = utils.CachedType("struct bpf_prog")
+
+
+def get_ksym_name(ksym):
+ name = ksym["name"].bytes
+ end = name.find(b"\x00")
+ if end != -1:
+ name = name[:end]
+ return name.decode()
+
+
+def list_ksyms():
+ if not (constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT):
+ return []
+ bpf_kallsyms = gdb.parse_and_eval("&bpf_kallsyms")
+ bpf_ksym_ptr_type = bpf_ksym_type.get_type().pointer()
+ return list(lists.list_for_each_entry(bpf_kallsyms,
+ bpf_ksym_ptr_type,
+ "lnode"))
+
+
+class KsymAddBreakpoint(gdb.Breakpoint):
+ def __init__(self, monitor):
+ super(KsymAddBreakpoint, self).__init__("bpf_ksym_add", internal=True)
+ self.silent = True
+ self.monitor = monitor
+
+ def stop(self):
+ self.monitor.add(gdb.parse_and_eval("ksym"))
+ return False
+
+
+class KsymRemoveBreakpoint(gdb.Breakpoint):
+ def __init__(self, monitor):
+ super(KsymRemoveBreakpoint, self).__init__("bpf_ksym_del",
+ internal=True)
+ self.silent = True
+ self.monitor = monitor
+
+ def stop(self):
+ self.monitor.remove(gdb.parse_and_eval("ksym"))
+ return False
+
+
+class KsymMonitor:
+ def __init__(self, add, remove):
+ self.add = add
+ self.remove = remove
+
+ self.add_bp = KsymAddBreakpoint(self)
+ self.remove_bp = KsymRemoveBreakpoint(self)
+
+ self.notify_initial()
+
+ def notify_initial(self):
+ for ksym in list_ksyms():
+ self.add(ksym)
+
+ def delete(self):
+ self.add_bp.delete()
+ self.remove_bp.delete()
+
+
+def list_progs():
+ if not constants.LX_CONFIG_BPF_SYSCALL:
+ return []
+ idr_rt = gdb.parse_and_eval("&prog_idr.idr_rt")
+ bpf_prog_ptr_type = bpf_prog_type.get_type().pointer()
+ progs = []
+ for _, slot in radixtree.for_each_slot(idr_rt):
+ prog = slot.dereference().cast(bpf_prog_ptr_type)
+ progs.append(prog)
+ # Subprogs are not registered in prog_idr, fetch them manually.
+ # func[0] is the current prog.
+ aux = prog["aux"]
+ func = aux["func"]
+ real_func_cnt = int(aux["real_func_cnt"])
+ for i in range(1, real_func_cnt):
+ progs.append(func[i])
+ return progs
+
+
+class ProgAddBreakpoint(gdb.Breakpoint):
+ def __init__(self, monitor):
+ super(ProgAddBreakpoint, self).__init__("bpf_prog_kallsyms_add",
+ internal=True)
+ self.silent = True
+ self.monitor = monitor
+
+ def stop(self):
+ self.monitor.add(gdb.parse_and_eval("fp"))
+ return False
+
+
+class ProgRemoveBreakpoint(gdb.Breakpoint):
+ def __init__(self, monitor):
+ super(ProgRemoveBreakpoint, self).__init__("bpf_prog_free_id",
+ internal=True)
+ self.silent = True
+ self.monitor = monitor
+
+ def stop(self):
+ self.monitor.remove(gdb.parse_and_eval("prog"))
+ return False
+
+
+class ProgMonitor:
+ def __init__(self, add, remove):
+ self.add = add
+ self.remove = remove
+
+ self.add_bp = ProgAddBreakpoint(self)
+ self.remove_bp = ProgRemoveBreakpoint(self)
+
+ self.notify_initial()
+
+ def notify_initial(self):
+ for prog in list_progs():
+ self.add(prog)
+
+ def delete(self):
+ self.add_bp.delete()
+ self.remove_bp.delete()
+
+
+def btf_str_by_offset(btf, offset):
+ while offset < btf["start_str_off"]:
+ btf = btf["base_btf"]
+
+ offset -= btf["start_str_off"]
+ if offset < btf["hdr"]["str_len"]:
+ return (btf["strings"] + offset).string()
+
+ return None
+
+
+def bpf_line_info_line_num(line_col):
+ return line_col >> 10
+
+
+def bpf_line_info_line_col(line_col):
+ return line_col & 0x3ff
+
+
+class LInfoIter:
+ def __init__(self, prog):
+ # See bpf_prog_get_file_line() for details.
+ self.pos = 0
+ self.nr_linfo = 0
+
+ if prog is None:
+ return
+
+ self.bpf_func = int(prog["bpf_func"])
+ aux = prog["aux"]
+ self.btf = aux["btf"]
+ linfo_idx = aux["linfo_idx"]
+ self.nr_linfo = int(aux["nr_linfo"]) - linfo_idx
+ if self.nr_linfo == 0:
+ return
+
+ linfo_ptr = aux["linfo"]
+ tpe = linfo_ptr.type.target().array(self.nr_linfo).pointer()
+ self.linfo = (linfo_ptr + linfo_idx).cast(tpe).dereference()
+ jited_linfo_ptr = aux["jited_linfo"]
+ tpe = jited_linfo_ptr.type.target().array(self.nr_linfo).pointer()
+ self.jited_linfo = (jited_linfo_ptr + linfo_idx).cast(tpe).dereference()
+
+ self.filenos = {}
+
+ def get_code_off(self):
+ if self.pos >= self.nr_linfo:
+ return -1
+ return self.jited_linfo[self.pos] - self.bpf_func
+
+ def advance(self):
+ self.pos += 1
+
+ def get_fileno(self):
+ file_name_off = int(self.linfo[self.pos]["file_name_off"])
+ fileno = self.filenos.get(file_name_off)
+ if fileno is not None:
+ return fileno, None
+ file_name = btf_str_by_offset(self.btf, file_name_off)
+ fileno = len(self.filenos) + 1
+ self.filenos[file_name_off] = fileno
+ return fileno, file_name
+
+ def get_line_col(self):
+ line_col = int(self.linfo[self.pos]["line_col"])
+ return bpf_line_info_line_num(line_col), \
+ bpf_line_info_line_col(line_col)
+
+
+def generate_debug_obj(ksym, prog):
+ name = get_ksym_name(ksym)
+ # Avoid read_memory(); it throws bogus gdb.MemoryError in some contexts.
+ start = ksym["start"]
+ code = start.cast(gdb.lookup_type("unsigned char")
+ .array(int(ksym["end"]) - int(start))
+ .pointer()).dereference().bytes
+ linfo_iter = LInfoIter(prog)
+
+ result = tempfile.NamedTemporaryFile(suffix=".o", mode="wb")
+ try:
+ with tempfile.NamedTemporaryFile(suffix=".s", mode="w") as src:
+ # ".loc" does not apply to ".byte"s, only to ".insn"s, but since
+ # this needs to work for all architectures, the latter are not an
+ # option. Ask the assembler to apply ".loc"s to labels as well,
+ # and generate dummy labels after each ".loc".
+ src.write(".loc_mark_labels 1\n")
+
+ src.write(".globl {}\n".format(name))
+ src.write(".type {},@function\n".format(name))
+ src.write("{}:\n".format(name))
+ for code_off, code_byte in enumerate(code):
+ if linfo_iter.get_code_off() == code_off:
+ fileno, file_name = linfo_iter.get_fileno()
+ if file_name is not None:
+ src.write(".file {} {}\n".format(
+ fileno, json.dumps(file_name)))
+ line, col = linfo_iter.get_line_col()
+ src.write(".loc {} {} {}\n".format(fileno, line, col))
+ src.write("0:\n")
+ linfo_iter.advance()
+ src.write(".byte {}\n".format(code_byte))
+ src.write(".size {},{}\n".format(name, len(code)))
+ src.flush()
+
+ try:
+ subprocess.check_call(["as", "-c", src.name, "-o", result.name])
+ except FileNotFoundError:
+ # "as" is not installed.
+ result.close()
+ return None
+ return result
+ except:
+ result.close()
+ raise
diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
index fd6bd69c5096..6d475540c6ba 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/maple_tree.h>
#include <linux/slab.h>
#include <linux/threads.h>
#include <linux/vmalloc.h>
@@ -73,12 +74,12 @@ if IS_BUILTIN(CONFIG_MODULES):
LX_GDBPARSED(MOD_RO_AFTER_INIT)
/* linux/mount.h */
-LX_VALUE(MNT_NOSUID)
-LX_VALUE(MNT_NODEV)
-LX_VALUE(MNT_NOEXEC)
-LX_VALUE(MNT_NOATIME)
-LX_VALUE(MNT_NODIRATIME)
-LX_VALUE(MNT_RELATIME)
+LX_GDBPARSED(MNT_NOSUID)
+LX_GDBPARSED(MNT_NODEV)
+LX_GDBPARSED(MNT_NOEXEC)
+LX_GDBPARSED(MNT_NOATIME)
+LX_GDBPARSED(MNT_NODIRATIME)
+LX_GDBPARSED(MNT_RELATIME)
/* linux/threads.h */
LX_VALUE(NR_CPUS)
@@ -93,6 +94,12 @@ LX_GDBPARSED(RADIX_TREE_MAP_SIZE)
LX_GDBPARSED(RADIX_TREE_MAP_SHIFT)
LX_GDBPARSED(RADIX_TREE_MAP_MASK)
+/* linux/maple_tree.h */
+LX_VALUE(MAPLE_NODE_SLOTS)
+LX_VALUE(MAPLE_RANGE64_SLOTS)
+LX_VALUE(MAPLE_ARANGE64_SLOTS)
+LX_GDBPARSED(MAPLE_NODE_MASK)
+
/* linux/vmalloc.h */
LX_VALUE(VM_IOREMAP)
LX_VALUE(VM_ALLOC)
@@ -163,3 +170,6 @@ LX_CONFIG(CONFIG_PAGE_OWNER)
LX_CONFIG(CONFIG_SLUB_DEBUG)
LX_CONFIG(CONFIG_SLAB_FREELIST_HARDENED)
LX_CONFIG(CONFIG_MMU)
+LX_CONFIG(CONFIG_BPF)
+LX_CONFIG(CONFIG_BPF_JIT)
+LX_CONFIG(CONFIG_BPF_SYSCALL)
diff --git a/scripts/gdb/linux/cpus.py b/scripts/gdb/linux/cpus.py
index 2f11c4f9c345..6edf4ef61636 100644
--- a/scripts/gdb/linux/cpus.py
+++ b/scripts/gdb/linux/cpus.py
@@ -46,7 +46,7 @@ def per_cpu(var_ptr, cpu):
# !CONFIG_SMP case
offset = 0
pointer = var_ptr.cast(utils.get_long_type()) + offset
- return pointer.cast(var_ptr.type).dereference()
+ return pointer.cast(var_ptr.type)
cpu_mask = {}
@@ -141,7 +141,7 @@ LxCpus()
class PerCpu(gdb.Function):
"""Return per-cpu variable.
-$lx_per_cpu("VAR"[, CPU]): Return the per-cpu variable called VAR for the
+$lx_per_cpu(VAR[, CPU]): Return the per-cpu variable called VAR for the
given CPU number. If CPU is omitted, the CPU of the current context is used.
Note that VAR has to be quoted as string."""
@@ -149,11 +149,29 @@ Note that VAR has to be quoted as string."""
super(PerCpu, self).__init__("lx_per_cpu")
def invoke(self, var, cpu=-1):
- return per_cpu(var.address, cpu)
+ return per_cpu(var.address, cpu).dereference()
PerCpu()
+
+class PerCpuPtr(gdb.Function):
+ """Return per-cpu pointer.
+
+$lx_per_cpu_ptr(VAR[, CPU]): Return the per-cpu pointer called VAR for the
+given CPU number. If CPU is omitted, the CPU of the current context is used.
+Note that VAR has to be quoted as string."""
+
+ def __init__(self):
+ super(PerCpuPtr, self).__init__("lx_per_cpu_ptr")
+
+ def invoke(self, var, cpu=-1):
+ return per_cpu(var, cpu)
+
+
+PerCpuPtr()
+
+
def get_current_task(cpu):
task_ptr_type = task_type.get_type().pointer()
@@ -164,10 +182,10 @@ def get_current_task(cpu):
var_ptr = gdb.parse_and_eval("(struct task_struct *)cpu_tasks[0].task")
return var_ptr.dereference()
else:
- var_ptr = gdb.parse_and_eval("&pcpu_hot.current_task")
+ var_ptr = gdb.parse_and_eval("&current_task")
return per_cpu(var_ptr, cpu).dereference()
elif utils.is_target_arch("aarch64"):
- current_task_addr = gdb.parse_and_eval("$SP_EL0")
+ current_task_addr = gdb.parse_and_eval("(unsigned long)$SP_EL0")
if (current_task_addr >> 63) != 0:
current_task = current_task_addr.cast(task_ptr_type)
return current_task.dereference()
diff --git a/scripts/gdb/linux/interrupts.py b/scripts/gdb/linux/interrupts.py
index 616a5f26377a..f4f715a8f0e3 100644
--- a/scripts/gdb/linux/interrupts.py
+++ b/scripts/gdb/linux/interrupts.py
@@ -7,7 +7,7 @@ import gdb
from linux import constants
from linux import cpus
from linux import utils
-from linux import radixtree
+from linux import mapletree
irq_desc_type = utils.CachedType("struct irq_desc")
@@ -23,12 +23,12 @@ def irqd_is_level(desc):
def show_irq_desc(prec, irq):
text = ""
- desc = radixtree.lookup(gdb.parse_and_eval("&irq_desc_tree"), irq)
+ desc = mapletree.mtree_load(gdb.parse_and_eval("&sparse_irqs"), irq)
if desc is None:
return text
- desc = desc.cast(irq_desc_type.get_type())
- if desc is None:
+ desc = desc.cast(irq_desc_type.get_type().pointer())
+ if desc == 0:
return text
if irq_settings_is_hidden(desc):
@@ -110,7 +110,7 @@ def x86_show_mce(prec, var, pfx, desc):
pvar = gdb.parse_and_eval(var)
text = "%*s: " % (prec, pfx)
for cpu in cpus.each_online_cpu():
- text += "%10u " % (cpus.per_cpu(pvar, cpu))
+ text += "%10u " % (cpus.per_cpu(pvar, cpu).dereference())
text += " %s\n" % (desc)
return text
@@ -142,7 +142,7 @@ def x86_show_interupts(prec):
if constants.LX_CONFIG_X86_MCE:
text += x86_show_mce(prec, "&mce_exception_count", "MCE", "Machine check exceptions")
- text == x86_show_mce(prec, "&mce_poll_count", "MCP", "Machine check polls")
+ text += x86_show_mce(prec, "&mce_poll_count", "MCP", "Machine check polls")
text += show_irq_err_count(prec)
@@ -221,8 +221,8 @@ class LxInterruptList(gdb.Command):
gdb.write("CPU%-8d" % cpu)
gdb.write("\n")
- if utils.gdb_eval_or_none("&irq_desc_tree") is None:
- return
+ if utils.gdb_eval_or_none("&sparse_irqs") is None:
+ raise gdb.GdbError("Unable to find the sparse IRQ tree, is CONFIG_SPARSE_IRQ enabled?")
for irq in range(nr_irqs):
gdb.write(show_irq_desc(prec, irq))
diff --git a/scripts/gdb/linux/kasan.py b/scripts/gdb/linux/kasan.py
new file mode 100644
index 000000000000..56730b3fde0b
--- /dev/null
+++ b/scripts/gdb/linux/kasan.py
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright 2024 Canonical Ltd.
+#
+# Authors:
+# Kuan-Ying Lee <kuan-ying.lee@canonical.com>
+#
+
+import gdb
+from linux import constants, mm
+
+def help():
+ t = """Usage: lx-kasan_mem_to_shadow [Hex memory addr]
+ Example:
+ lx-kasan_mem_to_shadow 0xffff000008eca008\n"""
+ gdb.write("Unrecognized command\n")
+ raise gdb.GdbError(t)
+
+class KasanMemToShadow(gdb.Command):
+ """Translate memory address to kasan shadow address"""
+
+ p_ops = None
+
+ def __init__(self):
+ if constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS:
+ super(KasanMemToShadow, self).__init__("lx-kasan_mem_to_shadow", gdb.COMMAND_SUPPORT)
+
+ def invoke(self, args, from_tty):
+ if not constants.LX_CONFIG_KASAN_GENERIC or constants.LX_CONFIG_KASAN_SW_TAGS:
+ raise gdb.GdbError('CONFIG_KASAN_GENERIC or CONFIG_KASAN_SW_TAGS is not set')
+
+ argv = gdb.string_to_argv(args)
+ if len(argv) == 1:
+ if self.p_ops is None:
+ self.p_ops = mm.page_ops().ops
+ addr = int(argv[0], 16)
+ shadow_addr = self.kasan_mem_to_shadow(addr)
+ gdb.write('shadow addr: 0x%x\n' % shadow_addr)
+ else:
+ help()
+ def kasan_mem_to_shadow(self, addr):
+ return (addr >> self.p_ops.KASAN_SHADOW_SCALE_SHIFT) + self.p_ops.KASAN_SHADOW_OFFSET
+
+KasanMemToShadow()
diff --git a/scripts/gdb/linux/mapletree.py b/scripts/gdb/linux/mapletree.py
new file mode 100644
index 000000000000..d52d51c0a03f
--- /dev/null
+++ b/scripts/gdb/linux/mapletree.py
@@ -0,0 +1,252 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Maple tree helpers
+#
+# Copyright (c) 2025 Broadcom
+#
+# Authors:
+# Florian Fainelli <florian.fainelli@broadcom.com>
+
+import gdb
+
+from linux import utils
+from linux import constants
+from linux import xarray
+
+maple_tree_root_type = utils.CachedType("struct maple_tree")
+maple_node_type = utils.CachedType("struct maple_node")
+maple_enode_type = utils.CachedType("void")
+
+maple_dense = 0
+maple_leaf_64 = 1
+maple_range_64 = 2
+maple_arange_64 = 3
+
+class Mas(object):
+ ma_active = 0
+ ma_start = 1
+ ma_root = 2
+ ma_none = 3
+ ma_pause = 4
+ ma_overflow = 5
+ ma_underflow = 6
+ ma_error = 7
+
+ def __init__(self, mt, first, end):
+ if mt.type == maple_tree_root_type.get_type().pointer():
+ self.tree = mt.dereference()
+ elif mt.type != maple_tree_root_type.get_type():
+ raise gdb.GdbError("must be {} not {}"
+ .format(maple_tree_root_type.get_type().pointer(), mt.type))
+ self.tree = mt
+ self.index = first
+ self.last = end
+ self.node = None
+ self.status = self.ma_start
+ self.min = 0
+ self.max = -1
+
+ def is_start(self):
+ # mas_is_start()
+ return self.status == self.ma_start
+
+ def is_ptr(self):
+ # mas_is_ptr()
+ return self.status == self.ma_root
+
+ def is_none(self):
+ # mas_is_none()
+ return self.status == self.ma_none
+
+ def root(self):
+ # mas_root()
+ return self.tree['ma_root'].cast(maple_enode_type.get_type().pointer())
+
+ def start(self):
+ # mas_start()
+ if self.is_start() is False:
+ return None
+
+ self.min = 0
+ self.max = ~0
+
+ while True:
+ self.depth = 0
+ root = self.root()
+ if xarray.xa_is_node(root):
+ self.depth = 0
+ self.status = self.ma_active
+ self.node = mte_safe_root(root)
+ self.offset = 0
+ if mte_dead_node(self.node) is True:
+ continue
+
+ return None
+
+ self.node = None
+ # Empty tree
+ if root is None:
+ self.status = self.ma_none
+ self.offset = constants.LX_MAPLE_NODE_SLOTS
+ return None
+
+ # Single entry tree
+ self.status = self.ma_root
+ self.offset = constants.LX_MAPLE_NODE_SLOTS
+
+ if self.index != 0:
+ return None
+
+ return root
+
+ return None
+
+ def reset(self):
+ # mas_reset()
+ self.status = self.ma_start
+ self.node = None
+
+def mte_safe_root(node):
+ if node.type != maple_enode_type.get_type().pointer():
+ raise gdb.GdbError("{} must be {} not {}"
+ .format(mte_safe_root.__name__, maple_enode_type.get_type().pointer(), node.type))
+ ulong_type = utils.get_ulong_type()
+ indirect_ptr = node.cast(ulong_type) & ~0x2
+ val = indirect_ptr.cast(maple_enode_type.get_type().pointer())
+ return val
+
+def mte_node_type(entry):
+ ulong_type = utils.get_ulong_type()
+ val = None
+ if entry.type == maple_enode_type.get_type().pointer():
+ val = entry.cast(ulong_type)
+ elif entry.type == ulong_type:
+ val = entry
+ else:
+ raise gdb.GdbError("{} must be {} not {}"
+ .format(mte_node_type.__name__, maple_enode_type.get_type().pointer(), entry.type))
+ return (val >> 0x3) & 0xf
+
+def ma_dead_node(node):
+ if node.type != maple_node_type.get_type().pointer():
+ raise gdb.GdbError("{} must be {} not {}"
+ .format(ma_dead_node.__name__, maple_node_type.get_type().pointer(), node.type))
+ ulong_type = utils.get_ulong_type()
+ parent = node['parent']
+ indirect_ptr = node['parent'].cast(ulong_type) & ~constants.LX_MAPLE_NODE_MASK
+ return indirect_ptr == node
+
+def mte_to_node(enode):
+ ulong_type = utils.get_ulong_type()
+ if enode.type == maple_enode_type.get_type().pointer():
+ indirect_ptr = enode.cast(ulong_type)
+ elif enode.type == ulong_type:
+ indirect_ptr = enode
+ else:
+ raise gdb.GdbError("{} must be {} not {}"
+ .format(mte_to_node.__name__, maple_enode_type.get_type().pointer(), enode.type))
+ indirect_ptr = indirect_ptr & ~constants.LX_MAPLE_NODE_MASK
+ return indirect_ptr.cast(maple_node_type.get_type().pointer())
+
+def mte_dead_node(enode):
+ if enode.type != maple_enode_type.get_type().pointer():
+ raise gdb.GdbError("{} must be {} not {}"
+ .format(mte_dead_node.__name__, maple_enode_type.get_type().pointer(), enode.type))
+ node = mte_to_node(enode)
+ return ma_dead_node(node)
+
+def ma_is_leaf(tp):
+ result = tp < maple_range_64
+ return tp < maple_range_64
+
+def mt_pivots(t):
+ if t == maple_dense:
+ return 0
+ elif t == maple_leaf_64 or t == maple_range_64:
+ return constants.LX_MAPLE_RANGE64_SLOTS - 1
+ elif t == maple_arange_64:
+ return constants.LX_MAPLE_ARANGE64_SLOTS - 1
+
+def ma_pivots(node, t):
+ if node.type != maple_node_type.get_type().pointer():
+ raise gdb.GdbError("{}: must be {} not {}"
+ .format(ma_pivots.__name__, maple_node_type.get_type().pointer(), node.type))
+ if t == maple_arange_64:
+ return node['ma64']['pivot']
+ elif t == maple_leaf_64 or t == maple_range_64:
+ return node['mr64']['pivot']
+ else:
+ return None
+
+def ma_slots(node, tp):
+ if node.type != maple_node_type.get_type().pointer():
+ raise gdb.GdbError("{}: must be {} not {}"
+ .format(ma_slots.__name__, maple_node_type.get_type().pointer(), node.type))
+ if tp == maple_arange_64:
+ return node['ma64']['slot']
+ elif tp == maple_range_64 or tp == maple_leaf_64:
+ return node['mr64']['slot']
+ elif tp == maple_dense:
+ return node['slot']
+ else:
+ return None
+
+def mt_slot(mt, slots, offset):
+ ulong_type = utils.get_ulong_type()
+ return slots[offset].cast(ulong_type)
+
+def mtree_lookup_walk(mas):
+ ulong_type = utils.get_ulong_type()
+ n = mas.node
+
+ while True:
+ node = mte_to_node(n)
+ tp = mte_node_type(n)
+ pivots = ma_pivots(node, tp)
+ end = mt_pivots(tp)
+ offset = 0
+ while True:
+ if pivots[offset] >= mas.index:
+ break
+ if offset >= end:
+ break
+ offset += 1
+
+ slots = ma_slots(node, tp)
+ n = mt_slot(mas.tree, slots, offset)
+ if ma_dead_node(node) is True:
+ mas.reset()
+ return None
+ break
+
+ if ma_is_leaf(tp) is True:
+ break
+
+ return n
+
+def mtree_load(mt, index):
+ ulong_type = utils.get_ulong_type()
+ # MT_STATE(...)
+ mas = Mas(mt, index, index)
+ entry = None
+
+ while True:
+ entry = mas.start()
+ if mas.is_none():
+ return None
+
+ if mas.is_ptr():
+ if index != 0:
+ entry = None
+ return entry
+
+ entry = mtree_lookup_walk(mas)
+ if entry is None and mas.is_start():
+ continue
+ else:
+ break
+
+ if xarray.xa_is_zero(entry):
+ return None
+
+ return entry
diff --git a/scripts/gdb/linux/mm.py b/scripts/gdb/linux/mm.py
index 515730fd4c9d..7571aebbe650 100644
--- a/scripts/gdb/linux/mm.py
+++ b/scripts/gdb/linux/mm.py
@@ -33,7 +33,7 @@ class aarch64_page_ops():
def __init__(self):
self.SUBSECTION_SHIFT = 21
self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT
- self.MODULES_VSIZE = 128 * 1024 * 1024
+ self.MODULES_VSIZE = 2 * 1024 * 1024 * 1024
if constants.LX_CONFIG_ARM64_64K_PAGES:
self.SECTION_SIZE_BITS = 29
@@ -47,8 +47,13 @@ class aarch64_page_ops():
self.VA_BITS = constants.LX_CONFIG_ARM64_VA_BITS
if self.VA_BITS > 48:
- self.VA_BITS_MIN = 48
- self.vabits_actual = gdb.parse_and_eval('vabits_actual')
+ if constants.LX_CONFIG_ARM64_16K_PAGES:
+ self.VA_BITS_MIN = 47
+ else:
+ self.VA_BITS_MIN = 48
+ tcr_el1 = gdb.execute("info registers $TCR_EL1", to_string=True)
+ tcr_el1 = int(tcr_el1.split()[1], 16)
+ self.vabits_actual = 64 - ((tcr_el1 >> 16) & 63)
else:
self.VA_BITS_MIN = self.VA_BITS
self.vabits_actual = self.VA_BITS
@@ -59,9 +64,9 @@ class aarch64_page_ops():
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 = 10
- self.MAX_ORDER_NR_PAGES = 1 << (self.MAX_ORDER - 1)
+ self.MAX_ORDER_NR_PAGES = 1 << (self.MAX_ORDER)
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
@@ -89,10 +94,10 @@ class aarch64_page_ops():
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.VMEMMAP_RANGE = self._PAGE_END(self.VA_BITS_MIN) - self.PAGE_OFFSET
+ self.VMEMMAP_SIZE = (self.VMEMMAP_RANGE >> self.PAGE_SHIFT) * self.struct_page_size
+ self.VMEMMAP_END = (-(1 * 1024 * 1024 * 1024)) & 0xffffffffffffffff
+ self.VMEMMAP_START = self.VMEMMAP_END - self.VMEMMAP_SIZE
self.VMALLOC_START = self.MODULES_END
self.VMALLOC_END = self.VMEMMAP_START - 256 * 1024 * 1024
diff --git a/scripts/gdb/linux/modules.py b/scripts/gdb/linux/modules.py
index 298dfcc25eae..fa15f872ddbe 100644
--- a/scripts/gdb/linux/modules.py
+++ b/scripts/gdb/linux/modules.py
@@ -19,6 +19,9 @@ from linux import cpus, utils, lists, constants
module_type = utils.CachedType("struct module")
+def has_modules():
+ return utils.gdb_eval_or_none("modules") is not None
+
def module_list():
global module_type
modules = utils.gdb_eval_or_none("modules")
diff --git a/scripts/gdb/linux/pgtable.py b/scripts/gdb/linux/pgtable.py
index 30d837f3dfae..09aac2421fb8 100644
--- a/scripts/gdb/linux/pgtable.py
+++ b/scripts/gdb/linux/pgtable.py
@@ -29,11 +29,9 @@ def page_mask(level=1):
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
+ pob = pob_symbol.name
return gdb.parse_and_eval(pob)
diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py
index 43c687e7a69d..65dd1bd12964 100644
--- a/scripts/gdb/linux/proc.py
+++ b/scripts/gdb/linux/proc.py
@@ -18,6 +18,7 @@ from linux import utils
from linux import tasks
from linux import lists
from linux import vfs
+from linux import rbtree
from struct import *
@@ -172,8 +173,7 @@ values of that process namespace"""
gdb.write("{:^18} {:^15} {:>9} {} {} options\n".format(
"mount", "super_block", "devname", "pathname", "fstype"))
- for mnt in lists.list_for_each_entry(namespace['list'],
- mount_ptr_type, "mnt_list"):
+ for mnt in rbtree.rb_inorder_for_each_entry(namespace['mounts'], mount_ptr_type, "mnt_node"):
devname = mnt['mnt_devname'].string()
devname = devname if devname else "none"
diff --git a/scripts/gdb/linux/radixtree.py b/scripts/gdb/linux/radixtree.py
index 074543ac763d..bc2954e45c32 100644
--- a/scripts/gdb/linux/radixtree.py
+++ b/scripts/gdb/linux/radixtree.py
@@ -30,13 +30,16 @@ def entry_to_node(node):
def node_maxindex(node):
return (constants.LX_RADIX_TREE_MAP_SIZE << node['shift']) - 1
-def lookup(root, index):
+def resolve_root(root):
+ if root.type == radix_tree_root_type.get_type():
+ return root
if root.type == radix_tree_root_type.get_type().pointer():
- node = root.dereference()
- elif root.type != radix_tree_root_type.get_type():
- raise gdb.GdbError("must be {} not {}"
- .format(radix_tree_root_type.get_type(), root.type))
+ return root.dereference()
+ raise gdb.GdbError("must be {} not {}"
+ .format(radix_tree_root_type.get_type(), root.type))
+def lookup(root, index):
+ root = resolve_root(root)
node = root['xa_head']
if node == 0:
return None
@@ -71,14 +74,120 @@ def lookup(root, index):
return node
-class LxRadixTree(gdb.Function):
+def descend(parent, index):
+ offset = (index >> int(parent["shift"])) & constants.LX_RADIX_TREE_MAP_MASK
+ return offset, parent["slots"][offset]
+
+def load_root(root):
+ node = root["xa_head"]
+ nodep = node
+
+ if is_internal_node(node):
+ node = entry_to_node(node)
+ maxindex = node_maxindex(node)
+ return int(node["shift"]) + constants.LX_RADIX_TREE_MAP_SHIFT, \
+ nodep, maxindex
+
+ return 0, nodep, 0
+
+class RadixTreeIter:
+ def __init__(self, start):
+ self.index = 0
+ self.next_index = start
+ self.node = None
+
+def xa_mk_internal(v):
+ return (v << 2) | 2
+
+LX_XA_RETRY_ENTRY = xa_mk_internal(256)
+LX_RADIX_TREE_RETRY = LX_XA_RETRY_ENTRY
+
+def next_chunk(root, iter):
+ mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1
+
+ index = iter.next_index
+ if index == 0 and iter.index != 0:
+ return None
+
+ restart = True
+ while restart:
+ restart = False
+
+ _, child, maxindex = load_root(root)
+ if index > maxindex:
+ return None
+ if not child:
+ return None
+
+ if not is_internal_node(child):
+ iter.index = index
+ iter.next_index = (maxindex + 1) & mask
+ iter.node = None
+ return root["xa_head"].address
+
+ while True:
+ node = entry_to_node(child)
+ offset, child = descend(node, index)
+
+ if not child:
+ while True:
+ offset += 1
+ if offset >= constants.LX_RADIX_TREE_MAP_SIZE:
+ break
+ slot = node["slots"][offset]
+ if slot:
+ break
+ index &= ~node_maxindex(node)
+ index = (index + (offset << int(node["shift"]))) & mask
+ if index == 0:
+ return None
+ if offset == constants.LX_RADIX_TREE_MAP_SIZE:
+ restart = True
+ break
+ child = node["slots"][offset]
+
+ if not child:
+ restart = True
+ break
+ if child == LX_XA_RETRY_ENTRY:
+ break
+ if not node["shift"] or not is_internal_node(child):
+ break
+
+ iter.index = (index & ~node_maxindex(node)) | offset
+ iter.next_index = ((index | node_maxindex(node)) + 1) & mask
+ iter.node = node
+
+ return node["slots"][offset].address
+
+def next_slot(slot, iter):
+ mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1
+ for _ in range(iter.next_index - iter.index - 1):
+ slot += 1
+ iter.index = (iter.index + 1) & mask
+ if slot.dereference():
+ return slot
+ return None
+
+def for_each_slot(root, start=0):
+ iter = RadixTreeIter(start)
+ slot = None
+ while True:
+ if not slot:
+ slot = next_chunk(root, iter)
+ if not slot:
+ break
+ yield iter.index, slot
+ slot = next_slot(slot, iter)
+
+class LxRadixTreeLookup(gdb.Function):
""" Lookup and return a node from a RadixTree.
$lx_radix_tree_lookup(root_node [, index]): Return the node at the given index.
If index is omitted, the root node is dereference and returned."""
def __init__(self):
- super(LxRadixTree, self).__init__("lx_radix_tree_lookup")
+ super(LxRadixTreeLookup, self).__init__("lx_radix_tree_lookup")
def invoke(self, root, index=0):
result = lookup(root, index)
@@ -87,4 +196,20 @@ If index is omitted, the root node is dereference and returned."""
return result
+class LxRadixTree(gdb.Command):
+ """Show all values stored in a RadixTree."""
+
+ def __init__(self):
+ super(LxRadixTree, self).__init__("lx-radix-tree", gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+
+ def invoke(self, argument, from_tty):
+ args = gdb.string_to_argv(argument)
+ if len(args) != 1:
+ raise gdb.GdbError("Usage: lx-radix-tree ROOT")
+ root = gdb.parse_and_eval(args[0])
+ for index, slot in for_each_slot(root):
+ gdb.write("[{}] = {}\n".format(index, slot.dereference()))
+
LxRadixTree()
+LxRadixTreeLookup()
diff --git a/scripts/gdb/linux/rbtree.py b/scripts/gdb/linux/rbtree.py
index fe462855eefd..fcbcc5f4153c 100644
--- a/scripts/gdb/linux/rbtree.py
+++ b/scripts/gdb/linux/rbtree.py
@@ -9,6 +9,18 @@ from linux import utils
rb_root_type = utils.CachedType("struct rb_root")
rb_node_type = utils.CachedType("struct rb_node")
+def rb_inorder_for_each(root):
+ def inorder(node):
+ if node:
+ yield from inorder(node['rb_left'])
+ yield node
+ yield from inorder(node['rb_right'])
+
+ yield from inorder(root['rb_node'])
+
+def rb_inorder_for_each_entry(root, gdbtype, member):
+ for node in rb_inorder_for_each(root):
+ yield utils.container_of(node, gdbtype, member)
def rb_first(root):
if root.type == rb_root_type.get_type():
diff --git a/scripts/gdb/linux/stackdepot.py b/scripts/gdb/linux/stackdepot.py
index 0281d9de4b7c..37313a5a51a0 100644
--- a/scripts/gdb/linux/stackdepot.py
+++ b/scripts/gdb/linux/stackdepot.py
@@ -13,6 +13,13 @@ if constants.LX_CONFIG_STACKDEPOT:
stack_record_type = utils.CachedType('struct stack_record')
DEPOT_STACK_ALIGN = 4
+def help():
+ t = """Usage: lx-stack_depot_lookup [Hex handle value]
+ Example:
+ lx-stack_depot_lookup 0x00c80300\n"""
+ gdb.write("Unrecognized command\n")
+ raise gdb.GdbError(t)
+
def stack_depot_fetch(handle):
global DEPOT_STACK_ALIGN
global stack_record_type
@@ -27,14 +34,18 @@ def stack_depot_fetch(handle):
offset = parts['offset'] << DEPOT_STACK_ALIGN
pools_num = gdb.parse_and_eval('pools_num')
- if parts['pool_index'] > pools_num:
+ if handle == 0:
+ raise gdb.GdbError("handle is 0\n")
+
+ pool_index = parts['pool_index_plus_1'] - 1
+ if pool_index >= pools_num:
gdb.write("pool index %d out of bounds (%d) for stack id 0x%08x\n" % (parts['pool_index'], pools_num, handle))
return gdb.Value(0), 0
stack_pools = gdb.parse_and_eval('stack_pools')
try:
- pool = stack_pools[parts['pool_index']]
+ pool = stack_pools[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
@@ -53,3 +64,23 @@ def stack_depot_print(handle):
gdb.execute("x /i 0x%x" % (int(entries[i])))
except Exception as e:
gdb.write("%s\n" % e)
+
+class StackDepotLookup(gdb.Command):
+ """Search backtrace by handle"""
+
+ def __init__(self):
+ if constants.LX_CONFIG_STACKDEPOT:
+ super(StackDepotLookup, self).__init__("lx-stack_depot_lookup", gdb.COMMAND_SUPPORT)
+
+ def invoke(self, args, from_tty):
+ if not constants.LX_CONFIG_STACKDEPOT:
+ raise gdb.GdbError('CONFIG_STACKDEPOT is not set')
+
+ argv = gdb.string_to_argv(args)
+ if len(argv) == 1:
+ handle = int(argv[0], 16)
+ stack_depot_print(gdb.Value(handle).cast(utils.get_uint_type()))
+ else:
+ help()
+
+StackDepotLookup()
diff --git a/scripts/gdb/linux/symbols.py b/scripts/gdb/linux/symbols.py
index e8316beb17a7..d4308b726183 100644
--- a/scripts/gdb/linux/symbols.py
+++ b/scripts/gdb/linux/symbols.py
@@ -11,11 +11,14 @@
# This work is licensed under the terms of the GNU GPL version 2.
#
+import atexit
import gdb
import os
import re
+import struct
-from linux import modules, utils, constants
+from itertools import count
+from linux import bpf, constants, modules, utils
if hasattr(gdb, 'Breakpoint'):
@@ -36,40 +39,103 @@ if hasattr(gdb, 'Breakpoint'):
# Disable pagination while reporting symbol (re-)loading.
# The console input is blocked in this context so that we would
# get stuck waiting for the user to acknowledge paged output.
- show_pagination = gdb.execute("show pagination", to_string=True)
- pagination = show_pagination.endswith("on.\n")
- gdb.execute("set pagination off")
-
- if module_name in cmd.loaded_modules:
- gdb.write("refreshing all symbols to reload module "
- "'{0}'\n".format(module_name))
- cmd.load_all_symbols()
- else:
- cmd.load_module_symbols(module)
-
- # restore pagination state
- gdb.execute("set pagination %s" % ("on" if pagination else "off"))
+ with utils.pagination_off():
+ if module_name in cmd.loaded_modules:
+ gdb.write("refreshing all symbols to reload module "
+ "'{0}'\n".format(module_name))
+ cmd.load_all_symbols()
+ else:
+ cmd.load_module_symbols(module)
return False
+def get_vmcore_s390():
+ with utils.qemu_phy_mem_mode():
+ vmcore_info = 0x0e0c
+ paddr_vmcoreinfo_note = gdb.parse_and_eval("*(unsigned long long *)" +
+ hex(vmcore_info))
+ if paddr_vmcoreinfo_note == 0 or paddr_vmcoreinfo_note & 1:
+ # In the early boot case, extract vm_layout.kaslr_offset from the
+ # vmlinux image in physical memory.
+ if paddr_vmcoreinfo_note == 0:
+ kaslr_offset_phys = 0
+ else:
+ kaslr_offset_phys = paddr_vmcoreinfo_note - 1
+ with utils.pagination_off():
+ gdb.execute("symbol-file {0} -o {1}".format(
+ utils.get_vmlinux(), hex(kaslr_offset_phys)))
+ kaslr_offset = gdb.parse_and_eval("vm_layout.kaslr_offset")
+ return "KERNELOFFSET=" + hex(kaslr_offset)[2:]
+ inferior = gdb.selected_inferior()
+ elf_note = inferior.read_memory(paddr_vmcoreinfo_note, 12)
+ n_namesz, n_descsz, n_type = struct.unpack(">III", elf_note)
+ desc_paddr = paddr_vmcoreinfo_note + len(elf_note) + n_namesz + 1
+ return gdb.parse_and_eval("(char *)" + hex(desc_paddr)).string()
+
+
+def get_kerneloffset():
+ if utils.is_target_arch('s390'):
+ try:
+ vmcore_str = get_vmcore_s390()
+ except gdb.error as e:
+ gdb.write("{}\n".format(e))
+ return None
+ return utils.parse_vmcore(vmcore_str).kerneloffset
+ return None
+
+
+def is_in_s390_decompressor():
+ # DAT is always off in decompressor. Use this as an indicator.
+ # Note that in the kernel, DAT can be off during kexec() or restart.
+ # Accept this imprecision in order to avoid complicating things.
+ # It is unlikely that someone will run lx-symbols at these points.
+ pswm = int(gdb.parse_and_eval("$pswm"))
+ return (pswm & 0x0400000000000000) == 0
+
+
+def skip_decompressor():
+ if utils.is_target_arch("s390"):
+ if is_in_s390_decompressor():
+ # The address of the jump_to_kernel function is statically placed
+ # into svc_old_psw.addr (see ipl_data.c); read it from there. DAT
+ # is off, so we do not need to care about lowcore relocation.
+ svc_old_pswa = 0x148
+ jump_to_kernel = int(gdb.parse_and_eval("*(unsigned long long *)" +
+ hex(svc_old_pswa)))
+ gdb.execute("tbreak *" + hex(jump_to_kernel))
+ gdb.execute("continue")
+ while is_in_s390_decompressor():
+ gdb.execute("stepi")
+
+
class LxSymbols(gdb.Command):
"""(Re-)load symbols of Linux kernel and currently loaded modules.
The kernel (vmlinux) is taken from the current working directly. Modules (.ko)
are scanned recursively, starting in the same directory. Optionally, the module
search path can be extended by a space separated list of paths passed to the
-lx-symbols command."""
+lx-symbols command.
+
+When the -bpf flag is specified, symbols from the currently loaded BPF programs
+are loaded as well."""
module_paths = []
module_files = []
module_files_updated = False
loaded_modules = []
breakpoint = None
+ bpf_prog_monitor = None
+ bpf_ksym_monitor = None
+ bpf_progs = {}
+ # The remove-symbol-file command, even when invoked with -a, requires the
+ # respective object file to exist, so keep them around.
+ bpf_debug_objs = {}
def __init__(self):
super(LxSymbols, self).__init__("lx-symbols", gdb.COMMAND_FILES,
gdb.COMPLETE_FILENAME)
+ atexit.register(self.cleanup_bpf)
def _update_module_files(self):
self.module_files = []
@@ -95,10 +161,14 @@ lx-symbols command."""
except gdb.error:
return str(module_addr)
- attrs = sect_attrs['attrs']
- section_name_to_address = {
- attrs[n]['battr']['attr']['name'].string(): attrs[n]['address']
- for n in range(int(sect_attrs['nsections']))}
+ section_name_to_address = {}
+ for i in count():
+ # this is a NULL terminated array
+ if sect_attrs['grp']['bin_attrs'][i] == 0x0:
+ break
+
+ attr = sect_attrs['grp']['bin_attrs'][i].dereference()
+ section_name_to_address[attr['attr']['name'].string()] = attr['private']
textaddr = section_name_to_address.get(".text", module_addr)
args = []
@@ -138,6 +208,51 @@ lx-symbols command."""
else:
gdb.write("no module object found for '{0}'\n".format(module_name))
+ def add_bpf_prog(self, prog):
+ if prog["jited"]:
+ self.bpf_progs[int(prog["bpf_func"])] = prog
+
+ def remove_bpf_prog(self, prog):
+ self.bpf_progs.pop(int(prog["bpf_func"]), None)
+
+ def add_bpf_ksym(self, ksym):
+ addr = int(ksym["start"])
+ name = bpf.get_ksym_name(ksym)
+ with utils.pagination_off():
+ gdb.write("loading @{addr}: {name}\n".format(
+ addr=hex(addr), name=name))
+ debug_obj = bpf.generate_debug_obj(ksym, self.bpf_progs.get(addr))
+ if debug_obj is None:
+ return
+ try:
+ cmdline = "add-symbol-file {obj} {addr}".format(
+ obj=debug_obj.name, addr=hex(addr))
+ gdb.execute(cmdline, to_string=True)
+ except:
+ debug_obj.close()
+ raise
+ self.bpf_debug_objs[addr] = debug_obj
+
+ def remove_bpf_ksym(self, ksym):
+ addr = int(ksym["start"])
+ debug_obj = self.bpf_debug_objs.pop(addr, None)
+ if debug_obj is None:
+ return
+ try:
+ name = bpf.get_ksym_name(ksym)
+ gdb.write("unloading @{addr}: {name}\n".format(
+ addr=hex(addr), name=name))
+ cmdline = "remove-symbol-file {path}".format(path=debug_obj.name)
+ gdb.execute(cmdline, to_string=True)
+ finally:
+ debug_obj.close()
+
+ def cleanup_bpf(self):
+ self.bpf_progs = {}
+ while len(self.bpf_debug_objs) > 0:
+ self.bpf_debug_objs.popitem()[1].close()
+
+
def load_all_symbols(self):
gdb.write("loading vmlinux\n")
@@ -149,13 +264,14 @@ lx-symbols command."""
saved_states.append({'breakpoint': bp, 'enabled': bp.enabled})
# drop all current symbols and reload vmlinux
- orig_vmlinux = 'vmlinux'
- for obj in gdb.objfiles():
- if (obj.filename.endswith('vmlinux') or
- obj.filename.endswith('vmlinux.debug')):
- orig_vmlinux = obj.filename
+ orig_vmlinux = utils.get_vmlinux()
gdb.execute("symbol-file", to_string=True)
- gdb.execute("symbol-file {0}".format(orig_vmlinux))
+ kerneloffset = get_kerneloffset()
+ if kerneloffset is None:
+ offset_arg = ""
+ else:
+ offset_arg = " -o " + hex(kerneloffset)
+ gdb.execute("symbol-file {0}{1}".format(orig_vmlinux, offset_arg))
self.loaded_modules = []
module_list = modules.module_list()
@@ -164,29 +280,59 @@ lx-symbols command."""
else:
[self.load_module_symbols(module) for module in module_list]
+ self.cleanup_bpf()
+ if self.bpf_prog_monitor is not None:
+ self.bpf_prog_monitor.notify_initial()
+ if self.bpf_ksym_monitor is not None:
+ self.bpf_ksym_monitor.notify_initial()
+
for saved_state in saved_states:
saved_state['breakpoint'].enabled = saved_state['enabled']
def invoke(self, arg, from_tty):
- self.module_paths = [os.path.abspath(os.path.expanduser(p))
- for p in arg.split()]
+ skip_decompressor()
+
+ monitor_bpf = False
+ self.module_paths = []
+ for p in arg.split():
+ if p == "-bpf":
+ monitor_bpf = True
+ else:
+ p.append(os.path.abspath(os.path.expanduser(p)))
self.module_paths.append(os.getcwd())
+ if self.breakpoint is not None:
+ self.breakpoint.delete()
+ self.breakpoint = None
+ if self.bpf_prog_monitor is not None:
+ self.bpf_prog_monitor.delete()
+ self.bpf_prog_monitor = None
+ if self.bpf_ksym_monitor is not None:
+ self.bpf_ksym_monitor.delete()
+ self.bpf_ksym_monitor = None
+
# enforce update
self.module_files = []
self.module_files_updated = False
self.load_all_symbols()
- if hasattr(gdb, 'Breakpoint'):
- if self.breakpoint is not None:
- self.breakpoint.delete()
- self.breakpoint = None
+ if not hasattr(gdb, 'Breakpoint'):
+ gdb.write("Note: symbol update on module and BPF loading not "
+ "supported with this gdb version\n")
+ return
+
+ if modules.has_modules():
self.breakpoint = LoadModuleBreakpoint(
"kernel/module/main.c:do_init_module", self)
- else:
- gdb.write("Note: symbol update on module loading not supported "
- "with this gdb version\n")
+
+ if monitor_bpf:
+ if constants.LX_CONFIG_BPF_SYSCALL:
+ self.bpf_prog_monitor = bpf.ProgMonitor(self.add_bpf_prog,
+ self.remove_bpf_prog)
+ if constants.LX_CONFIG_BPF and constants.LX_CONFIG_BPF_JIT:
+ self.bpf_ksym_monitor = bpf.KsymMonitor(self.add_bpf_ksym,
+ self.remove_bpf_ksym)
LxSymbols()
diff --git a/scripts/gdb/linux/timerlist.py b/scripts/gdb/linux/timerlist.py
index 64bc87191003..ccc24d30de80 100644
--- a/scripts/gdb/linux/timerlist.py
+++ b/scripts/gdb/linux/timerlist.py
@@ -56,8 +56,6 @@ def print_base(base):
text += " .index: {}\n".format(base['index'])
text += " .resolution: {} nsecs\n".format(constants.LX_hrtimer_resolution)
-
- text += " .get_time: {}\n".format(base['get_time'])
if constants.LX_CONFIG_HIGH_RES_TIMERS:
text += " .offset: {} nsecs\n".format(base['offset'])
text += "active timers:\n"
@@ -87,21 +85,22 @@ def print_cpu(hrtimer_bases, cpu, max_clock_bases):
text += "\n"
if constants.LX_CONFIG_TICK_ONESHOT:
- fmts = [(" .{} : {}", 'nohz_mode'),
- (" .{} : {} nsecs", 'last_tick'),
- (" .{} : {}", 'tick_stopped'),
- (" .{} : {}", 'idle_jiffies'),
- (" .{} : {}", 'idle_calls'),
- (" .{} : {}", 'idle_sleeps'),
- (" .{} : {} nsecs", 'idle_entrytime'),
- (" .{} : {} nsecs", 'idle_waketime'),
- (" .{} : {} nsecs", 'idle_exittime'),
- (" .{} : {} nsecs", 'idle_sleeptime'),
- (" .{}: {} nsecs", 'iowait_sleeptime'),
- (" .{} : {}", 'last_jiffies'),
- (" .{} : {}", 'next_timer'),
- (" .{} : {} nsecs", 'idle_expires')]
- text += "\n".join([s.format(f, ts[f]) for s, f in fmts])
+ TS_FLAG_STOPPED = 1 << 1
+ TS_FLAG_NOHZ = 1 << 4
+ text += f" .{'nohz':15s}: {int(bool(ts['flags'] & TS_FLAG_NOHZ))}\n"
+ text += f" .{'last_tick':15s}: {ts['last_tick']}\n"
+ text += f" .{'tick_stopped':15s}: {int(bool(ts['flags'] & TS_FLAG_STOPPED))}\n"
+ text += f" .{'idle_jiffies':15s}: {ts['idle_jiffies']}\n"
+ text += f" .{'idle_calls':15s}: {ts['idle_calls']}\n"
+ text += f" .{'idle_sleeps':15s}: {ts['idle_sleeps']}\n"
+ text += f" .{'idle_entrytime':15s}: {ts['idle_entrytime']} nsecs\n"
+ text += f" .{'idle_waketime':15s}: {ts['idle_waketime']} nsecs\n"
+ text += f" .{'idle_exittime':15s}: {ts['idle_exittime']} nsecs\n"
+ text += f" .{'idle_sleeptime':15s}: {ts['idle_sleeptime']} nsecs\n"
+ text += f" .{'iowait_sleeptime':15s}: {ts['iowait_sleeptime']} nsecs\n"
+ text += f" .{'last_jiffies':15s}: {ts['last_jiffies']}\n"
+ text += f" .{'next_timer':15s}: {ts['next_timer']}\n"
+ text += f" .{'idle_expires':15s}: {ts['idle_expires']} nsecs\n"
text += "\njiffies: {}\n".format(jiffies)
text += "\n"
diff --git a/scripts/gdb/linux/utils.py b/scripts/gdb/linux/utils.py
index 245ab297ea84..e11f6f67961a 100644
--- a/scripts/gdb/linux/utils.py
+++ b/scripts/gdb/linux/utils.py
@@ -11,6 +11,11 @@
# This work is licensed under the terms of the GNU GPL version 2.
#
+import contextlib
+import dataclasses
+import re
+import typing
+
import gdb
@@ -195,7 +200,7 @@ def get_gdbserver_type():
def probe_kgdb():
try:
- thread_info = gdb.execute("info thread 2", to_string=True)
+ thread_info = gdb.execute("info thread 1", to_string=True)
return "shadowCPU" in thread_info
except gdb.error:
return False
@@ -216,3 +221,53 @@ def gdb_eval_or_none(expresssion):
return gdb.parse_and_eval(expresssion)
except gdb.error:
return None
+
+
+@contextlib.contextmanager
+def qemu_phy_mem_mode():
+ connection = gdb.selected_inferior().connection
+ orig = connection.send_packet("qqemu.PhyMemMode")
+ if orig not in b"01":
+ raise gdb.error("Unexpected qemu.PhyMemMode")
+ orig = orig.decode()
+ if connection.send_packet("Qqemu.PhyMemMode:1") != b"OK":
+ raise gdb.error("Failed to set qemu.PhyMemMode")
+ try:
+ yield
+ finally:
+ if connection.send_packet("Qqemu.PhyMemMode:" + orig) != b"OK":
+ raise gdb.error("Failed to restore qemu.PhyMemMode")
+
+
+@dataclasses.dataclass
+class VmCore:
+ kerneloffset: typing.Optional[int]
+
+
+def parse_vmcore(s):
+ match = re.search(r"KERNELOFFSET=([0-9a-f]+)", s)
+ if match is None:
+ kerneloffset = None
+ else:
+ kerneloffset = int(match.group(1), 16)
+ return VmCore(kerneloffset=kerneloffset)
+
+
+def get_vmlinux():
+ vmlinux = 'vmlinux'
+ for obj in gdb.objfiles():
+ if (obj.filename.endswith('vmlinux') or
+ obj.filename.endswith('vmlinux.debug')):
+ vmlinux = obj.filename
+ return vmlinux
+
+
+@contextlib.contextmanager
+def pagination_off():
+ show_pagination = gdb.execute("show pagination", to_string=True)
+ pagination = show_pagination.endswith("on.\n")
+ gdb.execute("set pagination off")
+ try:
+ yield
+ finally:
+ gdb.execute("set pagination %s" % ("on" if pagination else "off"))
diff --git a/scripts/gdb/linux/vfs.py b/scripts/gdb/linux/vfs.py
index c77b9ce75f6d..9e921b645a68 100644
--- a/scripts/gdb/linux/vfs.py
+++ b/scripts/gdb/linux/vfs.py
@@ -22,7 +22,7 @@ def dentry_name(d):
if parent == d or parent == 0:
return ""
p = dentry_name(d['d_parent']) + "/"
- return p + d['d_iname'].string()
+ return p + d['d_name']['name'].string()
class DentryName(gdb.Function):
"""Return string of the full path of a dentry.
diff --git a/scripts/gdb/linux/xarray.py b/scripts/gdb/linux/xarray.py
new file mode 100644
index 000000000000..f4477b5def75
--- /dev/null
+++ b/scripts/gdb/linux/xarray.py
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Xarray helpers
+#
+# Copyright (c) 2025 Broadcom
+#
+# Authors:
+# Florian Fainelli <florian.fainelli@broadcom.com>
+
+import gdb
+
+from linux import utils
+from linux import constants
+
+def xa_is_internal(entry):
+ ulong_type = utils.get_ulong_type()
+ return ((entry.cast(ulong_type) & 3) == 2)
+
+def xa_mk_internal(v):
+ return ((v << 2) | 2)
+
+def xa_is_zero(entry):
+ ulong_type = utils.get_ulong_type()
+ return entry.cast(ulong_type) == xa_mk_internal(257)
+
+def xa_is_node(entry):
+ ulong_type = utils.get_ulong_type()
+ return xa_is_internal(entry) and (entry.cast(ulong_type) > 4096)
diff --git a/scripts/gdb/vmlinux-gdb.py b/scripts/gdb/vmlinux-gdb.py
index fc53cdf286f1..d4eeed4506fd 100644
--- a/scripts/gdb/vmlinux-gdb.py
+++ b/scripts/gdb/vmlinux-gdb.py
@@ -49,3 +49,4 @@ else:
import linux.page_owner
import linux.slab
import linux.vmalloc
+ import linux.kasan