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