xref: /freebsd/sys/tools/gdb/vnet.py (revision ea675a43f09ba569adf1dd17b4f1ced970e48de4)
1*ea675a43SMark Johnston#
2*ea675a43SMark Johnston# Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
3*ea675a43SMark Johnston#
4*ea675a43SMark Johnston# SPDX-License-Identifier: BSD-2-Clause
5*ea675a43SMark Johnston#
6*ea675a43SMark Johnston
7*ea675a43SMark Johnstonimport gdb
8*ea675a43SMark Johnstonimport traceback
9*ea675a43SMark Johnstonfrom freebsd import *
10*ea675a43SMark Johnston
11*ea675a43SMark Johnstonclass vnet(gdb.Function):
12*ea675a43SMark Johnston    """
13*ea675a43SMark Johnston    Register a function to look up VNET variables by name.
14*ea675a43SMark Johnston
15*ea675a43SMark Johnston    To look at the value of a VNET variable V_foo, print $V("foo").  The
16*ea675a43SMark Johnston    currently selected thread's VNET is used by default, but can be optionally
17*ea675a43SMark Johnston    specified as a second parameter, e.g., $V("foo", <vnet>), where <vnet> is a
18*ea675a43SMark Johnston    pointer to a struct vnet (e.g., vnet0 or allprison.tqh_first->pr_vnet) or a
19*ea675a43SMark Johnston    string naming a jail.
20*ea675a43SMark Johnston    """
21*ea675a43SMark Johnston    def __init__(self):
22*ea675a43SMark Johnston        super(vnet, self).__init__("V")
23*ea675a43SMark Johnston
24*ea675a43SMark Johnston    def invoke(self, sym, vnet=None):
25*ea675a43SMark Johnston        sym = sym.string()
26*ea675a43SMark Johnston        if sym.startswith("V_"):
27*ea675a43SMark Johnston            sym = sym[len("V_"):]
28*ea675a43SMark Johnston        if gdb.lookup_symbol("sysctl___kern_features_vimage")[0] is None:
29*ea675a43SMark Johnston            return symval(sym)
30*ea675a43SMark Johnston
31*ea675a43SMark Johnston        # Look up the VNET's base address.
32*ea675a43SMark Johnston        if vnet is None:
33*ea675a43SMark Johnston            vnet = tdfind(gdb.selected_thread().ptid[2])['td_vnet']
34*ea675a43SMark Johnston            if not vnet:
35*ea675a43SMark Johnston                # If curthread->td_vnet == NULL, vnet0 is the current vnet.
36*ea675a43SMark Johnston                vnet = symval("vnet0")
37*ea675a43SMark Johnston        elif vnet.type.is_string_like:
38*ea675a43SMark Johnston            vnet = vnet.string()
39*ea675a43SMark Johnston            for prison in tailq_foreach(symval("allprison"), "pr_list"):
40*ea675a43SMark Johnston                if prison['pr_name'].string() == vnet:
41*ea675a43SMark Johnston                    vnet = prison['pr_vnet']
42*ea675a43SMark Johnston                    break
43*ea675a43SMark Johnston            else:
44*ea675a43SMark Johnston                raise gdb.error(f"No prison named {vnet}")
45*ea675a43SMark Johnston
46*ea675a43SMark Johnston        def uintptr_t(val):
47*ea675a43SMark Johnston            return val.cast(gdb.lookup_type("uintptr_t"))
48*ea675a43SMark Johnston
49*ea675a43SMark Johnston        # Now the tricky part: compute the address of the symbol relative
50*ea675a43SMark Johnston        # to the selected VNET.  In the compiled kernel this is done at
51*ea675a43SMark Johnston        # load time by applying a magic transformation to relocations
52*ea675a43SMark Johnston        # against symbols in the vnet linker set.  Here we have to apply
53*ea675a43SMark Johnston        # the transformation manually.
54*ea675a43SMark Johnston        vnet_data_base = vnet['vnet_data_base']
55*ea675a43SMark Johnston        vnet_entry = symval("vnet_entry_" + sym)
56*ea675a43SMark Johnston        vnet_entry_addr = uintptr_t(vnet_entry.address)
57*ea675a43SMark Johnston
58*ea675a43SMark Johnston        # First, which kernel module does the symbol belong to?
59*ea675a43SMark Johnston        for lf in linker_file_foreach():
60*ea675a43SMark Johnston            # Find the bounds of this linker file's VNET linker set.  The
61*ea675a43SMark Johnston            # struct containing the bounds depends on the type of the linker
62*ea675a43SMark Johnston            # file, and unfortunately both are called elf_file_t.  So we use a
63*ea675a43SMark Johnston            # PC value from the compilation unit (either link_elf.c or
64*ea675a43SMark Johnston            # link_elf_obj.c) to disambiguate.
65*ea675a43SMark Johnston            block = gdb.block_for_pc(lf['ops']['cls']['methods'][0]['func'])
66*ea675a43SMark Johnston            elf_file_t = gdb.lookup_type("elf_file_t", block).target()
67*ea675a43SMark Johnston            ef = lf.cast(elf_file_t)
68*ea675a43SMark Johnston
69*ea675a43SMark Johnston            file_type = lf['ops']['cls']['name'].string()
70*ea675a43SMark Johnston            if file_type == "elf64":
71*ea675a43SMark Johnston                start = uintptr_t(ef['vnet_start'])
72*ea675a43SMark Johnston                if start == 0:
73*ea675a43SMark Johnston                    # This linker file doesn't have a VNET linker set.
74*ea675a43SMark Johnston                    continue
75*ea675a43SMark Johnston                end = uintptr_t(ef['vnet_stop'])
76*ea675a43SMark Johnston                base = uintptr_t(ef['vnet_base'])
77*ea675a43SMark Johnston            elif file_type == "elf64_obj":
78*ea675a43SMark Johnston                for i in range(ef['nprogtab']):
79*ea675a43SMark Johnston                    pe = ef['progtab'][i]
80*ea675a43SMark Johnston                    if pe['name'].string() == "set_vnet":
81*ea675a43SMark Johnston                        start = uintptr_t(pe['origaddr'])
82*ea675a43SMark Johnston                        end = start + uintptr_t(pe['size'])
83*ea675a43SMark Johnston                        base = uintptr_t(pe['addr'])
84*ea675a43SMark Johnston                        break
85*ea675a43SMark Johnston                else:
86*ea675a43SMark Johnston                    # This linker file doesn't have a VNET linker set.
87*ea675a43SMark Johnston                    continue
88*ea675a43SMark Johnston            else:
89*ea675a43SMark Johnston                path = lf['pathname'].string()
90*ea675a43SMark Johnston                raise gdb.error(f"{path} has unexpected linker file type {file_type}")
91*ea675a43SMark Johnston
92*ea675a43SMark Johnston            if vnet_entry_addr >= start and vnet_entry_addr < end:
93*ea675a43SMark Johnston                # The symbol belongs to this linker file, so compute the final
94*ea675a43SMark Johnston                # address.
95*ea675a43SMark Johnston                obj = gdb.Value(vnet_data_base + vnet_entry_addr - start + base)
96*ea675a43SMark Johnston                return obj.cast(vnet_entry.type.pointer()).dereference()
97*ea675a43SMark Johnston
98*ea675a43SMark Johnston
99*ea675a43SMark Johnston# Register with gdb.
100*ea675a43SMark Johnstonvnet()
101