Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Systrack changelog
==================

Unreleased
----------

**Improvements**:

- Add FreeBSD 13+ kernel syscall extraction support (amd64/arm64).


v0.8
----
Expand Down
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
122 changes: 97 additions & 25 deletions src/systrack/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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',
Expand Down Expand Up @@ -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

Expand All @@ -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)

Expand Down Expand Up @@ -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':
Expand Down
8 changes: 5 additions & 3 deletions src/systrack/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 = {}

Expand Down
Loading