xref: /freebsd/sys/tools/gdb/pcpu.py (revision ea675a43f09ba569adf1dd17b4f1ced970e48de4)
1#
2# Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
3#
4# SPDX-License-Identifier: BSD-2-Clause
5#
6
7import gdb
8from freebsd import *
9
10class pcpu(gdb.Function):
11    """
12    Register a function to lookup PCPU and DPCPU variables by name.
13
14    To look up the value of the PCPU field foo on CPU n, use
15    $PCPU("foo", n).  This works for DPCPU fields too.  If the CPU ID is
16    omitted, and the currently selected thread is on-CPU, that CPU is
17    used, otherwise an error is raised.
18    """
19    def __init__(self):
20        super(pcpu, self).__init__("PCPU")
21
22    def invoke(self, field, cpuid=-1):
23        if cpuid == -1:
24            cpuid = tdfind(gdb.selected_thread().ptid[2])['td_oncpu']
25            if cpuid == -1:
26                raise gdb.error("Currently selected thread is off-CPU")
27            if cpuid < 0 or cpuid > symval("mp_maxid"):
28                raise gdb.error(f"Currently selected on invalid CPU {cpuid}")
29        pcpu = symval("cpuid_to_pcpu")[cpuid]
30
31        # Are we dealing with a PCPU or DPCPU field?
32        field = field.string()
33        for f in gdb.lookup_type("struct pcpu").fields():
34            if f.name == "pc_" + field:
35                return pcpu["pc_" + field]
36
37        def uintptr_t(val):
38            return val.cast(gdb.lookup_type("uintptr_t"))
39
40        # We're dealing with a DPCPU field.  This is handled similarly
41        # to VNET symbols, see vnet.py for comments.
42        pcpu_base = pcpu['pc_dynamic']
43        pcpu_entry = symval("pcpu_entry_" + field)
44        pcpu_entry_addr = uintptr_t(pcpu_entry.address)
45
46        for lf in linker_file_foreach():
47            block = gdb.block_for_pc(lf['ops']['cls']['methods'][0]['func'])
48            elf_file_t = gdb.lookup_type("elf_file_t", block).target()
49            ef = lf.cast(elf_file_t)
50
51            file_type = lf['ops']['cls']['name'].string()
52            if file_type == "elf64":
53                start = uintptr_t(ef['pcpu_start'])
54                if start == 0:
55                    continue
56                end = uintptr_t(ef['pcpu_stop'])
57                base = uintptr_t(ef['pcpu_base'])
58            elif file_type == "elf64_obj":
59                for i in range(ef['nprogtab']):
60                    pe = ef['progtab'][i]
61                    if pe['name'].string() == "set_pcpu":
62                        start = uintptr_t(pe['origaddr'])
63                        end = start + uintptr_t(pe['size'])
64                        base = uintptr_t(pe['addr'])
65                        break
66                else:
67                    continue
68            else:
69                path = lf['pathname'].string()
70                raise gdb.error(f"{path} has unexpected linker file type {file_type}")
71
72            if pcpu_entry_addr >= start and pcpu_entry_addr < end:
73                obj = gdb.Value(pcpu_base + pcpu_entry_addr - start + base)
74                return obj.cast(pcpu_entry.type.pointer()).dereference()
75
76# Register with gdb.
77pcpu()
78