| #!/usr/bin/env python3 |
| |
| # Ad-hoc script to print BTF file in a readable format. |
| # Follows the same printing conventions as bpftool with format 'raw'. |
| # Usage: |
| # |
| # ./print_btf.py <btf_file> |
| # |
| # Parameters: |
| # |
| # <btf_file> :: a file name or '-' to read from stdin. |
| # |
| # Intended usage: |
| # |
| # llvm-objcopy --dump-section .BTF=- <input> | ./print_btf.py - |
| # |
| # Kernel documentation contains detailed format description: |
| # https://www.kernel.org/doc/html/latest/bpf/btf.html |
| |
| import struct |
| import ctypes |
| import sys |
| |
| |
| class SafeDict(dict): |
| def __getitem__(self, key): |
| try: |
| return dict.__getitem__(self, key) |
| except KeyError: |
| return f"<BAD_KEY: {key}>" |
| |
| |
| KINDS = SafeDict( |
| { |
| 0: "UNKN", |
| 1: "INT", |
| 2: "PTR", |
| 3: "ARRAY", |
| 4: "STRUCT", |
| 5: "UNION", |
| 6: "ENUM", |
| 7: "FWD", |
| 8: "TYPEDEF", |
| 9: "VOLATILE", |
| 10: "CONST", |
| 11: "RESTRICT", |
| 12: "FUNC", |
| 13: "FUNC_PROTO", |
| 14: "VAR", |
| 15: "DATASEC", |
| 16: "FLOAT", |
| 17: "DECL_TAG", |
| 18: "TYPE_TAG", |
| 19: "ENUM64", |
| } |
| ) |
| |
| INT_ENCODING = SafeDict( |
| {0 << 0: "(none)", 1 << 0: "SIGNED", 1 << 1: "CHAR", 1 << 2: "BOOL"} |
| ) |
| |
| ENUM_ENCODING = SafeDict({0: "UNSIGNED", 1: "SIGNED"}) |
| |
| FUNC_LINKAGE = SafeDict({0: "static", 1: "global", 2: "extern"}) |
| |
| VAR_LINKAGE = SafeDict({0: "static", 1: "global", 2: "extern"}) |
| |
| FWD_KIND = SafeDict( |
| { |
| 0: "struct", |
| 1: "union", |
| } |
| ) |
| |
| for val, name in KINDS.items(): |
| globals()["BTF_KIND_" + name] = val |
| |
| |
| def warn(message): |
| print(message, file=sys.stderr) |
| |
| |
| def print_btf(filename): |
| if filename == "-": |
| buf = sys.stdin.buffer.read() |
| else: |
| with open(filename, "rb") as file: |
| buf = file.read() |
| |
| fmt_cache = {} |
| endian_pfx = ">" # big endian |
| off = 0 |
| |
| def unpack(fmt): |
| nonlocal off, endian_pfx |
| fmt = endian_pfx + fmt |
| if fmt not in fmt_cache: |
| fmt_cache[fmt] = struct.Struct(fmt) |
| st = fmt_cache[fmt] |
| r = st.unpack_from(buf, off) |
| off += st.size |
| return r |
| |
| # Use magic number at the header start to determine endianness |
| (magic,) = unpack("H") |
| if magic == 0xEB9F: |
| endian_pfx = ">" # big endian |
| elif magic == 0x9FEB: |
| endian_pfx = "<" # little endian |
| else: |
| warn(f"Unexpected BTF magic: {magic:02x}") |
| return |
| |
| # Rest of the header |
| version, flags, hdr_len = unpack("BBI") |
| type_off, type_len, str_off, str_len = unpack("IIII") |
| |
| # Offsets in the header are relative to the end of a header |
| type_off += hdr_len |
| str_off += hdr_len |
| off = hdr_len |
| type_end = type_off + type_len |
| |
| def string(rel_off): |
| try: |
| start = str_off + rel_off |
| end = buf.index(b"\0", start) |
| if start == end: |
| return "(anon)" |
| return buf[start:end].decode("utf8") |
| except ValueError as e: |
| warn(f"Can't get string at offset {str_off} + {rel_off}: {e}") |
| return f"<BAD_STRING {rel_off}>" |
| |
| idx = 1 |
| while off < type_end: |
| name_off, info, size = unpack("III") |
| kind = (info >> 24) & 0x1F |
| vlen = info & 0xFFFF |
| kflag = info >> 31 |
| kind_name = KINDS[kind] |
| name = string(name_off) |
| |
| def warn_nonzero(val, name): |
| nonlocal idx |
| if val != 0: |
| warn(f"<{idx}> {name} should be 0 but is {val}") |
| |
| if kind == BTF_KIND_INT: |
| (info,) = unpack("I") |
| encoding = (info & 0x0F000000) >> 24 |
| offset = (info & 0x00FF0000) >> 16 |
| bits = info & 0x000000FF |
| enc_name = INT_ENCODING[encoding] |
| print( |
| f"[{idx}] {kind_name} '{name}' size={size} " |
| f"bits_offset={offset} " |
| f"nr_bits={bits} encoding={enc_name}" |
| ) |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| |
| elif kind in [ |
| BTF_KIND_PTR, |
| BTF_KIND_CONST, |
| BTF_KIND_VOLATILE, |
| BTF_KIND_RESTRICT, |
| ]: |
| print(f"[{idx}] {kind_name} '{name}' type_id={size}") |
| warn_nonzero(name_off, "name_off") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| |
| elif kind == BTF_KIND_ARRAY: |
| warn_nonzero(name_off, "name_off") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| warn_nonzero(size, "size") |
| type, index_type, nelems = unpack("III") |
| print( |
| f"[{idx}] {kind_name} '{name}' type_id={type} " |
| f"index_type_id={index_type} nr_elems={nelems}" |
| ) |
| |
| elif kind in [BTF_KIND_STRUCT, BTF_KIND_UNION]: |
| print(f"[{idx}] {kind_name} '{name}' size={size} vlen={vlen}") |
| if kflag not in [0, 1]: |
| warn(f"<{idx}> kflag should 0 or 1: {kflag}") |
| for _ in range(0, vlen): |
| name_off, type, offset = unpack("III") |
| if kflag == 0: |
| print( |
| f"\t'{string(name_off)}' type_id={type} " |
| f"bits_offset={offset}" |
| ) |
| else: |
| bits_offset = offset & 0xFFFFFF |
| bitfield_size = offset >> 24 |
| print( |
| f"\t'{string(name_off)}' type_id={type} " |
| f"bits_offset={bits_offset} " |
| f"bitfield_size={bitfield_size}" |
| ) |
| |
| elif kind == BTF_KIND_ENUM: |
| encoding = ENUM_ENCODING[kflag] |
| print( |
| f"[{idx}] {kind_name} '{name}' encoding={encoding} " |
| f"size={size} vlen={vlen}" |
| ) |
| for _ in range(0, vlen): |
| (name_off,) = unpack("I") |
| (val,) = unpack("i" if kflag == 1 else "I") |
| print(f"\t'{string(name_off)}' val={val}") |
| |
| elif kind == BTF_KIND_ENUM64: |
| encoding = ENUM_ENCODING[kflag] |
| print( |
| f"[{idx}] {kind_name} '{name}' encoding={encoding} " |
| f"size={size} vlen={vlen}" |
| ) |
| for _ in range(0, vlen): |
| name_off, lo, hi = unpack("III") |
| val = hi << 32 | lo |
| if kflag == 1: |
| val = ctypes.c_long(val).value |
| print(f"\t'{string(name_off)}' val={val}LL") |
| |
| elif kind == BTF_KIND_FWD: |
| print(f"[{idx}] {kind_name} '{name}' fwd_kind={FWD_KIND[kflag]}") |
| warn_nonzero(vlen, "vlen") |
| warn_nonzero(size, "size") |
| |
| elif kind in [BTF_KIND_TYPEDEF, BTF_KIND_TYPE_TAG]: |
| print(f"[{idx}] {kind_name} '{name}' type_id={size}") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(kflag, "vlen") |
| |
| elif kind == BTF_KIND_FUNC: |
| linkage = FUNC_LINKAGE[vlen] |
| print(f"[{idx}] {kind_name} '{name}' type_id={size} " f"linkage={linkage}") |
| warn_nonzero(kflag, "kflag") |
| |
| elif kind == BTF_KIND_FUNC_PROTO: |
| print(f"[{idx}] {kind_name} '{name}' ret_type_id={size} " f"vlen={vlen}") |
| warn_nonzero(name_off, "name_off") |
| warn_nonzero(kflag, "kflag") |
| for _ in range(0, vlen): |
| name_off, type = unpack("II") |
| print(f"\t'{string(name_off)}' type_id={type}") |
| |
| elif kind == BTF_KIND_VAR: |
| (linkage,) = unpack("I") |
| linkage = VAR_LINKAGE[linkage] |
| print(f"[{idx}] {kind_name} '{name}' type_id={size}, " f"linkage={linkage}") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| |
| elif kind == BTF_KIND_DATASEC: |
| print(f"[{idx}] {kind_name} '{name}' size={size} vlen={vlen}") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(size, "size") |
| for _ in range(0, vlen): |
| type, offset, size = unpack("III") |
| print(f"\ttype_id={type} offset={offset} size={size}") |
| |
| elif kind == BTF_KIND_FLOAT: |
| print(f"[{idx}] {kind_name} '{name}' size={size}") |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| |
| elif kind == BTF_KIND_DECL_TAG: |
| (component_idx,) = unpack("i") |
| print( |
| f"[{idx}] {kind_name} '{name}' type_id={size} " |
| + f"component_idx={component_idx}" |
| ) |
| warn_nonzero(kflag, "kflag") |
| warn_nonzero(vlen, "vlen") |
| |
| else: |
| warn( |
| f"<{idx}> Unexpected entry: kind={kind_name} " |
| f"name_off={name_off} " |
| f"vlen={vlen} kflag={kflag} size={size}" |
| ) |
| |
| idx += 1 |
| |
| |
| if __name__ == "__main__": |
| if len(sys.argv) != 2: |
| warn(f"Usage: {sys.argv[0]} <btf_file>") |
| sys.exit(1) |
| print_btf(sys.argv[1]) |