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