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