diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e29008..3da99b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Systrack changelog ================== +Unreleased +---------- + +**Improvements**: + +- Add FreeBSD 13+ kernel syscall extraction support (amd64/arm64). + v0.8 ---- diff --git a/README.md b/README.md index b193ba8..69e5dda 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,14 @@ Systrack **See [mebeim/linux-syscalls](https://github.com/mebeim/linux-syscalls) for live syscall tables powered by Systrack**. -Systrack is a tool to analyze Linux kernel images (`vmlinux`) and extract -information about implemented syscalls. Given a `vmlinux` image, Systrack can -extract syscall numbers, names, symbol names, definition locations within kernel -sources, function signatures, and more. - -Systrack can configure and build kernels for all its +Systrack is a tool to analyze kernel images and extract information about +implemented syscalls. For Linux, given a `vmlinux` image, Systrack can extract +syscall numbers, names, symbol names, definition locations within kernel +sources, function signatures, and more. FreeBSD 13+ kernels are also supported +(amd64/arm64, native syscall vector only) by parsing `sysent` and +`syscallnames`. + +Systrack can configure and build Linux kernels for all its [supported architectures](#supported-architectures-and-abis), and works best at analyzing kernels that it has configured and built by itself. @@ -40,16 +42,22 @@ pip install dist/systrack-XXX.whl Usage ----- -Systrack can mainly be used for two purposes: analyzing or building Linux -kernels. See also [Command line help](#command-line-help) (`systrack --help`) +Systrack can mainly be used for two purposes: analyzing kernel images, or +building Linux kernels. See also [Command line help](#command-line-help) +(`systrack --help`) and [Supported architectures and ABIs](#supported-architectures-and-abis) (`systrack --arch help`) below. -- **Analyzing** a kernel image can be done given a `vmlinux` ELF with symbols, - and optionally also a kernel source directory (`--kdir`). Systrack will - extract information about implemented syscalls from the symbol table present - in the given `vmlinux` ELF, and if debugging information is present, it will - also extract file and line number information for syscall definitions. +- **Analyzing** a kernel image can be done given a kernel ELF with symbols + (Linux `vmlinux` or FreeBSD kernel image). Systrack will extract information + about implemented syscalls from the symbol table present in the given ELF, + and if debugging information is present, it will also extract file and line + number information for syscall definitions. + + FreeBSD support is implemented by parsing the kernel's `sysent` table and + `syscallnames` array (FreeBSD 13+, amd64/arm64). Use `--os freebsd` to + override auto-detection when needed. + Supplying a `--kdir` pointing Systrack to the checked-out sources for the right kernel version (the same as the one to analyze) will help refine and/or correct the location of the definitions. @@ -64,10 +72,15 @@ and [Supported architectures and ABIs](#supported-architectures-and-abis) systrack --format html path/to/vmlinux systrack --kdir path/to/linux_git_repo path/to/vmlinux systrack --kdir path/to/linux_git_repo --arch x86-64-ia32 path/to/vmlinux + + # FreeBSD examples (13+) + systrack /boot/kernel/kernel + systrack --format html /boot/kernel/kernel.debug + systrack --os freebsd /boot/kernel/kernel ``` -- **Building** can be done through the `--build` option. You will need to - provide a kernel source directory (`--kdir`) and an architecture/ABI +- **Building** (Linux-only) can be done through the `--build` option. You will + need to provide a kernel source directory (`--kdir`) and an architecture/ABI combination to build for (`--arch`). ```none @@ -189,16 +202,17 @@ $ systrack --help usage: systrack [OPTIONS...] [VMLINUX] -Analyze a Linux kernel image and extract information about implemented syscalls +Analyze a kernel image and extract information about implemented syscalls positional arguments: - VMLINUX path to vmlinux, if not inside KDIR or no KDIR supplied + VMLINUX path to kernel image, if not inside KDIR or no KDIR supplied options: -h, --help show this help message and exit -k KDIR, --kdir KDIR kernel source directory -a ARCH, --arch ARCH kernel architecture/ABI combination; pass "help" for a list (default: autodetect) + --os OS kernel OS: linux or freebsd; if omitted it will be auto-detected from the image -b, --build configure and build kernel and exit -c, --config configure kernel and exit -C, --clean clean kernel sources (make distclean) and exit diff --git a/pyproject.toml b/pyproject.toml index 2b81ad5..e18e1bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = 'README.md' platforms = 'any' requires-python = '>=3.8' dynamic = ['version'] -keywords = ['systrack', 'linux', 'kernel', 'syscall', 'kconfig', 'elf', 'abi'] +keywords = ['systrack', 'linux', 'freebsd', 'bsd', 'kernel', 'syscall', 'kconfig', 'elf', 'abi'] classifiers = [ 'Development Status :: 4 - Beta', 'Environment :: Console', diff --git a/src/systrack/__main__.py b/src/systrack/__main__.py index 2d2e536..9adc0e5 100644 --- a/src/systrack/__main__.py +++ b/src/systrack/__main__.py @@ -8,14 +8,24 @@ from textwrap import TextWrapper from .arch import SUPPORTED_ARCHS, SUPPORTED_ARCHS_HELP +from .elf import ELF +from .freebsd_kernel import FreeBSDKernel, FreeBSDKernelError from .kernel import Kernel, KernelError, KernelArchError, KernelMultiABIError from .kernel import KernelVersionError, KernelWithoutSymbolsError from .log import log_setup, eprint +from .os_detect import detect_kernel_os from .output import output_syscalls from .utils import command_argv_to_string, command_available from .utils import gcc_version, git_checkout, maybe_rel, format_duration +from .utils import readelf_command from .version import VERSION, VERSION_HELP +FREEBSD_ARCHS_HELP = '''Supported FreeBSD architectures: + +amd64 AMD64 64-bit +arm64 ARM64 64-bit +''' + def sigint_handler(_, __): sys.stderr.write('Caught SIGINT, stopping\n') sys.exit(1) @@ -33,17 +43,20 @@ def parse_args() -> argparse.Namespace: ap = argparse.ArgumentParser( prog='systrack', usage='systrack [OPTIONS...] [VMLINUX]', - description='Analyze a Linux kernel image and extract information about implemented syscalls', + description='Analyze a kernel image and extract information about implemented syscalls', formatter_class=argparse.RawTextHelpFormatter ) ap.add_argument('vmlinux', metavar='VMLINUX', nargs='?', - help=wrap_help('path to vmlinux, if not inside KDIR or no KDIR supplied')) + help=wrap_help('path to kernel image, if not inside KDIR or no KDIR supplied')) ap.add_argument('-k', '--kdir', metavar='KDIR', help=wrap_help('kernel source directory')) ap.add_argument('-a', '--arch', metavar='ARCH', help=wrap_help('kernel architecture/ABI combination; pass "help" for a ' 'list (default: autodetect)')) + ap.add_argument('--os', metavar='OS', choices=('linux', 'freebsd'), + type=str.lower, help=wrap_help('kernel OS: linux or freebsd; if omitted ' + 'it will be auto-detected from the image')) ap.add_argument('-b', '--build', action='store_true', help=wrap_help('configure and build kernel and exit')) ap.add_argument('-c', '--config', action='store_true', @@ -127,26 +140,33 @@ def main() -> int: logging.debug('Systrack v%s', VERSION) logging.debug('Command line: systrack %s', command_argv_to_string(sys.argv[1:])) + os_name = args.os arch_name = args.arch if arch_name is not None: arch_name = arch_name.lower() - if arch_name not in SUPPORTED_ARCHS: - if arch_name not in ('help', '?'): - eprint(f'Unsupported architecture/ABI combination: {arch_name}') - eprint('See --arch HELP for a list') - return 1 - - eprint(SUPPORTED_ARCHS_HELP) + if arch_name in ('help', '?'): + if os_name == 'freebsd': + eprint(FREEBSD_ARCHS_HELP) + else: + eprint(SUPPORTED_ARCHS_HELP) return 0 + if os_name == 'freebsd' and (args.clean or args.config or args.build): + eprint('Building/configuring kernels is only supported for Linux images.') + return 1 + + if os_name == 'freebsd' and (args.checkout or args.cross or args.disable_opt): + eprint('Options related to building/checking out kernel sources are only supported for Linux images.') + return 1 + if not args.kdir and not args.vmlinux: - eprint('Need to specify a kernel source direcory and/or path to vmlinux') + eprint('Need to specify a kernel source direcory and/or path to a kernel image') eprint('See --help for more information') return 1 - if not args.kdir and (args.checkout or args.config or args.build): + if not args.kdir and (args.checkout or args.config or args.build or args.clean): eprint('Need to specify a kernel source direcory (--kdir)') return 1 @@ -161,12 +181,21 @@ def main() -> int: outdir = Path(args.out) if args.out else None rdir = Path(args.remap) if args.remap else None - # Checkout before building only if not set to auto - if args.checkout and args.checkout != 'auto': - eprint('Checking out to', args.checkout) - git_checkout(kdir, args.checkout) - if args.clean or args.config or args.build: + if os_name == 'freebsd': + eprint('Building/configuring kernels is only supported for Linux images.') + return 1 + + if arch_name and arch_name not in SUPPORTED_ARCHS: + eprint(f'Unsupported architecture/ABI combination: {arch_name}') + eprint('See --arch HELP for a list') + return 1 + + # Checkout before building only if not set to auto + if args.checkout and args.checkout != 'auto': + eprint('Checking out to', args.checkout) + git_checkout(kdir, args.checkout) + if args.out: out = Path(args.out) @@ -217,25 +246,68 @@ def main() -> int: return 0 - # Auto-checkout to the correct tag is only possible if we already have a - # vmlinux to extract the version from - if args.checkout == 'auto' and not vmlinux: - eprint('Cannot perform auto-checkout without a vmlinux image!') - return 1 - if not vmlinux: vmlinux = kdir / 'vmlinux' if not vmlinux.is_file(): - eprint(f'Unable to find vmlinux at "{vmlinux}".') + eprint(f'Unable to find kernel image at "{vmlinux}".') eprint('Build the kernel or provide a valid path.') return 1 - if not command_available('readelf'): + if readelf_command() is None: eprint('Command "readelf" unavailable, can\'t do much without it!') return 127 - kernel = instantiate_kernel(arch_name, vmlinux, kdir, outdir, rdir) + try: + elf = ELF(vmlinux) + except Exception as e: + eprint(f'Bad kernel ELF: {e}') + return 1 + + detected_os = detect_kernel_os(elf) + + if os_name is not None and detected_os is not None and detected_os != os_name: + eprint(f'Kernel image looks like {detected_os}, but --os {os_name} was specified.') + return 1 + + os_name = os_name or detected_os + if os_name is None: + eprint('Unable to detect kernel OS. Specify --os linux|freebsd.') + return 1 + + if os_name == 'freebsd': + if args.checkout or args.cross or args.disable_opt: + eprint('Options related to building/checking out kernel sources are only supported for Linux images.') + return 1 + + if arch_name and arch_name not in ('amd64', 'arm64'): + eprint(f'Unsupported FreeBSD architecture: {arch_name}') + eprint('See --os freebsd --arch HELP for a list') + return 1 + + try: + kernel = FreeBSDKernel(vmlinux, kdir=kdir, rdir=rdir, arch_name=arch_name) + except FreeBSDKernelError as e: + eprint(str(e)) + return 1 + else: + if arch_name and arch_name not in SUPPORTED_ARCHS: + eprint(f'Unsupported architecture/ABI combination: {arch_name}') + eprint('See --arch HELP for a list') + return 1 + + # Auto-checkout to the correct tag is only possible if we already have a + # vmlinux to extract the version from + if args.checkout == 'auto' and not args.vmlinux: + eprint('Cannot perform auto-checkout without a vmlinux image!') + return 1 + + # Checkout before analyzing only if not set to auto + if args.checkout and args.checkout != 'auto': + eprint('Checking out to', args.checkout) + git_checkout(kdir, args.checkout) + + kernel = instantiate_kernel(arch_name, vmlinux, kdir, outdir, rdir) eprint('Detected kernel version:', kernel.version_str) if args.checkout == 'auto': diff --git a/src/systrack/elf.py b/src/systrack/elf.py index 28862e1..2c8362f 100644 --- a/src/systrack/elf.py +++ b/src/systrack/elf.py @@ -8,7 +8,7 @@ from collections import namedtuple from typing import Union, Dict, Optional -from .utils import ensure_command +from .utils import ensure_command, readelf_command # Only EM_* macros relevant for vmlinux ELFs class E_MACHINE(IntEnum): @@ -89,7 +89,8 @@ def sections(self) -> Dict[str,Section]: # We actually only really care about SHT_PROGBITS or SHT_NOBITS exp = re.compile(r'\s([.\w]+)\s+(PROGBITS|NOBITS)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)') - out = ensure_command(['readelf', '-WS', self.path]) + readelf = readelf_command() or 'readelf' + out = ensure_command([readelf, '-WS', self.path]) secs = {} for match in exp.finditer(out): @@ -117,7 +118,8 @@ def has_debug_info(self) -> bool: def __extract_symbols(self): exp = re.compile(r'\d+:\s+([0-9a-fA-F]+)\s+(\d+)\s+(\w+).+\s+(\S+)$') - out = ensure_command(['readelf', '-Ws', self.path]).splitlines() + readelf = readelf_command() or 'readelf' + out = ensure_command([readelf, '-Ws', self.path]).splitlines() syms = {} funcs = {} diff --git a/src/systrack/freebsd_kernel.py b/src/systrack/freebsd_kernel.py new file mode 100644 index 0000000..91cea3d --- /dev/null +++ b/src/systrack/freebsd_kernel.py @@ -0,0 +1,227 @@ +import logging +import struct + +from pathlib import Path +from typing import List, Optional + +from .elf import ELF, E_MACHINE, Symbol +from .location import addr2line +from .syscall import Syscall +from .utils import addr2line_command, maybe_rel + + +class FreeBSDKernelError(RuntimeError): + pass + + +def freebsd_arch_from_vmlinux(vmlinux: ELF) -> str: + if vmlinux.e_machine == E_MACHINE.EM_X86_64: + return 'amd64' + if vmlinux.e_machine == E_MACHINE.EM_AARCH64: + return 'arm64' + + raise FreeBSDKernelError(f'Unsupported FreeBSD architecture: e_machine={vmlinux.e_machine}') + + +class FreeBSDArchInfo: + __slots__ = ( + 'name', 'bits32', + 'abi', 'compat', 'abi_bits32', + 'syscall_table_name', + 'syscall_num_reg', 'syscall_arg_regs', + '__base_arg_regs' + ) + + def __init__(self, name: str): + assert name in ('amd64', 'arm64'), f'Unexpected FreeBSD arch name: {name!r}' + + self.name = name + self.bits32 = False + self.abi = 'freebsd' + self.compat = False + self.abi_bits32 = False + self.syscall_table_name = 'sysent' + + if name == 'amd64': + self.syscall_num_reg = 'rax' + self.syscall_arg_regs = ['rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'] + else: + self.syscall_num_reg = 'x8' + self.syscall_arg_regs = ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7'] + + self.__base_arg_regs = len(self.syscall_arg_regs) + + def extend_arg_regs_to(self, n: int): + '''Ensure syscall_arg_regs has at least n entries by appending + stack{idx} placeholders. + ''' + while len(self.syscall_arg_regs) < n: + self.syscall_arg_regs.append(f'stack{len(self.syscall_arg_regs) - self.__base_arg_regs}') + + +class FreeBSDKernel: + os = 'freebsd' + __syscalls = None + + def __init__(self, vmlinux: Path, kdir: Optional[Path] = None, + rdir: Optional[Path] = None, arch_name: Optional[str] = None): + self.vmlinux = ELF(vmlinux) + self.kdir = kdir + self.rdir = rdir + + arch = self.__detect_arch() + if arch_name is not None: + arch_name = arch_name.lower() + if arch_name not in ('amd64', 'arm64'): + raise FreeBSDKernelError(f'Unsupported FreeBSD architecture: {arch_name}') + if arch_name != arch: + raise FreeBSDKernelError(f'Selected arch "{arch_name}" does not match vmlinux ({arch})') + + arch = arch_name + + self.arch = FreeBSDArchInfo(arch) + + self.__version_tag, self.__version_source = self.__extract_version() + + def __detect_arch(self) -> str: + return freebsd_arch_from_vmlinux(self.vmlinux) + + def __extract_version(self): + vers_sym = self.vmlinux.symbols.get('version') + if vers_sym is not None: + try: + s = self.vmlinux.vaddr_read_string(vers_sym.vaddr) + except Exception: + s = '' + + if s.startswith('FreeBSD '): + parts = s.split() + if len(parts) >= 2: + return parts[1], 'version' + + return 'unknown', 'elf' + + @property + def version_tag(self) -> str: + return self.__version_tag + + @property + def version_source(self) -> str: + return self.__version_source + + @property + def version_str(self) -> str: + return f'{self.version_tag} (from {self.version_source})' + + @property + def can_extract_location_info(self) -> bool: + return self.vmlinux.has_debug_info + + @property + def syscalls(self) -> List[Syscall]: + if self.__syscalls is None: + self.__syscalls = self.__extract_syscalls() + return self.__syscalls + + def __extract_syscalls(self) -> List[Syscall]: + syms = self.vmlinux.symbols + syscallnames_sym = syms.get('syscallnames') + sysent_sym = syms.get('sysent') + + if syscallnames_sym is None or sysent_sym is None: + raise FreeBSDKernelError('Missing required symbols ("sysent" and/or "syscallnames")') + + ptr_sz = 4 if self.vmlinux.bits32 else 8 + ptr_fmt = '<>'[self.vmlinux.big_endian] + 'QL'[self.vmlinux.bits32] + + if syscallnames_sym.size <= 0 or syscallnames_sym.size % ptr_sz: + raise FreeBSDKernelError('Bad syscallnames size') + + n = syscallnames_sym.size // ptr_sz + names_data = self.vmlinux.vaddr_read(syscallnames_sym.vaddr, syscallnames_sym.size) + name_ptrs = map(lambda u: u[0], struct.iter_unpack(ptr_fmt, names_data)) + names = list(map(lambda p: self.vmlinux.vaddr_read_string(p).strip(), name_ptrs)) + + if sysent_sym.size <= 0 or sysent_sym.size % n: + raise FreeBSDKernelError('Bad sysent size') + + ent_sz = sysent_sym.size // n + if ent_sz < ptr_sz * 2 + 1: + raise FreeBSDKernelError('Unsupported FreeBSD sysent layout') + + text = self.vmlinux.sections.get('.text') + if not text: + raise FreeBSDKernelError('vmlinux has no .text section') + + text_vstart = text.vaddr + text_vend = text_vstart + text.size + + unimpl_vaddrs = set() + for name in ('nosys', 'lkmnosys', 'sys_nosys'): + sym = syms.get(name) + if sym is not None: + unimpl_vaddrs.add(sym.vaddr) + unimpl_vaddrs.add(sym.real_vaddr) + + sysent_data = self.vmlinux.vaddr_read(sysent_sym.vaddr, sysent_sym.size) + + calls = [] + nargs = [] + invalid = 0 + + for i in range(n): + off = i * ent_sz + call = struct.unpack(ptr_fmt, sysent_data[off:off + ptr_sz])[0] + narg = sysent_data[off + ptr_sz * 2] + calls.append(call) + nargs.append(narg) + + if call == 0 or not (text_vstart <= call < text_vend): + invalid += 1 + + if n > 0 and invalid / n > 0.25: + raise FreeBSDKernelError( + 'sysent entries do not point into .text; relocation entries/PIE kernels ' + 'are not supported yet (or sysent layout is unsupported)' + ) + + symbols_by_vaddr = {sym.vaddr: sym for sym in syms.values()} + syscalls = [] + max_narg = 0 + + for i, (name, call, narg) in enumerate(zip(names, calls, nargs)): + max_narg = max(max_narg, narg) + + if call == 0 or call in unimpl_vaddrs: + continue + + sym = symbols_by_vaddr.get(call) + if sym is None: + sym = Symbol(call, call, 0, 'FUNC', f'{call:#x}') + + sig = [f'? arg{j}' for j in range(1, narg + 1)] + syscalls.append(Syscall(i, i, name, name, sym, None, signature=sig)) + + self.arch.extend_arg_regs_to(max_narg) + + if self.can_extract_location_info and addr2line_command() is not None: + addrs = list(map(lambda s: s.symbol.real_vaddr, syscalls)) + locs = list(addr2line(self.vmlinux.path, addrs)) + + if self.kdir: + if self.rdir: + remap = lambda p: self.kdir / maybe_rel(p, self.rdir) if p is not None else None + else: + remap = lambda p: self.kdir / p if p is not None else None + else: + remap = lambda p: p + + for sc, (file, line) in zip(syscalls, locs): + file = remap(file) + sc.file = file + sc.line = line + sc.good_location = file is not None and line is not None + else: + logging.debug('No DWARF/addr2line available, skipping FreeBSD location extraction') + + return syscalls diff --git a/src/systrack/kernel.py b/src/systrack/kernel.py index 0cd1042..f1ee76f 100644 --- a/src/systrack/kernel.py +++ b/src/systrack/kernel.py @@ -2,9 +2,9 @@ import logging import struct import atexit +import os from pathlib import Path from time import monotonic -from os import sched_getaffinity from operator import itemgetter, attrgetter from collections import defaultdict, Counter from typing import Tuple, List, Dict, Iterable, Iterator, Union, Any, Optional @@ -648,7 +648,10 @@ def __edit_config_with_deps(self, options: Dict[str,List[str]]): kconfig_check_with_deps(config_file, self.kdir, options) def make(self, target: str, stdin=None, ensure=True) -> int: - j = max(len(sched_getaffinity(0)) - 1, 1) + try: + j = max(len(os.sched_getaffinity(0)) - 1, 1) + except AttributeError: + j = max((os.cpu_count() or 1) - 1, 1) cmd = ['make', f'-j{j}', f'ARCH={self.arch.name}'] # Generate debug info with relative paths to make our life easier for diff --git a/src/systrack/location.py b/src/systrack/location.py index a1ea223..cedbf65 100644 --- a/src/systrack/location.py +++ b/src/systrack/location.py @@ -9,10 +9,11 @@ from .arch import Arch from .elf import ELF from .syscall import Syscall -from .utils import ensure_command, command_available, maybe_rel +from .utils import ensure_command, command_available, maybe_rel, addr2line_command def addr2line(elf: Path, addrs: Iterable[int]) -> Iterator[Tuple[Optional[Path],Optional[int]]]: - out = ensure_command(['addr2line', '-e', elf, *map(hex, addrs)]) + addr2line = addr2line_command() or 'addr2line' + out = ensure_command([addr2line, '-e', elf, *map(hex, addrs)]) for file, line in map(lambda d: d.split(':'), out.splitlines()): if file == '??': @@ -236,7 +237,7 @@ def adjust_line(file: Path, line: int, sc: Syscall) -> int: def extract_syscall_locations(syscalls: List[Syscall], vmlinux: ELF, arch: Arch, kdir: Optional[Path], rdir: Optional[Path]): - if not command_available('addr2line'): + if addr2line_command() is None: logging.warning('Command "addr2line" unavailable, skipping location info extraction') return diff --git a/src/systrack/os_detect.py b/src/systrack/os_detect.py new file mode 100644 index 0000000..a2767c5 --- /dev/null +++ b/src/systrack/os_detect.py @@ -0,0 +1,20 @@ +from typing import Optional + +from .elf import ELF + + +def detect_kernel_os(elf: ELF) -> Optional[str]: + '''Try to detect the kernel OS from ELF symbols. + + Returns one of: "linux", "freebsd", or None if unknown. + ''' + syms = elf.symbols + + if 'linux_banner' in syms: + return 'linux' + + if 'sysent' in syms and 'syscallnames' in syms: + return 'freebsd' + + return None + diff --git a/src/systrack/output.py b/src/systrack/output.py index 58b4e62..106fe9f 100644 --- a/src/systrack/output.py +++ b/src/systrack/output.py @@ -74,9 +74,11 @@ def output_syscalls_text(syscalls: Iterable[Syscall], spacing: int = 2): sys.stdout.flush() def output_syscalls_json(kernel: Kernel): + os_name = getattr(kernel, 'os', 'linux') data = { 'systrack_version': VERSION, 'kernel': { + 'os': os_name, 'version': kernel.version_tag, 'version_source': kernel.version_source, 'architecture': { @@ -97,6 +99,9 @@ def output_syscalls_json(kernel: Kernel): 'syscalls': kernel.syscalls } + if os_name == 'freebsd': + data['kernel']['syscall_names_symbol'] = 'syscallnames' + dump(data, sys.stdout, cls=SyscallJSONEncoder, sort_keys=True, indent='\t') def output_syscalls_html(kernel: Kernel): @@ -109,9 +114,11 @@ def output_syscalls_html(kernel: Kernel): env = Environment(loader=PackageLoader('systrack'), line_statement_prefix='#', autoescape=True) template = env.get_template('syscall_table.html') - max_args = max(len(s.signature) for s in kernel.syscalls if s.signature is not None) + max_args = max((len(s.signature) for s in kernel.syscalls if s.signature is not None), default=0) + os_name = getattr(kernel, 'os', 'linux') template.stream( + os_name=os_name, kernel_version_tag=kernel.version_tag, arch=kernel.arch.name, bits=32 if kernel.arch.bits32 else 64, diff --git a/src/systrack/templates/syscall_table.html b/src/systrack/templates/syscall_table.html index 7bf0d11..8f3c40b 100644 --- a/src/systrack/templates/syscall_table.html +++ b/src/systrack/templates/syscall_table.html @@ -1,13 +1,13 @@
-