├── .gitignore ├── LICENSE ├── README.md ├── USER_GUIDE.md ├── asstrace.py ├── examples └── count_lines.py ├── gen ├── riscv64 │ └── syscall_names.csv ├── syscall_num_params.csv └── x86_64 │ └── syscall_names.csv ├── gen_syscall_headers.sh ├── jpg ├── count_lines.png ├── pathsubst.png └── unlink.png ├── signatures.txt ├── syscall_64.tbl ├── syscall_signature_debian.py └── tools ├── find_pattern_offset.py ├── objdump_wrapper.py ├── pid_access_mem.py └── pid_vaddr2line.py /.gitignore: -------------------------------------------------------------------------------- 1 | asstrace 2 | **/*.so 3 | gen/* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mateusz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | `asstrace` stands for **a** **s**tateful **strace**-like - Linux syscall tampering-first `strace`-like tool. 3 | 4 | As opposed to `strace`, `asstrace` alters binary behavior by being "man in the middle" of binary and operating system. If your goal is to understand why some black-box binary is not working as expected, then `strace` with all it's advanced features is the way to go. 5 | 6 | `asstrace` is designed to **provide a convenient way of altering binary behavior and sharing it to other people**. 7 | 8 | It doesn't change the binary itself, but allows for manipulating behavior of system calls that the binary executes. 9 | `asstrace` is designed to work with `Linux`. Currently `x86` and `RISC-V` are supported. 10 | 11 | # Example use cases 12 | 13 | * legacy executable which source code is not available no longer works on modern workstations, as it assumes presence of some special files (sockets, device character special etc.). We can intercept all system calls touching that particular device and provide our own implementation that emulate the device (all the emulation is in user mode). 14 | 15 | * black-box executable does not work because inside a binary there are IP address and port hardcoded, that are no longer accessible as the service moved to a different server. We can intercept network system calls that try to access non-existing address, and change it so that the new address is used. 16 | 17 | * black-box executable does some computation, and as a result it creates a single output file. During computation it creates lots of meaningful temporary files, but unfortunately it deletes them all before output is produced. Using `asstrace` we can intercept all `unlink` system calls and cause them to do nothing. This way no temporary files get removed! [[go to example]](#unlink-example) 18 | 19 | # `unlink` example 20 | 21 | In this example we run `gcc`, but prevent it from deleting temporary files. 22 | 23 | The command used: `echo "int main();" | ./asstrace.py -q -ex 'unlink:nop:msg=prevented {path} from deletion' -- gcc -o a.out -x c -c -` 24 | 25 | ![unlink example](jpg/unlink.png) 26 | 27 | 28 | # `pathsubst` example 29 | 30 | Often in order to get some functionality, we need to hook more than a single syscall. For such purpose `asstrace` defines concept of groups, available by `-g` CLI param. 31 | Here we use `pathsubst`, that hooks `open`, `openat`, `faccessat2` and `statx`. 32 | 33 | The command used is `./asstrace.py -qq -g 'pathsubst:old=zeros,new=abc' -- cat zeros` 34 | 35 | ![pathsubst example](jpg/pathsubst.png) 36 | 37 | 38 | # `count_lines` example 39 | 40 | In this example we manipulate `ls -1` command, so that for each regular file that it prints it will include metadata: number of lines. 41 | 42 | The command used: `./asstrace.py -qq -x examples/count_lines.py ls -1` 43 | 44 | ![count_lines example](jpg/count_lines.png) 45 | 46 | The code of `write` syscall in `count_lines` example is slightly more complicated, thus not suitable for `--ex` as previously. Instead we have a Python file that can use `API` functionality: 47 | 48 | ```py 49 | # examples/count_lines.py 50 | 51 | from pathlib import Path 52 | from asstrace import API 53 | 54 | # defining function called asstrace_X will make a hook for syscall named 'X'. 55 | # hook will be executed before each entry to 'X'. 56 | def asstrace_write(fd, buf, num, *_): 57 | if fd != 1: 58 | # not interesting to use - we care about stdout only. 59 | API.invoke_syscall_anyway() # jump to 'write' with default params 60 | return 61 | path = Path(API.ptrace_read_mem(buf, num)[:-1].decode("ascii")) # strip '\n' and decode from bytes 62 | if not path.is_file(): 63 | # probably a directory - follow default execution path 64 | API.invoke_syscall_anyway() 65 | return 66 | try: 67 | num_lines = len(path.read_text().splitlines()) 68 | except UnicodeDecodeError: 69 | # raw-bytes file - number of lines doesn't make sense for it. 70 | API.invoke_syscall_anyway() 71 | return 72 | 73 | # if we are here, it means that our file is regular, UTF-8, and has 'num_lines' lines. 74 | # print it to stdout instead of default 'buf'. 75 | res_str = f"{path}({num_lines})\n" 76 | print(res_str, end="") 77 | 78 | # 'ls -1' program will think that it has written 'len(res_str)' characters, 79 | # as 'write' syscall returns number of characters really written (see 'man write'). 80 | return len(res_str) 81 | 82 | ``` 83 | 84 | # Few more examples 85 | 86 | ``` 87 | -ex 'open,openat:delay:time=0.5' - invoke each 'open' and 'openat' syscall as usual, but sleep for 0.5s before each invocation 88 | -ex 'unlink:nop' - 'unlink' syscall will not have any effect. value '0' will be returned to userspace. 89 | -ex 'mmap:nop:ret=-1' - 'mmap' syscall will not have any effect. value '-1' will be returned to userspace (fault injection; see 'man mmap'). 90 | -ex 'open:nop:ret=-1' -ex read:detach - fail each open, detach on first read 91 | ``` 92 | 93 | # Verbose mode 94 | 95 | When invoking without `-q` or `-qq` params `asstrace.py` will print all syscalls executed to stderr, in similar manner as `strace` do (but without fancy beautifying): 96 | 97 | ```bash 98 | m.bieganski@test:~/github/asstrace$ ./asstrace.py ls 99 | openat(0xffffff9c, 0x7f4883e8d660, 0x80000, 0x0, 0x80000, 0x7f4883e8d660) = 0x3 100 | read(0x3, 0x7ffd70b6e9b8, 0x340, 0x0, 0x80000, 0x7f4883e8d660) = 0x340 101 | pread64(0x3, 0x7ffd70b6e5c0, 0x310, 0x40, 0x7ffd70b6e5c0, 0x7f4883e8d660) = 0x310 102 | pread64(0x3, 0x7ffd70b6e580, 0x30, 0x350, 0x7ffd70b6e5c0, 0x0) = 0x30 103 | pread64(0x3, 0x7ffd70b6e530, 0x44, 0x380, 0x7ffd70b6e5c0, 0x0) = 0x44 104 | newfstatat(0x3, 0x7f4883ebdee9, 0x7ffd70b6e850, 0x1000, 0x7f4883e8d660, 0x7f4883eca2e0) = 0x0 105 | pread64(0x3, 0x7ffd70b6e490, 0x310, 0x40, 0xc0ff, 0x7f4883e8db08) = 0x310 106 | mmap(0x0, 0x228e50, 0x1, 0x802, 0x3, 0x0) = 0x7f4883c00000 107 | mprotect(0x7f4883c28000, 0x1ee000, 0x0, 0x802, 0x3, 0x0) = 0x0 108 | ... 109 | ``` 110 | 111 | 112 | # User Guide 113 | 114 | See [user guide](./USER_GUIDE.md) for more details. 115 | 116 | # Distribution 117 | 118 | * MIT license 119 | * to make `asstrace` run on your Linux only a single file is needed (`asstrace.py`)* 120 | * no external Python dependencies - no need for `requirements.txt` etc. 121 | * no native code - only CPython interpreter is required 122 | * cross platform - adding a new target is as simple as defining CPU ABI: 123 | 124 | ```py 125 | CPU_Arch.riscv64: CPU_ABI( 126 | user_regs_struct_type=riscv64_user_regs_struct, 127 | syscall_args_registers_ordered=[f"a{i}" for i in range(6)], 128 | syscall_number="a7", 129 | syscall_ret_val="a0", 130 | syscall_ret_addr="ra", 131 | pc="pc", 132 | ) 133 | ``` 134 | 135 | * the `*` gotcha is that it needs additionaly `syscall_names.csv`. It either seeks it locally (will fork if obtained `asstrace` via `git clone`) or downloads directly from GitHub (url is hardcoded in `asstrace.py`). -------------------------------------------------------------------------------- /USER_GUIDE.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | There are serveral functions that names start with `api_` prefix. They are defined by framework, and user hooks can use them. Most important ones: 4 | 5 | * `patch_tracee_syscall_params` - will jump to kernel with modified syscall arguments. 6 | * `ptrace_set_regs_arch_agnostic` / `ptrace_set_regs_arch_agnostic` - access/modify registers, just before jumping to syscall kernel routine 7 | * `ptrace_read_mem` / `ptrace_write_mem` - access/modify memory of tracee program 8 | * `detach` - detach debugger. program will still run, and other debugger can connect (e.g. `gdb`). 9 | * `register_hook` - change syscall's hook in runtime. 10 | # Limitations 11 | 12 | `asstrace` is in early stage of development and there are many features missing. 13 | Below is a list with all entries possible (and often easy) to implement, but still missing: 14 | 15 | * We can trace only single-process programs (missing `strace -f` equivalent) 16 | * We miss any kind of syscall logging formatting (`strace`'s params like `-e`, `-y` and many many more). I'm not sure though if I would accept such change - it's probably out of scope of `asstrace`, as for debugging `strace` should be used - `asstrace` is meant to be binary compatibility layer. -------------------------------------------------------------------------------- /asstrace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | from pathlib import Path 5 | import logging 6 | from copy import copy 7 | import signal 8 | import ctypes 9 | import sys 10 | import os 11 | import time 12 | from types import ModuleType 13 | from typing import Optional, Any, Callable, Type 14 | import platform 15 | from enum import Enum 16 | from dataclasses import dataclass, _MISSING_TYPE 17 | import time 18 | 19 | class Color(Enum): 20 | bold = "\033[1m" 21 | reset_bold = "\033[0m" 22 | 23 | def __str__(self) -> str: 24 | """ 25 | emulates StrEnum for f-strings (that call __str__ under the hood) 26 | """ 27 | return str(self.value) 28 | 29 | 30 | logging.basicConfig(level=logging.INFO) 31 | 32 | # CREDITS (mostly cpython bindings): https://github.com/ancat/gremlin/blob/master/inject_so.py 33 | 34 | class ctypes_Struct_wrapper(ctypes.Structure): 35 | 36 | def __repr__(self) -> str: 37 | names = [x[0] for x in self._fields_] 38 | register_values = [getattr(self, name) for name in names] 39 | 40 | # adjust Array type. 41 | # TODO: make it sane. 42 | for i in range(len(register_values)): 43 | if not isinstance(register_values[i], int): 44 | register_values[i] = 0xdeafbeef 45 | 46 | return (",\n".join([f"{name}={hex(value)}" for name, value in zip(names, register_values) if value != 0])) 47 | 48 | # class user_regs_wrapper(ctypes_Struct_wrapper): 49 | # def set() # TODO 50 | 51 | class x86_64_user_regs_struct(ctypes_Struct_wrapper): 52 | _reg_names_ordered_ = ["r15", "r14", "r13", "r12", "rbp", "rbx", "r11", "r10", "r9", "r8", "rax", "rcx", "rdx", "rsi", "rdi", "orig_rax", "rip", "cs", "eflags", "rsp", "ss", "fs_base", "gs_base", "ds", "es", "fs", "gs"] 53 | _fields_ = [(x, ctypes.c_ulonglong) for x in _reg_names_ordered_] 54 | 55 | class riscv64_user_regs_struct(ctypes_Struct_wrapper): 56 | _reg_names_ordered_ = ["pc", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"] 57 | _fields_ = [(x, ctypes.c_ulonglong) for x in _reg_names_ordered_] 58 | 59 | 60 | class ptrace_syscall_info(ctypes_Struct_wrapper): 61 | _fields_ = [ 62 | ("op", ctypes.c_uint8), 63 | ("pad", ctypes.c_uint8 * 3), 64 | ("arch", ctypes.c_uint32), 65 | ("instruction_pointer", ctypes.c_uint64), 66 | ("stack_pointer", ctypes.c_uint64), 67 | 68 | # actually below is union, but we only use syscall's part. 69 | ("nr", ctypes.c_uint64), 70 | ("args", ctypes.c_uint64 * 6), 71 | ] 72 | 73 | 74 | class iovec(ctypes_Struct_wrapper): 75 | _fields_ = [ 76 | ("iov_base", ctypes.c_void_p), 77 | ("iov_len", ctypes.c_ulong) 78 | ] 79 | 80 | PTRACE_PEEKTEXT = 1 81 | PTRACE_PEEKDATA = 2 82 | PTRACE_POKETEXT = 4 83 | PTRACE_POKEDATA = 5 84 | PTRACE_CONT = 7 85 | PTRACE_SINGLESTEP = 9 86 | PTRACE_GETREGS = 12 87 | PTRACE_SETREGS = 13 88 | PTRACE_ATTACH = 16 89 | PTRACE_DETACH = 17 90 | 91 | PTRACE_SYSCALL = 24 92 | 93 | PTRACE_GETREGSET = 0x4204 94 | PTRACE_SETREGSET = 0x4205 95 | PTRACE_SETOPTIONS = 0x4200 96 | PTRACE_GET_SYSCALL_INFO = 0x420e 97 | 98 | PTRACE_SYSCALL_INFO_NONE = 0 99 | PTRACE_SYSCALL_INFO_ENTRY = 1 100 | PTRACE_SYSCALL_INFO_EXIT = 2 101 | PTRACE_SYSCALL_INFO_SECCOMP = 3 102 | 103 | NT_PRSTATUS = 1 # from 'man ptrace': NT_PRSTATUS (with numerical value 1) 104 | 105 | PTRACE_O_TRACESYSGOOD = 1 106 | PTRACE_O_EXITKILL = 0x00100000 107 | 108 | SIGTRAP = 5 109 | SIGCHLD = 17 110 | SIGSTOP = 19 111 | 112 | class CPU_Arch(Enum): 113 | """ 114 | NOTE: string value must be compatible with convention in gen/ directory. 115 | """ 116 | x86_64 = "x86_64" 117 | riscv64 = "riscv64" 118 | arm = "arm" 119 | aarch64 = "aarch64" 120 | unknown = "unknown" 121 | 122 | 123 | def system_get_cpu_arch() -> CPU_Arch: 124 | machine = platform.machine() 125 | return CPU_Arch(machine) # TODO: handle 'unknown' 126 | 127 | 128 | @dataclass 129 | class CPU_ABI: 130 | user_regs_struct_type : type[ctypes.Structure] 131 | syscall_args_registers_ordered: list[str] 132 | syscall_number: str 133 | syscall_ret_val: str 134 | syscall_ret_addr: str 135 | pc: str 136 | 137 | KNOWN_ABI : dict[CPU_Arch, CPU_ABI] = { 138 | CPU_Arch.x86_64: CPU_ABI( 139 | user_regs_struct_type=x86_64_user_regs_struct, 140 | syscall_args_registers_ordered=["rdi", "rsi", "rdx", "rcx", "r8", "r9"], 141 | syscall_number="orig_rax", 142 | syscall_ret_val="rax", 143 | syscall_ret_addr="rcx", # For x86, address of instruction following SYSCALL is stored in RCX (and RFLAGS in R11). https://www.felixcloutier.com/x86/syscall 144 | pc="rip", 145 | ), 146 | 147 | CPU_Arch.riscv64: CPU_ABI( 148 | user_regs_struct_type=riscv64_user_regs_struct, 149 | syscall_args_registers_ordered=[f"a{i}" for i in range(6)], 150 | syscall_number="a7", 151 | syscall_ret_val="a0", 152 | syscall_ret_addr="ra", 153 | pc="pc", 154 | ) 155 | } 156 | 157 | system_arch = system_get_cpu_arch() 158 | system_abi = KNOWN_ABI[system_arch] 159 | user_regs_struct = system_abi.user_regs_struct_type 160 | syscall_params_getter = lambda user_regs: [getattr(user_regs, x) for x in system_abi.syscall_args_registers_ordered] 161 | 162 | def load_maps(pid) -> list[dict]: 163 | handle = open('/proc/{}/maps'.format(pid), 'r') 164 | output = [] 165 | for line in handle: 166 | line = line.strip() 167 | parts = line.split() 168 | (addr_start, addr_end) = map(lambda x: int(x, 16), parts[0].split('-')) 169 | permissions = parts[1] 170 | offset = int(parts[2], 16) 171 | device_id = parts[3] 172 | inode = parts[4] 173 | map_name = parts[5] if len(parts) > 5 else '' 174 | 175 | mapping = { 176 | 'addr_start': addr_start, 177 | 'addr_end': addr_end, 178 | 'size': addr_end - addr_start, 179 | 'permissions': permissions, 180 | 'offset': offset, 181 | 'device_id': device_id, 182 | 'inode': inode, 183 | 'map_name': Path(map_name) 184 | } 185 | output.append(mapping) 186 | 187 | handle.close() 188 | return output 189 | 190 | def system_find_self_lib(prefix: str) -> Path: 191 | regions = load_maps("self") 192 | matches = [r["map_name"] for r in regions if r["map_name"].name.startswith(prefix)] 193 | assert len(set(matches)) == 1, set(matches) 194 | return matches[0] 195 | 196 | libdl = ctypes.CDLL(system_find_self_lib(prefix="ld-linux")) 197 | libc = ctypes.CDLL(system_find_self_lib(prefix="libc.so")) 198 | 199 | libc.dlopen.restype = ctypes.c_void_p 200 | libc.dlsym.restype = ctypes.c_void_p 201 | libc.ptrace.restype = ctypes.c_uint64 202 | libc.ptrace.argtypes = [ctypes.c_uint64, ctypes.c_uint64, ctypes.c_void_p, ctypes.c_void_p] 203 | ptrace = libc.ptrace 204 | 205 | def _ptrace_get_or_set_regs_arch_agnostic(pid: int, ref: user_regs_struct, do_set: bool): 206 | op = PTRACE_SETREGSET if do_set else PTRACE_GETREGSET 207 | v = iovec(ctypes.cast(ctypes.byref(ref), ctypes.c_void_p), ctypes.sizeof(ref)) 208 | res = ptrace(op, pid, NT_PRSTATUS, ctypes.byref(v)) 209 | assert res == 0, hex(res) 210 | 211 | def ptrace_get_regs_arch_agnostic(pid: int, user_regs: user_regs_struct): 212 | return _ptrace_get_or_set_regs_arch_agnostic(pid=pid, ref=user_regs, do_set=False) 213 | 214 | def ptrace_set_regs_arch_agnostic(pid: int, user_regs: user_regs_struct): 215 | return _ptrace_get_or_set_regs_arch_agnostic(pid=pid, ref=user_regs, do_set=True) 216 | 217 | @dataclass 218 | class SyscallDetailedContext: 219 | """ 220 | Basic context is (up to) 6 syscall arguments (available through hook signature, e.g 'def write(fd, buf, num, *_)'). 221 | Detailed context is available on api request. 222 | """ 223 | syscall_name : str 224 | 225 | # https://stackoverflow.com/a/142566 226 | # the problem: allow importee to change importer's global variable. 227 | import builtins 228 | if __name__ == "__main__": 229 | builtins.user_hook_requested_syscall_invocation = False 230 | builtins.user_requested_debugger_detach = False 231 | builtins.tracee_pid = -1 232 | builtins.user_hooks = dict() 233 | builtins.cur_syscall_context = None 234 | 235 | def _user_api_get_detailed_context(): 236 | res = builtins.cur_syscall_context 237 | assert res is not None 238 | return res 239 | 240 | def _user_api_register_hook(syscall: str, hook: "Cmd"): 241 | hooks = builtins.user_hooks 242 | assert isinstance(hook, Callable) 243 | if syscall in hooks: 244 | logging.warning(f"Overwriting previously defined hook for syscall {syscall}") 245 | hooks[syscall] = hook 246 | 247 | def _user_api_detach_debugger(): 248 | builtins.user_requested_debugger_detach = True 249 | 250 | def _user_api_invoke_syscall_anyway(): 251 | builtins.user_hook_requested_syscall_invocation = True 252 | 253 | def _user_api_get_tracee_pid(): 254 | return builtins.tracee_pid 255 | 256 | def _read_or_write_process_memory(address, size, data, pid: int, do_write: bool): 257 | assert pid > 0 258 | bytes_buffer = ctypes.create_string_buffer(size) 259 | bytes_buffer.raw = data 260 | local_iovec = iovec(ctypes.cast(ctypes.byref(bytes_buffer), ctypes.c_void_p), size) 261 | remote_iovec = iovec(ctypes.c_void_p(address), size) 262 | f = libc.process_vm_writev if do_write else libc.process_vm_readv 263 | bytes_transferred = f( 264 | pid, ctypes.byref(local_iovec), 1, ctypes.byref(remote_iovec), 1, 0 265 | ) 266 | 267 | if bytes_transferred < 0: 268 | name = f.__name__ 269 | raise RuntimeError(f"<{name}> invocation failed! Use 'strace -e {name}' for more details ") 270 | 271 | if do_write: 272 | return bytes_transferred 273 | return bytes_buffer.raw 274 | 275 | def resolve_fd(fd: int, pid: int): 276 | return os.readlink(f"/proc/{pid}/{fd}") 277 | 278 | def _user_api_tracee_resolve_fd(fd: int): 279 | pid = builtins.tracee_pid 280 | return os.readlink(f"/proc/{pid}/fd/{fd}") 281 | 282 | def _user_api_write_tracee_mem(address, data): 283 | if isinstance(data, str): 284 | data = bytes(data, encoding="ascii") 285 | return _read_or_write_process_memory(address=address, size=len(data), data=data, pid=builtins.tracee_pid, do_write=True) 286 | 287 | def _user_api_write_tracee_mem_null_terminated(address, data): 288 | new_data = bytearray(data) 289 | new_data.append(0x0) 290 | return _read_or_write_process_memory(address=address, size=len(new_data), data=new_data, pid=builtins.tracee_pid, do_write=True) 291 | 292 | def _user_api_read_tracee_mem(address, size): 293 | return _read_or_write_process_memory(address=address, size=size, data=bytes(), pid=builtins.tracee_pid, do_write=False) 294 | 295 | def _user_api_read_tracee_mem_null_terminated(address, size): 296 | data = _read_or_write_process_memory(address=address, size=size, data=bytes(), pid=builtins.tracee_pid, do_write=False) 297 | return data[0:data.index(0)] 298 | 299 | 300 | def patch_tracee_syscall_params(*args, **kwargs): 301 | if args: 302 | raise ValueError("positional arguments not allowed!") 303 | 304 | # cursed code that finds out what user meant by e.g. setting fd=3 (to understand that fd stands for first argument of syscall). 305 | import inspect, gc 306 | caller_frame = inspect.stack()[1].frame 307 | code_obj = caller_frame.f_code 308 | referrers = gc.get_referrers(code_obj) 309 | assert len(referrers) == 1, referrers 310 | caller = referrers[0] 311 | 312 | # expect caller to be user hook. 313 | assert caller.__name__.startswith("asstrace"), caller.__name__ 314 | caller_signature = inspect.signature(caller) 315 | 316 | caller_arguments_ordered : list[str] = list(caller_signature.parameters.keys()) 317 | 318 | # create a [int, int_castable] dict, whose keys directly map to registers to be written by 'ptrace'. 319 | try: 320 | final = dict() 321 | for str_k, v in kwargs.items(): 322 | k = caller_arguments_ordered.index(str_k) 323 | final[k] = v 324 | except ValueError: 325 | raise ValueError(f"Possible values are {caller_arguments_ordered}, not '{str_k}'") 326 | 327 | # try int_castable -> int. 328 | try: 329 | for k, v in final.items(): 330 | final[k] = int(v) 331 | except ValueError: 332 | raise ValueError(f"Only int-castable values are allowed, not {type(v)} ({v})") 333 | 334 | 335 | def update_user_regs_inplace(abi: CPU_ABI, patch: dict[int, int], user_regs: user_regs_struct): 336 | for k, v in patch.items(): 337 | assert k < 6 338 | cpu_reg_name = abi.syscall_args_registers_ordered[k] 339 | setattr(user_regs, cpu_reg_name, v) 340 | 341 | update_user_regs_inplace(abi=system_abi, patch=final, user_regs=builtins.tracee_regs) 342 | 343 | 344 | class API: 345 | ptrace_set_regs_arch_agnostic = ptrace_set_regs_arch_agnostic 346 | ptrace_get_regs_arch_agnostic = ptrace_get_regs_arch_agnostic 347 | invoke_syscall_anyway = _user_api_invoke_syscall_anyway 348 | get_tracee_pid = _user_api_get_tracee_pid 349 | ptrace_write_mem = _user_api_write_tracee_mem 350 | ptrace_write_mem_null_terminated = _user_api_write_tracee_mem_null_terminated 351 | ptrace_read_mem = _user_api_read_tracee_mem 352 | ptrace_read_null_terminated = _user_api_read_tracee_mem_null_terminated 353 | tracee_resolve_fd = _user_api_tracee_resolve_fd 354 | patch_tracee_syscall_params = patch_tracee_syscall_params 355 | detach = _user_api_detach_debugger 356 | register_hook = _user_api_register_hook 357 | get_detailed_context = _user_api_get_detailed_context 358 | 359 | 360 | def check_child_alive_or_exit(pid: int): 361 | stat = os.waitpid(pid, 0) 362 | status = stat[1] 363 | 364 | if os.WIFEXITED(status): 365 | if exit_code := os.WEXITSTATUS(status): 366 | print(f"Child process exited with status {exit_code}") 367 | exit(exit_code) 368 | 369 | if os.WIFSTOPPED(status): 370 | sig = os.WSTOPSIG(status) & 127 371 | if sig not in (known_signals := [SIGTRAP, SIGCHLD, SIGSTOP]): 372 | print(f"Tracee received unexpected signal: {signal.strsignal(sig)} ({sig})") 373 | exit(1) 374 | 375 | def prepare_tracee(pid: int, no_fork_but_seize_running_process: bool): 376 | flags = PTRACE_O_TRACESYSGOOD 377 | if not no_fork_but_seize_running_process: 378 | flags |= PTRACE_O_EXITKILL 379 | ptrace(PTRACE_SETOPTIONS, pid, None, flags) 380 | 381 | def get_sideeffectfree_syscall_number(arch_syscalls : dict[int, str]) -> int: 382 | name = "getpid" 383 | return dict((y, x) for x, y in arch_syscalls.items())[name] 384 | 385 | 386 | def run_shell(cmd: str) -> tuple[str, str]: 387 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) 388 | stdout, stderr = process.communicate() 389 | if (ecode := process.returncode): 390 | raise ValueError(f"Command <{cmd}> exited with {ecode}") 391 | return stdout, stderr 392 | 393 | def load_syscalls(arch : Optional[CPU_Arch] = None) -> dict[int, str]: 394 | """ 395 | it will try to get it in various ways, with order as follows: 396 | 1) check if CWD/gen/ARCH/syscall_names.csv exists 397 | 2) download from github.com 398 | """ 399 | 400 | arch = arch or system_get_cpu_arch() 401 | 402 | ##### try locally 403 | csv_to_dict = lambda lines: dict([(int(y), x) for x, y in map(lambda x: x.split(","), lines)]) 404 | 405 | local_csv = Path(__file__).parent / "gen" / arch.value/ "syscall_names.csv" 406 | if local_csv.is_file(): 407 | # success. 408 | return csv_to_dict(lines=local_csv.read_text().splitlines()) 409 | 410 | ##### try Internet download 411 | url = f"https://raw.githubusercontent.com/bieganski/asstrace/main/gen/{arch.value}/syscall_names.csv" 412 | 413 | import requests 414 | response = requests.get(url) 415 | if response.ok: 416 | lines = response.content.decode("ascii").splitlines() 417 | return csv_to_dict(lines) 418 | 419 | raise ValueError(f"Could not find syscall name,number mapping for arch {arch.value}") 420 | 421 | 422 | @dataclass 423 | class Cmd(): 424 | def __post_init__(self): 425 | if self.__class__ == Cmd: 426 | raise TypeError("Cannot instantiate abstract class.") 427 | 428 | 429 | def deserialize_ex(s: str, known_cmds: dict[str, Type[Cmd]]) -> tuple[list[str], Cmd]: 430 | # close[,open,openat]:sleep[:time=10,x=y,...] 431 | try: 432 | _syscalls, cmd, *maybe_params = s.split(":") 433 | syscalls = _syscalls.split(",") 434 | params = deserialize_kwargs("" if not len(maybe_params) else maybe_params[0]) 435 | 436 | cmd_type : Type = known_cmds[cmd] 437 | 438 | for field in cmd_type.__dataclass_fields__.values(): 439 | assert field.type in [str, int, float] 440 | if field.name not in params: 441 | if isinstance(field.default, _MISSING_TYPE): 442 | raise ValueError(f"field '{field.name}' missing during '{cmd}' initialization (and doesn't have default value)") 443 | else: 444 | res = params[field.name] 445 | if field.type in [int, float]: 446 | res = field.type(res) 447 | params[field.name] = res 448 | return syscalls, known_cmds[cmd](**params) 449 | except Exception as e: 450 | print(f"deserialization of string '{s}' failed: {e}", file=sys.stderr) 451 | exit(1) 452 | 453 | @dataclass 454 | class Signature: 455 | _orig: str 456 | 457 | @staticmethod 458 | def from_line(line: str): 459 | assert len(line.splitlines()) == 1 460 | return Signature(_orig=line) 461 | 462 | def fmt(self) -> str: 463 | words = self._orig.split() 464 | 465 | assert words[0] == "asmlinkage" 466 | assert words[2].startswith(("sys_", "compat_")) 467 | 468 | # Apply transformations. 469 | words[2] = words[2][4:] 470 | words = words[1:] 471 | words = [x for x in words if x != "__user"] 472 | return " ".join(words) 473 | 474 | def basename(self, with_sys_prefix=False) -> str: 475 | res = self._orig.split("(")[0].split()[-1] 476 | return res if with_sys_prefix else res.removeprefix("compat_").removeprefix("sys_") 477 | 478 | @property 479 | def num_params(self) -> int: 480 | return self._orig.count(",") + 1 481 | 482 | @property 483 | def param_types_and_varnames(self) -> list[str]: 484 | start, end = self._orig.index("("), self._orig.index(")") 485 | params_str = self._orig[start:(end+1)] 486 | params_lst = params_str.split(",") 487 | assert len(params_lst) == self.num_params 488 | return params_lst 489 | 490 | def get_signature(syscall_name: str) -> Signature: 491 | all_signatures = [Signature.from_line(x) for x in Path("signatures.txt").read_text().splitlines()] 492 | matches = [x for x in all_signatures if x.basename() == syscall_name] 493 | if len(matches) != 1: 494 | raise ValueError(f"Was expecting to find exactly one matching signature, got {len(matches)} instead: {matches}") 495 | return matches[0] 496 | 497 | def guess_const_char_ptr_param(context: SyscallDetailedContext) -> Optional[int]: 498 | """ 499 | consider 'open(path, flags)' and openat(dirfd, path, flags). 500 | that function tries to determine which param stands for path. e.g. it will return 0 for open, 1 for openat. 501 | returns None if could not guess. 502 | """ 503 | signature = get_signature(syscall_name=context.syscall_name) 504 | vars = signature.param_types_and_varnames 505 | matches = [i for i, x in enumerate(vars) if "const char __user *" in x] 506 | if len(matches) != 1: 507 | raise ValueError(f"Could not auto-determine the param of type 'const char*' for syscall {context.syscall_name}! Was expecting exactly one match, got {len(matches)} instead!") 508 | return matches[0] 509 | 510 | def fmt_msg(msg: str, syscall_args: list[int]) -> str: 511 | if "{path}" in msg: 512 | context = API.get_detailed_context() 513 | path_syscall_param_idx = guess_const_char_ptr_param(context=context) 514 | if path_syscall_param_idx is None: 515 | raise RuntimeError(f"Could not auto-guess path idx for syscall {context.syscall_name}") 516 | if len(syscall_args) <= path_syscall_param_idx: 517 | raise RuntimeError(f"Auto-guessed file path at index {path_syscall_param_idx} for syscall {context.syscall_name}, but argument list provided is of length {len(syscall_args)}") 518 | addr = syscall_args[path_syscall_param_idx] 519 | path = API.ptrace_read_null_terminated(addr, 1000).decode("ascii") 520 | return msg.replace("{path}", path) 521 | return msg 522 | 523 | @dataclass 524 | class NopCmd(Cmd): 525 | help = "replace syscall with no-op syscall (getpid)" 526 | ret: int = 0 527 | msg: str = None 528 | def handler(self, *syscall_invocation_params): 529 | if self.msg: 530 | print(fmt_msg(self.msg, syscall_invocation_params)) 531 | return self.ret 532 | 533 | hang_flag : bool = False 534 | hang_handled : bool = False 535 | 536 | @dataclass 537 | class MarkCmd(Cmd): 538 | help = "'hang' command will take effect only after 'mark' hits." 539 | def handler(self, *syscall_invocation_params): 540 | global hang_flag 541 | hang_flag = True 542 | _user_api_invoke_syscall_anyway() 543 | 544 | @dataclass 545 | class HangAfterMarkCmd(Cmd): 546 | help = "'hang' command will take effect only after 'mark' hits." 547 | ret: int = 0 548 | time: float = .0 549 | def handler(self, *syscall_invocation_params): 550 | global hang_flag, hang_handled 551 | if hang_flag: 552 | logging.info("hang!") 553 | time.sleep(self.time) if self.time else input() 554 | hang_handled = True 555 | _user_api_invoke_syscall_anyway() 556 | 557 | @dataclass 558 | class DelayCmd(Cmd): 559 | time: float 560 | help = "invoke original syscall, but delayed" 561 | 562 | def handler(self, *syscall_invocation_params): 563 | time.sleep(self.time) 564 | _user_api_invoke_syscall_anyway() 565 | 566 | @dataclass 567 | class SleepCmd(Cmd): 568 | time: float 569 | help = "replace syscall with sleep" 570 | ret: int = 0 571 | 572 | def handler(self, *syscall_invocation_params): 573 | time.sleep(self.time) 574 | return self.ret 575 | 576 | @dataclass 577 | class ExitCmd(Cmd): 578 | msg : str = "" 579 | help = "exit program at first syscall invocation" 580 | ret: int = 0 581 | 582 | def handler(self, *syscall_invocation_params): 583 | if self.msg: 584 | print(fmt_msg(self.msg, syscall_invocation_params)) 585 | exit(self.ret) 586 | 587 | @dataclass 588 | class DetachCmd(Cmd): 589 | help = "detaches debugger from process" 590 | 591 | def handler(*syscall_invocation_params): 592 | _user_api_detach_debugger() 593 | _user_api_invoke_syscall_anyway() 594 | 595 | @dataclass 596 | class PathSubstCmd(Cmd): 597 | old: str 598 | new: str 599 | help = "invokes syscall but with modified file path (assumes second syscall argument to be a fs path string, suitable for openat,fstatat etc.)" 600 | 601 | def handler(self, dfd, filename, mode, *_): 602 | # empirically checked, that AT_FDCWD is 603 | # 0xffffffffffffff9c on risc-v , 0xffffff9c on x86_64 for some reason. 604 | AT_FDCWD_LOWER_4BYTES = 0xffffff9c 605 | 606 | assert not ((dfd & 0xffff_ffff) ^ AT_FDCWD_LOWER_4BYTES) 607 | 608 | path_extractor = lambda pth: Path(pth).expanduser().absolute() 609 | 610 | tracee_requested_path = API.ptrace_read_null_terminated(filename, 1024).decode("ascii") 611 | 612 | path, old, new = map(path_extractor, [tracee_requested_path, self.old, self.new]) 613 | 614 | if path != old: 615 | # user opens file that is out of scope of our tampering 616 | API.invoke_syscall_anyway() 617 | return 618 | if not args.quietquiet: 619 | print(f"{Color.bold}{old} -> {new}{Color.reset_bold}") 620 | if len(new.name) > len(old.name): 621 | raise ValueError(f"{Color.bold}memory corruption attempted (as we don't implement memory allocation yet), killing.{Color.reset_bold}") 622 | if not new.exists(): 623 | raise ValueError(f"{new} does not exist! TODO: error might be false, if tracee is in different FS namespace.") 624 | API.ptrace_write_mem_null_terminated(filename, bytes(str(new), encoding="ascii")) 625 | API.invoke_syscall_anyway() 626 | 627 | known_expressions = { 628 | "sleep": SleepCmd, 629 | "nop": NopCmd, 630 | "exit": ExitCmd, 631 | "delay": DelayCmd, 632 | "pathsubst": PathSubstCmd, 633 | "detach": DetachCmd, 634 | "mark": MarkCmd, 635 | "hang": HangAfterMarkCmd, 636 | } 637 | 638 | pathsubst_factory = lambda old, new: { 639 | "open": ExitCmd(msg="'open' was not expected, rather 'openat'"), 640 | "openat": PathSubstCmd(old=old, new=new), 641 | "faccessat2": PathSubstCmd(old=old, new=new), 642 | "statx": PathSubstCmd(old=old, new=new), 643 | } 644 | 645 | builtin_groups = { 646 | "pathsubst": pathsubst_factory, 647 | "vmlinux": lambda new: pathsubst_factory(old="/sys/kernel/btf/vmlinux", new=new), 648 | } 649 | 650 | def deserialize_kwargs(s: str) -> dict[str, str]: 651 | assert isinstance(s, str) 652 | return dict() if not s else dict([x.split("=") for x in s.split(",")]) 653 | 654 | def signature_get_arguments(f: Callable) -> list[str]: 655 | import inspect 656 | signature = inspect.signature(f) 657 | return [param.name for param in signature.parameters.values()] 658 | 659 | expr_help = """ 660 | some examples: 661 | -ex 'open,openat:delay:time=0.5' - invoke each 'open' and 'openat' syscall as usual, but sleep for 0.5s before each invocation 662 | -ex 'unlink:nop' - 'unlink' syscall will not have any effect. value '0' will be returned to userspace. 663 | -ex 'mmap:nop:ret=-1' - 'mmap' syscall will not have any effect. value '-1' will be returned to userspace (fault injection; see 'man mmap'). 664 | -ex 'open:nop:ret=-1' -ex read:detach - fail each open, detach on first read 665 | 666 | try 'asstrace.py -ex help' to list all available commands. 667 | """ 668 | 669 | if __name__ == "__main__": 670 | 671 | from argparse import ArgumentParser, RawTextHelpFormatter 672 | parser = ArgumentParser(formatter_class=RawTextHelpFormatter) 673 | parser.add_argument("-ex", "--expressions", nargs="+", help=expr_help) 674 | parser.add_argument("-x", "--batch", type=Path) 675 | parser.add_argument("-g", "--groups", nargs="+") 676 | parser.add_argument("-q", "--quiet", action="store_true") 677 | parser.add_argument("-qq", "--quietquiet", action="store_true") 678 | parser.add_argument("-p", "--pid", type=int) 679 | parser.add_argument("argv", nargs="*") 680 | 681 | # 'user_hooks' is an union of all user-provided commands, either -x, -ex or -b. 682 | user_hooks: dict[str, Cmd] = builtins.user_hooks 683 | 684 | args = parser.parse_args() 685 | 686 | if args.quietquiet: 687 | args.quiet = True 688 | 689 | if args.expressions and "help" in args.expressions: 690 | for name, type in known_expressions.items(): 691 | tokens = [] 692 | tokens.append(f"{name}:") 693 | for i, field in enumerate(type.__dataclass_fields__.values()): 694 | if i: 695 | tokens.append(",") 696 | tokens.append(f"{field.name}:{field.type.__name__}") 697 | if not isinstance(field.default, _MISSING_TYPE): 698 | tokens.append(f"(='{field.default}')") 699 | print(f"{''.join(tokens):<40}", end="") 700 | print(type.help if hasattr(type, "help") else "") 701 | exit(0) 702 | 703 | if args.pid: 704 | if args.argv: 705 | raise RuntimeError("--pid and are mututally exclusive! (either spawn a process or connect to existing one)") 706 | else: 707 | if not args.argv: 708 | raise RuntimeError("either --pid or list are required! (attach to running process or spawn new bash command)") 709 | 710 | if args.batch: 711 | user_hook_abs = args.batch.absolute() 712 | sys.path.append(str(user_hook_abs.parent)) 713 | import_module_str = user_hook_abs.name.removesuffix(".py").replace("/", ".") 714 | exec(f"import {import_module_str} as __user_hook") 715 | user_hook_module : ModuleType = globals()["__user_hook"] # make IDE happy. 716 | user_hook_names : list[str] = [x.replace("asstrace_", "") for x in dir(user_hook_module) if x.startswith("asstrace_")] 717 | if not args.quietquiet: 718 | logging.info(f"User-provided hooks: {user_hook_names}") 719 | for name in user_hook_names: 720 | assert name not in user_hooks 721 | user_hooks[name] = getattr(user_hook_module, f"asstrace_{name}") 722 | del user_hook_abs, import_module_str, user_hook_module, user_hook_names 723 | 724 | if exs := args.expressions: 725 | for e in exs: 726 | syscalls, cmd = deserialize_ex(e, known_cmds=known_expressions) 727 | for x in syscalls: 728 | if x != "any": 729 | user_hooks[x] = cmd 730 | else: 731 | for sysname in load_syscalls().values(): 732 | if not user_hooks.get(sysname): 733 | user_hooks[sysname] = cmd 734 | 735 | if groups := args.groups: 736 | if "help" in groups: 737 | print(", ".join(builtin_groups.keys())) 738 | exit(0) 739 | for gname_and_params in groups: 740 | gname, *params_or_empty = gname_and_params.split(":") 741 | params = deserialize_kwargs("" if not len(params_or_empty) else params_or_empty[0]) 742 | if not gname in builtin_groups: 743 | print(f"unknown group '{gname}'. try 'asstrace.py -g help'", file=sys.stderr) 744 | g = builtin_groups[gname] 745 | assert isinstance(g, Callable) 746 | 747 | expected_args = signature_get_arguments(g) 748 | if (params_names := list(params.keys())) != expected_args: 749 | raise ValueError(f"Params mismatch for group '{gname}'! Was expecting {expected_args}, got {params_names} instead.") 750 | g = g(**params) 751 | 752 | for syscall, cmd in g.items(): 753 | if syscall == "any": 754 | raise NotImplementedError() 755 | assert syscall not in user_hooks 756 | user_hooks[syscall] = cmd 757 | 758 | no_fork_but_seize_running_process = (args.pid is not None) 759 | 760 | if not no_fork_but_seize_running_process: 761 | process = subprocess.Popen(args.argv) 762 | pid = builtins.tracee_pid = process.pid 763 | else: 764 | pid = args.pid 765 | 766 | res = ptrace(PTRACE_ATTACH, pid, None, None) 767 | 768 | check_child_alive_or_exit(pid) 769 | 770 | prepare_tracee(pid=pid, no_fork_but_seize_running_process=no_fork_but_seize_running_process) 771 | 772 | class loop_state: 773 | cur_syscall_overriden_with_sideffectless: bool = False 774 | user_ret_val: int = 0 775 | 776 | state = loop_state() 777 | arch_syscalls : dict[int, str] = load_syscalls() 778 | sideeffectfree_syscall: int = get_sideeffectfree_syscall_number(arch_syscalls=arch_syscalls) 779 | 780 | # validate user_hooks. 781 | for x in user_hooks: 782 | if x not in arch_syscalls.values() and x != "any": 783 | logging.warning(f"Defined user-hook for '{x}', but such syscall seemingly doesn't exist!") 784 | 785 | while True: 786 | if hang_handled: 787 | user_hooks = dict([(k, v) for k, v in user_hooks.items() if not isinstance(v, (HangAfterMarkCmd, MarkCmd))]) 788 | 789 | ptrace(PTRACE_SYSCALL, pid, None, None) 790 | time.sleep(0.001) # TODO: othewise sometimes PTRACE_GETREGSET fails for unknown reason. 791 | 792 | regs = builtins.tracee_regs = user_regs_struct() 793 | check_child_alive_or_exit(pid) 794 | ptrace_get_regs_arch_agnostic(pid=pid, user_regs=regs) 795 | 796 | syscall_info = ptrace_syscall_info() 797 | ptrace(PTRACE_GET_SYSCALL_INFO, pid, ctypes.sizeof(ptrace_syscall_info), ctypes.byref(syscall_info)) 798 | 799 | if syscall_info.op not in [PTRACE_SYSCALL_INFO_EXIT, PTRACE_SYSCALL_INFO_ENTRY, PTRACE_SYSCALL_INFO_NONE]: 800 | print(f"PTRACE_GET_SYSCALL_INFO: unknown/unexpected op: {syscall_info.op}") 801 | continue 802 | 803 | if syscall_info.op == PTRACE_SYSCALL_INFO_ENTRY: 804 | 805 | # Reset some state. 806 | builtins.user_hook_requested_syscall_invocation = False 807 | state.cur_syscall_overriden_with_sideffectless 808 | 809 | syscall_name = arch_syscalls.get(syscall_info.nr, "unknown_syscall") 810 | 811 | if syscall_name in user_hooks: 812 | 813 | # Transfer control to user hook. 814 | if not args.quietquiet: 815 | print(f"{Color.bold}{syscall_name}{Color.reset_bold}", file=sys.stderr) 816 | user_hook_fn = user_hooks[syscall_name] 817 | builtins.cur_syscall_context = SyscallDetailedContext(syscall_name=syscall_name) 818 | hook_ret = user_hook_fn.handler(*syscall_params_getter(regs)) 819 | 820 | skip_real_syscall \ 821 | = state.cur_syscall_overriden_with_sideffectless \ 822 | = not builtins.user_hook_requested_syscall_invocation 823 | 824 | if skip_real_syscall: 825 | # Hook was invoked instead. 826 | setattr(regs, system_abi.syscall_number, sideeffectfree_syscall) 827 | state.user_ret_val = hook_ret 828 | else: 829 | # Need to invoke real syscall as well as already invoked user hook. 830 | # NOTE: the hook might have tampered with registers - need to write it back 831 | pass 832 | 833 | # whatever 'skip_real_syscall' is, update tracee registers. 834 | ptrace_set_regs_arch_agnostic(pid, regs) 835 | else: 836 | # don't intercept a syscall - just log invocation params. 837 | if not args.quiet: 838 | print_end = '\n' if syscall_name.startswith("exit") else '' 839 | print(f"{syscall_name}({', '.join([hex(x) for x in syscall_info.args])}) = ", end=print_end, file=sys.stderr) 840 | 841 | elif syscall_info.op == PTRACE_SYSCALL_INFO_EXIT: 842 | if builtins.user_requested_debugger_detach: 843 | print(f"{Color.bold}asstrace: detaching from {pid} on user request{Color.reset_bold}", flush=True) 844 | res = ptrace(PTRACE_DETACH, pid, None, None) 845 | assert res == 0 846 | exit(0) 847 | if state.cur_syscall_overriden_with_sideffectless: 848 | if not isinstance(state.user_ret_val, int): 849 | raise ValueError("User hook is obliged to return integer value, if real syscall is not executed") 850 | setattr(regs, system_abi.syscall_ret_val, state.user_ret_val) 851 | state.cur_syscall_overriden_with_sideffectless = False 852 | ptrace_set_regs_arch_agnostic(pid, regs) 853 | if not args.quiet: 854 | retval = getattr(regs, system_abi.syscall_ret_val) 855 | print(f"{hex(retval)}", file=sys.stderr) 856 | 857 | 858 | process.communicate() 859 | raise ValueError(process.pid) 860 | -------------------------------------------------------------------------------- /examples/count_lines.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from asstrace import API 3 | 4 | # defining function called asstrace_X will make a hook for syscall named 'X'. 5 | # hook will be executed before each entry to 'X'. 6 | def asstrace_write(fd, buf, num, *_): 7 | if fd != 1: 8 | # not interesting to use - we care about stdout only. 9 | API.invoke_syscall_anyway() # jump to 'write' with default params 10 | return 11 | path = Path(API.ptrace_read_mem(buf, num)[:-1].decode("ascii")) # strip '\n' and decode from bytes 12 | if not path.is_file(): 13 | # probably a directory - follow default execution path 14 | API.invoke_syscall_anyway() 15 | return 16 | try: 17 | num_lines = len(path.read_text().splitlines()) 18 | except UnicodeDecodeError: 19 | # raw-bytes file - number of lines doesn't make sense for it. 20 | API.invoke_syscall_anyway() 21 | return 22 | 23 | # if we are here, it means that our file is regular, UTF-8, and has 'num_lines' lines. 24 | # print it to stdout instead of default 'buf'. 25 | res_str = f"{path}({num_lines})\n" 26 | print(res_str, end="") 27 | 28 | # 'ls -1' program will think that it has written 'len(res_str)' characters, 29 | # as 'write' syscall returns number of characters really written (see 'man write'). 30 | return len(res_str) -------------------------------------------------------------------------------- /gen/riscv64/syscall_names.csv: -------------------------------------------------------------------------------- 1 | accept,202 2 | accept4,242 3 | acct,89 4 | add_key,217 5 | adjtimex,171 6 | bind,200 7 | bpf,280 8 | brk,214 9 | cachestat,451 10 | capget,90 11 | capset,91 12 | chdir,49 13 | chroot,51 14 | clock_adjtime,266 15 | clock_getres,114 16 | clock_gettime,113 17 | clock_nanosleep,115 18 | clock_settime,112 19 | clone,220 20 | clone3,435 21 | close,57 22 | close_range,436 23 | connect,203 24 | copy_file_range,285 25 | delete_module,106 26 | dup,23 27 | dup3,24 28 | epoll_create1,20 29 | epoll_ctl,21 30 | epoll_pwait,22 31 | epoll_pwait2,441 32 | eventfd2,19 33 | execve,221 34 | execveat,281 35 | exit,93 36 | exit_group,94 37 | faccessat,48 38 | faccessat2,439 39 | fadvise64,223 40 | fallocate,47 41 | fanotify_init,262 42 | fanotify_mark,263 43 | fchdir,50 44 | fchmod,52 45 | fchmodat,53 46 | fchmodat2,452 47 | fchown,55 48 | fchownat,54 49 | fcntl,25 50 | fdatasync,83 51 | fgetxattr,10 52 | finit_module,273 53 | flistxattr,13 54 | flock,32 55 | fremovexattr,16 56 | fsconfig,431 57 | fsetxattr,7 58 | fsmount,432 59 | fsopen,430 60 | fspick,433 61 | fstat,80 62 | fstatfs,44 63 | fsync,82 64 | ftruncate,46 65 | futex,98 66 | futex_requeue,456 67 | futex_wait,455 68 | futex_waitv,449 69 | futex_wake,454 70 | get_mempolicy,236 71 | get_robust_list,100 72 | getcpu,168 73 | getcwd,17 74 | getdents64,61 75 | getegid,177 76 | geteuid,175 77 | getgid,176 78 | getgroups,158 79 | getitimer,102 80 | getpeername,205 81 | getpgid,155 82 | getpid,172 83 | getppid,173 84 | getpriority,141 85 | getrandom,278 86 | getresgid,150 87 | getresuid,148 88 | getrlimit,163 89 | getrusage,165 90 | getsid,156 91 | getsockname,204 92 | getsockopt,209 93 | gettid,178 94 | gettimeofday,169 95 | getuid,174 96 | getxattr,8 97 | init_module,105 98 | inotify_add_watch,27 99 | inotify_init1,26 100 | inotify_rm_watch,28 101 | io_cancel,3 102 | io_destroy,1 103 | io_getevents,4 104 | io_pgetevents,292 105 | io_setup,0 106 | io_submit,2 107 | io_uring_enter,426 108 | io_uring_register,427 109 | io_uring_setup,425 110 | ioctl,29 111 | ioprio_get,31 112 | ioprio_set,30 113 | kcmp,272 114 | kexec_file_load,294 115 | kexec_load,104 116 | keyctl,219 117 | kill,129 118 | landlock_add_rule,445 119 | landlock_create_ruleset,444 120 | landlock_restrict_self,446 121 | lgetxattr,9 122 | linkat,37 123 | listen,201 124 | listmount,458 125 | listxattr,11 126 | llistxattr,12 127 | lookup_dcookie,18 128 | lremovexattr,15 129 | lseek,62 130 | lsetxattr,6 131 | lsm_get_self_attr,459 132 | lsm_list_modules,461 133 | lsm_set_self_attr,460 134 | madvise,233 135 | map_shadow_stack,453 136 | mbind,235 137 | membarrier,283 138 | memfd_create,279 139 | memfd_secret,447 140 | migrate_pages,238 141 | mincore,232 142 | mkdirat,34 143 | mknodat,33 144 | mlock,228 145 | mlock2,284 146 | mlockall,230 147 | mmap,222 148 | mount,40 149 | mount_setattr,442 150 | move_mount,429 151 | move_pages,239 152 | mprotect,226 153 | mq_getsetattr,185 154 | mq_notify,184 155 | mq_open,180 156 | mq_timedreceive,183 157 | mq_timedsend,182 158 | mq_unlink,181 159 | mremap,216 160 | mseal,462 161 | msgctl,187 162 | msgget,186 163 | msgrcv,188 164 | msgsnd,189 165 | msync,227 166 | munlock,229 167 | munlockall,231 168 | munmap,215 169 | name_to_handle_at,264 170 | nanosleep,101 171 | newfstatat,79 172 | open_by_handle_at,265 173 | open_tree,428 174 | openat,56 175 | openat2,437 176 | perf_event_open,241 177 | personality,92 178 | pidfd_getfd,438 179 | pidfd_open,434 180 | pidfd_send_signal,424 181 | pipe2,59 182 | pivot_root,41 183 | pkey_alloc,289 184 | pkey_free,290 185 | pkey_mprotect,288 186 | ppoll,73 187 | prctl,167 188 | pread64,67 189 | preadv,69 190 | preadv2,286 191 | prlimit64,261 192 | process_madvise,440 193 | process_mrelease,448 194 | process_vm_readv,270 195 | process_vm_writev,271 196 | pselect6,72 197 | ptrace,117 198 | pwrite64,68 199 | pwritev,70 200 | pwritev2,287 201 | quotactl,60 202 | quotactl_fd,443 203 | read,63 204 | readahead,213 205 | readlinkat,78 206 | readv,65 207 | reboot,142 208 | recvfrom,207 209 | recvmmsg,243 210 | recvmsg,212 211 | remap_file_pages,234 212 | removexattr,14 213 | renameat2,276 214 | request_key,218 215 | restart_syscall,128 216 | riscv_flush_icache,259 217 | riscv_hwprobe,258 218 | rseq,293 219 | rt_sigaction,134 220 | rt_sigpending,136 221 | rt_sigprocmask,135 222 | rt_sigqueueinfo,138 223 | rt_sigreturn,139 224 | rt_sigsuspend,133 225 | rt_sigtimedwait,137 226 | rt_tgsigqueueinfo,240 227 | sched_get_priority_max,125 228 | sched_get_priority_min,126 229 | sched_getaffinity,123 230 | sched_getattr,275 231 | sched_getparam,121 232 | sched_getscheduler,120 233 | sched_rr_get_interval,127 234 | sched_setaffinity,122 235 | sched_setattr,274 236 | sched_setparam,118 237 | sched_setscheduler,119 238 | sched_yield,124 239 | seccomp,277 240 | semctl,191 241 | semget,190 242 | semop,193 243 | semtimedop,192 244 | sendfile,71 245 | sendmmsg,269 246 | sendmsg,211 247 | sendto,206 248 | set_mempolicy,237 249 | set_mempolicy_home_node,450 250 | set_robust_list,99 251 | set_tid_address,96 252 | setdomainname,162 253 | setfsgid,152 254 | setfsuid,151 255 | setgid,144 256 | setgroups,159 257 | sethostname,161 258 | setitimer,103 259 | setns,268 260 | setpgid,154 261 | setpriority,140 262 | setregid,143 263 | setresgid,149 264 | setresuid,147 265 | setreuid,145 266 | setrlimit,164 267 | setsid,157 268 | setsockopt,208 269 | settimeofday,170 270 | setuid,146 271 | setxattr,5 272 | shmat,196 273 | shmctl,195 274 | shmdt,197 275 | shmget,194 276 | shutdown,210 277 | sigaltstack,132 278 | signalfd4,74 279 | socket,198 280 | socketpair,199 281 | splice,76 282 | statfs,43 283 | statmount,457 284 | statx,291 285 | swapoff,225 286 | swapon,224 287 | symlinkat,36 288 | sync,81 289 | sync_file_range,84 290 | syncfs,267 291 | sysinfo,179 292 | syslog,116 293 | tee,77 294 | tgkill,131 295 | timer_create,107 296 | timer_delete,111 297 | timer_getoverrun,109 298 | timer_gettime,108 299 | timer_settime,110 300 | timerfd_create,85 301 | timerfd_gettime,87 302 | timerfd_settime,86 303 | times,153 304 | tkill,130 305 | truncate,45 306 | umask,166 307 | umount2,39 308 | uname,160 309 | unlinkat,35 310 | unshare,97 311 | userfaultfd,282 312 | utimensat,88 313 | vhangup,58 314 | vmsplice,75 315 | wait4,260 316 | waitid,95 317 | write,64 318 | writev,66 319 | -------------------------------------------------------------------------------- /gen/syscall_num_params.csv: -------------------------------------------------------------------------------- 1 | read,3 2 | write,3 3 | open,3 4 | close,1 5 | stat,2 6 | fstat,2 7 | lstat,2 8 | poll,3 9 | lseek,3 10 | mmap,6 11 | mprotect,3 12 | munmap,2 13 | brk,1 14 | rt_sigaction,4 15 | rt_sigprocmask,4 16 | rt_sigreturn,2 17 | ioctl,3 18 | pread64,4 19 | pwrite64,4 20 | readv,3 21 | writev,3 22 | access,2 23 | pipe,1 24 | select,5 25 | sched_yield,1 26 | mremap,5 27 | msync,3 28 | mincore,3 29 | madvise,3 30 | shmget,3 31 | shmat,3 32 | shmctl,3 33 | dup,1 34 | dup2,2 35 | pause,1 36 | nanosleep,2 37 | getitimer,2 38 | alarm,1 39 | setitimer,3 40 | getpid,1 41 | sendfile,4 42 | socket,3 43 | connect,3 44 | accept,3 45 | sendto,6 46 | recvfrom,6 47 | sendmsg,3 48 | recvmsg,3 49 | shutdown,2 50 | bind,3 51 | listen,2 52 | getsockname,3 53 | getpeername,3 54 | socketpair,4 55 | setsockopt,5 56 | getsockopt,5 57 | clone,2 58 | fork,1 59 | vfork,1 60 | execve,3 61 | exit,1 62 | wait4,4 63 | kill,2 64 | uname,1 65 | semget,3 66 | semop,3 67 | semctl,4 68 | shmdt,1 69 | msgget,2 70 | msgsnd,4 71 | msgrcv,5 72 | msgctl,3 73 | fcntl,3 74 | flock,2 75 | fsync,1 76 | fdatasync,1 77 | truncate,2 78 | ftruncate,2 79 | getdents,3 80 | getcwd,2 81 | chdir,1 82 | fchdir,1 83 | rename,2 84 | mkdir,2 85 | rmdir,1 86 | creat,2 87 | link,2 88 | unlink,1 89 | symlink,2 90 | readlink,3 91 | chmod,2 92 | fchmod,2 93 | chown,3 94 | fchown,3 95 | lchown,3 96 | umask,1 97 | gettimeofday,2 98 | getrlimit,2 99 | getrusage,2 100 | sysinfo,1 101 | times,1 102 | ptrace,4 103 | getuid,1 104 | syslog,3 105 | getgid,1 106 | setuid,1 107 | setgid,1 108 | geteuid,1 109 | getegid,1 110 | setpgid,2 111 | getppid,1 112 | getpgrp,1 113 | setsid,1 114 | setreuid,2 115 | setregid,2 116 | getgroups,2 117 | setgroups,2 118 | setresuid,3 119 | getresuid,3 120 | setresgid,3 121 | getresgid,3 122 | getpgid,1 123 | setfsuid,1 124 | setfsgid,1 125 | getsid,1 126 | capget,2 127 | capset,2 128 | rt_sigpending,2 129 | rt_sigtimedwait,4 130 | rt_sigqueueinfo,3 131 | rt_sigsuspend,2 132 | sigaltstack,2 133 | utime,2 134 | mknod,3 135 | personality,1 136 | ustat,2 137 | statfs,2 138 | fstatfs,2 139 | sysfs,3 140 | getpriority,2 141 | setpriority,3 142 | sched_setparam,2 143 | sched_getparam,2 144 | sched_setscheduler,3 145 | sched_getscheduler,1 146 | sched_get_priority_max,1 147 | sched_get_priority_min,1 148 | sched_rr_get_interval,2 149 | mlock,2 150 | munlock,2 151 | mlockall,1 152 | munlockall,1 153 | vhangup,1 154 | modify_ldt,2 155 | pivot_root,2 156 | _sysctl,1 157 | prctl,5 158 | arch_prctl,2 159 | adjtimex,1 160 | setrlimit,2 161 | chroot,1 162 | sync,1 163 | acct,1 164 | settimeofday,2 165 | mount,5 166 | umount2,2 167 | swapon,2 168 | swapoff,1 169 | reboot,4 170 | sethostname,2 171 | setdomainname,2 172 | iopl,2 173 | ioperm,3 174 | init_module,3 175 | delete_module,2 176 | quotactl,4 177 | gettid,1 178 | readahead,3 179 | setxattr,5 180 | lsetxattr,5 181 | fsetxattr,5 182 | getxattr,4 183 | lgetxattr,4 184 | fgetxattr,4 185 | listxattr,3 186 | llistxattr,3 187 | flistxattr,3 188 | removexattr,2 189 | lremovexattr,2 190 | fremovexattr,2 191 | tkill,2 192 | time,1 193 | futex,6 194 | sched_setaffinity,3 195 | sched_getaffinity,3 196 | io_setup,2 197 | io_destroy,1 198 | io_getevents,5 199 | io_submit,3 200 | io_cancel,3 201 | epoll_create,1 202 | remap_file_pages,5 203 | getdents64,3 204 | set_tid_address,1 205 | restart_syscall,1 206 | semtimedop,4 207 | fadvise64,4 208 | timer_create,3 209 | timer_settime,4 210 | timer_gettime,2 211 | timer_getoverrun,1 212 | timer_delete,1 213 | clock_settime,2 214 | clock_gettime,2 215 | clock_getres,2 216 | clock_nanosleep,4 217 | exit_group,1 218 | epoll_wait,4 219 | epoll_ctl,4 220 | tgkill,3 221 | utimes,2 222 | mbind,6 223 | set_mempolicy,3 224 | get_mempolicy,5 225 | mq_open,4 226 | mq_unlink,1 227 | mq_timedsend,5 228 | mq_timedreceive,5 229 | mq_notify,2 230 | mq_getsetattr,3 231 | kexec_load,4 232 | waitid,5 233 | add_key,5 234 | request_key,4 235 | keyctl,5 236 | ioprio_set,3 237 | ioprio_get,2 238 | inotify_init,1 239 | inotify_add_watch,3 240 | inotify_rm_watch,2 241 | migrate_pages,4 242 | openat,4 243 | mkdirat,3 244 | mknodat,4 245 | fchownat,5 246 | futimesat,3 247 | newfstatat,4 248 | unlinkat,3 249 | renameat,4 250 | linkat,5 251 | symlinkat,3 252 | readlinkat,4 253 | fchmodat,3 254 | faccessat,3 255 | pselect6,6 256 | ppoll,5 257 | unshare,1 258 | set_robust_list,2 259 | get_robust_list,3 260 | splice,6 261 | tee,4 262 | sync_file_range,4 263 | vmsplice,4 264 | move_pages,6 265 | utimensat,4 266 | epoll_pwait,6 267 | signalfd,3 268 | timerfd_create,2 269 | eventfd,1 270 | fallocate,4 271 | timerfd_settime,4 272 | timerfd_gettime,2 273 | accept4,4 274 | signalfd4,4 275 | eventfd2,2 276 | epoll_create1,1 277 | dup3,3 278 | pipe2,2 279 | inotify_init1,1 280 | preadv,5 281 | pwritev,5 282 | rt_tgsigqueueinfo,4 283 | perf_event_open,5 284 | recvmmsg,5 285 | fanotify_init,2 286 | fanotify_mark,5 287 | prlimit64,4 288 | name_to_handle_at,5 289 | open_by_handle_at,3 290 | clock_adjtime,2 291 | syncfs,1 292 | sendmmsg,4 293 | setns,2 294 | getcpu,3 295 | process_vm_readv,6 296 | process_vm_writev,6 297 | kcmp,5 298 | finit_module,3 299 | sched_setattr,3 300 | sched_getattr,4 301 | renameat2,5 302 | seccomp,3 303 | getrandom,3 304 | memfd_create,2 305 | kexec_file_load,5 306 | bpf,3 307 | execveat,5 308 | userfaultfd,1 309 | membarrier,3 310 | mlock2,3 311 | copy_file_range,6 312 | preadv2,6 313 | pwritev2,6 314 | pkey_mprotect,4 315 | pkey_alloc,2 316 | pkey_free,1 317 | statx,5 318 | io_pgetevents,6 319 | rseq,4 320 | pidfd_send_signal,4 321 | io_uring_setup,2 322 | io_uring_enter,6 323 | io_uring_register,4 324 | open_tree,3 325 | move_mount,5 326 | fsopen,2 327 | fsconfig,5 328 | fsmount,3 329 | fspick,3 330 | pidfd_open,2 331 | clone3,2 332 | close_range,3 333 | openat2,4 334 | pidfd_getfd,3 335 | faccessat2,4 336 | process_madvise,5 337 | epoll_pwait2,6 338 | mount_setattr,5 339 | quotactl_fd,4 340 | landlock_create_ruleset,3 341 | landlock_add_rule,4 342 | landlock_restrict_self,2 343 | memfd_secret,1 344 | process_mrelease,2 345 | futex_waitv,5 346 | set_mempolicy_home_node,4 347 | cachestat,2 348 | fchmodat2,2 349 | map_shadow_stack,2 350 | futex_wake,2 351 | futex_wait,2 352 | futex_requeue,2 353 | statmount,2 354 | listmount,2 355 | lsm_get_self_attr,2 356 | lsm_set_self_attr,2 357 | lsm_list_modules,2 358 | -------------------------------------------------------------------------------- /gen/x86_64/syscall_names.csv: -------------------------------------------------------------------------------- 1 | accept,43 2 | accept4,288 3 | access,21 4 | acct,163 5 | add_key,248 6 | adjtimex,159 7 | alarm,37 8 | arch_prctl,158 9 | bind,49 10 | bpf,321 11 | brk,12 12 | cachestat,451 13 | capget,125 14 | capset,126 15 | chdir,80 16 | chmod,90 17 | chown,92 18 | chroot,161 19 | clock_adjtime,305 20 | clock_getres,229 21 | clock_gettime,228 22 | clock_nanosleep,230 23 | clock_settime,227 24 | clone,56 25 | clone3,435 26 | close,3 27 | close_range,436 28 | connect,42 29 | copy_file_range,326 30 | creat,85 31 | delete_module,176 32 | dup,32 33 | dup2,33 34 | dup3,292 35 | epoll_create,213 36 | epoll_create1,291 37 | epoll_ctl,233 38 | epoll_ctl_old,214 39 | epoll_pwait,281 40 | epoll_pwait2,441 41 | epoll_wait,232 42 | epoll_wait_old,215 43 | eventfd,284 44 | eventfd2,290 45 | execve,59 46 | execveat,322 47 | exit,60 48 | exit_group,231 49 | faccessat,269 50 | faccessat2,439 51 | fadvise64,221 52 | fallocate,285 53 | fanotify_init,300 54 | fanotify_mark,301 55 | fchdir,81 56 | fchmod,91 57 | fchmodat,268 58 | fchmodat2,452 59 | fchown,93 60 | fchownat,260 61 | fcntl,72 62 | fdatasync,75 63 | fgetxattr,193 64 | finit_module,313 65 | flistxattr,196 66 | flock,73 67 | fork,57 68 | fremovexattr,199 69 | fsconfig,431 70 | fsetxattr,190 71 | fsmount,432 72 | fsopen,430 73 | fspick,433 74 | fstat,5 75 | fstatfs,138 76 | fsync,74 77 | ftruncate,77 78 | futex,202 79 | futex_requeue,456 80 | futex_wait,455 81 | futex_waitv,449 82 | futex_wake,454 83 | futimesat,261 84 | get_mempolicy,239 85 | get_robust_list,274 86 | get_thread_area,211 87 | getcpu,309 88 | getcwd,79 89 | getdents,78 90 | getdents64,217 91 | getegid,108 92 | geteuid,107 93 | getgid,104 94 | getgroups,115 95 | getitimer,36 96 | getpeername,52 97 | getpgid,121 98 | getpgrp,111 99 | getpid,39 100 | getppid,110 101 | getpriority,140 102 | getrandom,318 103 | getresgid,120 104 | getresuid,118 105 | getrlimit,97 106 | getrusage,98 107 | getsid,124 108 | getsockname,51 109 | getsockopt,55 110 | gettid,186 111 | gettimeofday,96 112 | getuid,102 113 | getxattr,191 114 | init_module,175 115 | inotify_add_watch,254 116 | inotify_init,253 117 | inotify_init1,294 118 | inotify_rm_watch,255 119 | io_cancel,210 120 | io_destroy,207 121 | io_getevents,208 122 | io_pgetevents,333 123 | io_setup,206 124 | io_submit,209 125 | io_uring_enter,426 126 | io_uring_register,427 127 | io_uring_setup,425 128 | ioctl,16 129 | ioperm,173 130 | iopl,172 131 | ioprio_get,252 132 | ioprio_set,251 133 | kcmp,312 134 | kexec_file_load,320 135 | kexec_load,246 136 | keyctl,250 137 | kill,62 138 | landlock_add_rule,445 139 | landlock_create_ruleset,444 140 | landlock_restrict_self,446 141 | lchown,94 142 | lgetxattr,192 143 | link,86 144 | linkat,265 145 | listen,50 146 | listmount,458 147 | listxattr,194 148 | llistxattr,195 149 | lookup_dcookie,212 150 | lremovexattr,198 151 | lseek,8 152 | lsetxattr,189 153 | lsm_get_self_attr,459 154 | lsm_list_modules,461 155 | lsm_set_self_attr,460 156 | lstat,6 157 | madvise,28 158 | map_shadow_stack,453 159 | mbind,237 160 | membarrier,324 161 | memfd_create,319 162 | memfd_secret,447 163 | migrate_pages,256 164 | mincore,27 165 | mkdir,83 166 | mkdirat,258 167 | mknod,133 168 | mknodat,259 169 | mlock,149 170 | mlock2,325 171 | mlockall,151 172 | mmap,9 173 | modify_ldt,154 174 | mount,165 175 | mount_setattr,442 176 | move_mount,429 177 | move_pages,279 178 | mprotect,10 179 | mq_getsetattr,245 180 | mq_notify,244 181 | mq_open,240 182 | mq_timedreceive,243 183 | mq_timedsend,242 184 | mq_unlink,241 185 | mremap,25 186 | mseal,462 187 | msgctl,71 188 | msgget,68 189 | msgrcv,70 190 | msgsnd,69 191 | msync,26 192 | munlock,150 193 | munlockall,152 194 | munmap,11 195 | name_to_handle_at,303 196 | nanosleep,35 197 | newfstatat,262 198 | open,2 199 | open_by_handle_at,304 200 | open_tree,428 201 | openat,257 202 | openat2,437 203 | pause,34 204 | perf_event_open,298 205 | personality,135 206 | pidfd_getfd,438 207 | pidfd_open,434 208 | pidfd_send_signal,424 209 | pipe,22 210 | pipe2,293 211 | pivot_root,155 212 | pkey_alloc,330 213 | pkey_free,331 214 | pkey_mprotect,329 215 | poll,7 216 | ppoll,271 217 | prctl,157 218 | pread64,17 219 | preadv,295 220 | preadv2,327 221 | prlimit64,302 222 | process_madvise,440 223 | process_mrelease,448 224 | process_vm_readv,310 225 | process_vm_writev,311 226 | pselect6,270 227 | ptrace,101 228 | pwrite64,18 229 | pwritev,296 230 | pwritev2,328 231 | quotactl,179 232 | quotactl_fd,443 233 | read,0 234 | readahead,187 235 | readlink,89 236 | readlinkat,267 237 | readv,19 238 | reboot,169 239 | recvfrom,45 240 | recvmmsg,299 241 | recvmsg,47 242 | remap_file_pages,216 243 | removexattr,197 244 | rename,82 245 | renameat,264 246 | renameat2,316 247 | request_key,249 248 | restart_syscall,219 249 | rmdir,84 250 | rseq,334 251 | rt_sigaction,13 252 | rt_sigpending,127 253 | rt_sigprocmask,14 254 | rt_sigqueueinfo,129 255 | rt_sigreturn,15 256 | rt_sigsuspend,130 257 | rt_sigtimedwait,128 258 | rt_tgsigqueueinfo,297 259 | sched_get_priority_max,146 260 | sched_get_priority_min,147 261 | sched_getaffinity,204 262 | sched_getattr,315 263 | sched_getparam,143 264 | sched_getscheduler,145 265 | sched_rr_get_interval,148 266 | sched_setaffinity,203 267 | sched_setattr,314 268 | sched_setparam,142 269 | sched_setscheduler,144 270 | sched_yield,24 271 | seccomp,317 272 | select,23 273 | semctl,66 274 | semget,64 275 | semop,65 276 | semtimedop,220 277 | sendfile,40 278 | sendmmsg,307 279 | sendmsg,46 280 | sendto,44 281 | set_mempolicy,238 282 | set_mempolicy_home_node,450 283 | set_robust_list,273 284 | set_thread_area,205 285 | set_tid_address,218 286 | setdomainname,171 287 | setfsgid,123 288 | setfsuid,122 289 | setgid,106 290 | setgroups,116 291 | sethostname,170 292 | setitimer,38 293 | setns,308 294 | setpgid,109 295 | setpriority,141 296 | setregid,114 297 | setresgid,119 298 | setresuid,117 299 | setreuid,113 300 | setrlimit,160 301 | setsid,112 302 | setsockopt,54 303 | settimeofday,164 304 | setuid,105 305 | setxattr,188 306 | shmat,30 307 | shmctl,31 308 | shmdt,67 309 | shmget,29 310 | shutdown,48 311 | sigaltstack,131 312 | signalfd,282 313 | signalfd4,289 314 | socket,41 315 | socketpair,53 316 | splice,275 317 | stat,4 318 | statfs,137 319 | statmount,457 320 | statx,332 321 | swapoff,168 322 | swapon,167 323 | symlink,88 324 | symlinkat,266 325 | sync,162 326 | sync_file_range,277 327 | syncfs,306 328 | sysfs,139 329 | sysinfo,99 330 | syslog,103 331 | tee,276 332 | tgkill,234 333 | time,201 334 | timer_create,222 335 | timer_delete,226 336 | timer_getoverrun,225 337 | timer_gettime,224 338 | timer_settime,223 339 | timerfd_create,283 340 | timerfd_gettime,287 341 | timerfd_settime,286 342 | times,100 343 | tkill,200 344 | truncate,76 345 | umask,95 346 | umount2,166 347 | uname,63 348 | unlink,87 349 | unlinkat,263 350 | unshare,272 351 | userfaultfd,323 352 | ustat,136 353 | utime,132 354 | utimensat,280 355 | utimes,235 356 | vfork,58 357 | vhangup,153 358 | vmsplice,278 359 | wait4,61 360 | waitid,247 361 | write,1 362 | writev,20 363 | -------------------------------------------------------------------------------- /gen_syscall_headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | 7 | SYSCALL_TOOL=$SCRIPT_DIR/syscall_signature_debian.py 8 | 9 | OUT_GEN_DIR=$SCRIPT_DIR/gen 10 | OUT_SYSCALL_NAMES_BASENAME=syscall_names 11 | OUT_SYSCALL_NUM_PARAMS_BASENAME=syscall_num_params 12 | 13 | TEMP_DIR=`mktemp -d` 14 | mkdir -p $OUT_GEN_DIR 15 | 16 | cd $TEMP_DIR 17 | 18 | git clone https://github.com/hrw/syscalls-table 19 | pip install ./syscalls-table 20 | 21 | from_debian_base=$OUT_GEN_DIR/$OUT_SYSCALL_NUM_PARAMS_BASENAME 22 | 23 | # Generate (name,num_params) csv. 24 | $SYSCALL_TOOL list --fmt name num_params > $from_debian_base.csv 25 | 26 | for arch in x86_64 riscv64; do 27 | mkdir -p $OUT_GEN_DIR/$arch 28 | from_python_base=$OUT_GEN_DIR/$arch/$OUT_SYSCALL_NAMES_BASENAME 29 | 30 | # Generate (name,number) csv 31 | ./syscalls-table/bin/syscall --dump $arch | awk '{ print $1 "," $2 }' > $from_python_base.csv 32 | done 33 | -------------------------------------------------------------------------------- /jpg/count_lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bieganski/asstrace/523229b1d5b2529a15e31fb12a49913827fd0f39/jpg/count_lines.png -------------------------------------------------------------------------------- /jpg/pathsubst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bieganski/asstrace/523229b1d5b2529a15e31fb12a49913827fd0f39/jpg/pathsubst.png -------------------------------------------------------------------------------- /jpg/unlink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bieganski/asstrace/523229b1d5b2529a15e31fb12a49913827fd0f39/jpg/unlink.png -------------------------------------------------------------------------------- /signatures.txt: -------------------------------------------------------------------------------- 1 | long read(unsigned int fd, char __user *buf, size_t count); 2 | long write(unsigned int fd, const char __user *buf, size_t count); 3 | long open(const char __user *filename, int flags, umode_t mode); 4 | long close(unsigned int fd); 5 | long newstat(const char __user *filename, struct stat __user *statbuf); 6 | long newfstat(unsigned int fd, struct stat __user *statbuf); 7 | long newlstat(const char __user *filename, struct stat __user *statbuf); 8 | long poll(struct pollfd __user *ufds, unsigned int nfds, int timeout); 9 | long lseek(unsigned int fd, off_t offset, unsigned int whence); 10 | long mmap_pgoff(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long pgoff); 11 | long mprotect(unsigned long start, size_t len, unsigned long prot); 12 | long munmap(unsigned long addr, size_t len); 13 | long brk(unsigned long brk); 14 | long rt_sigaction(int, const struct sigaction __user *, struct sigaction __user *, size_t); 15 | long rt_sigprocmask(int how, sigset_t __user *set, sigset_t __user *oset, size_t sigsetsize); 16 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 17 | long ioctl(unsigned int fd, unsigned int cmd, unsigned long arg); 18 | long pread64(unsigned int fd, char __user *buf, size_t count, loff_t pos); 19 | long pwrite64(unsigned int fd, const char __user *buf, size_t count, loff_t pos); 20 | long readv(unsigned long fd, const struct iovec __user *vec, unsigned long vlen); 21 | long writev(unsigned long fd, const struct iovec __user *vec, unsigned long vlen); 22 | long access(const char __user *filename, int mode); 23 | long pipe(int __user *fildes); 24 | long select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct __kernel_old_timeval __user *tvp); 25 | long sched_yield(void); 26 | long mremap(unsigned long addr, unsigned long old_len, unsigned long new_len, unsigned long flags, unsigned long new_addr); 27 | long msync(unsigned long start, size_t len, int flags); 28 | long mincore(unsigned long start, size_t len, unsigned char __user * vec); 29 | long madvise(unsigned long start, size_t len, int behavior); 30 | long shmget(key_t key, size_t size, int flag); 31 | long shmat(int shmid, char __user *shmaddr, int shmflg); 32 | long shmctl(int shmid, int cmd, struct shmid_ds __user *buf); 33 | long dup(unsigned int fildes); 34 | long dup2(unsigned int oldfd, unsigned int newfd); 35 | long pause(void); 36 | long nanosleep(struct __kernel_timespec __user *rqtp, struct __kernel_timespec __user *rmtp); 37 | long getitimer(int which, struct __kernel_old_itimerval __user *value); 38 | long alarm(unsigned int seconds); 39 | long setitimer(int which, struct __kernel_old_itimerval __user *value, struct __kernel_old_itimerval __user *ovalue); 40 | long getpid(void); 41 | long sendfile64(int out_fd, int in_fd, loff_t __user *offset, size_t count); 42 | long socket(int, int, int); 43 | long connect(int, struct sockaddr __user *, int); 44 | long accept(int, struct sockaddr __user *, int __user *); 45 | long sendto(int, void __user *, size_t, unsigned, struct sockaddr __user *, int); 46 | long recvfrom(int, void __user *, size_t, unsigned, struct sockaddr __user *, int __user *); 47 | long sendmsg(int fd, struct user_msghdr __user *msg, unsigned flags); 48 | long recvmsg(int fd, struct user_msghdr __user *msg, unsigned flags); 49 | long shutdown(int, int); 50 | long bind(int, struct sockaddr __user *, int); 51 | long listen(int, int); 52 | long getsockname(int, struct sockaddr __user *, int __user *); 53 | long getpeername(int, struct sockaddr __user *, int __user *); 54 | long socketpair(int, int, int, int __user *); 55 | long setsockopt(int fd, int level, int optname, char __user *optval, int optlen); 56 | long getsockopt(int fd, int level, int optname, char __user *optval, int __user *optlen); 57 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 58 | long fork(void); 59 | long vfork(void); 60 | long execve(const char __user *filename, const char __user *const __user *argv, const char __user *const __user *envp); 61 | long exit(int error_code); 62 | long wait4(pid_t pid, int __user *stat_addr, int options, struct rusage __user *ru); 63 | long kill(pid_t pid, int sig); 64 | long newuname(struct new_utsname __user *name); 65 | long semget(key_t key, int nsems, int semflg); 66 | long semop(int semid, struct sembuf __user *sops, unsigned nsops); 67 | long semctl(int semid, int semnum, int cmd, unsigned long arg); 68 | long shmdt(char __user *shmaddr); 69 | long msgget(key_t key, int msgflg); 70 | long msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz, int msgflg); 71 | long msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz, long msgtyp, int msgflg); 72 | long msgctl(int msqid, int cmd, struct msqid_ds __user *buf); 73 | long fcntl(unsigned int fd, unsigned int cmd, unsigned long arg); 74 | long flock(unsigned int fd, unsigned int cmd); 75 | long fsync(unsigned int fd); 76 | long fdatasync(unsigned int fd); 77 | long truncate(const char __user *path, long length); 78 | long ftruncate(unsigned int fd, unsigned long length); 79 | long getdents(unsigned int fd, struct linux_dirent __user *dirent, unsigned int count); 80 | long getcwd(char __user *buf, unsigned long size); 81 | long chdir(const char __user *filename); 82 | long fchdir(unsigned int fd); 83 | long rename(const char __user *oldname, const char __user *newname); 84 | long mkdir(const char __user *pathname, umode_t mode); 85 | long rmdir(const char __user *pathname); 86 | long creat(const char __user *pathname, umode_t mode); 87 | long link(const char __user *oldname, const char __user *newname); 88 | long unlink(const char __user *pathname); 89 | long symlink(const char __user *old, const char __user *new); 90 | long readlink(const char __user *path, char __user *buf, int bufsiz); 91 | long chmod(const char __user *filename, umode_t mode); 92 | long fchmod(unsigned int fd, umode_t mode); 93 | long chown(const char __user *filename, uid_t user, gid_t group); 94 | long fchown(unsigned int fd, uid_t user, gid_t group); 95 | long lchown(const char __user *filename, uid_t user, gid_t group); 96 | long umask(int mask); 97 | long gettimeofday(struct __kernel_old_timeval __user *tv, struct timezone __user *tz); 98 | long getrlimit(unsigned int resource, struct rlimit __user *rlim); 99 | long getrusage(int who, struct rusage __user *ru); 100 | long sysinfo(struct sysinfo __user *info); 101 | long times(struct tms __user *tbuf); 102 | long ptrace(long request, long pid, unsigned long addr, unsigned long data); 103 | long getuid(void); 104 | long syslog(int type, char __user *buf, int len); 105 | long getgid(void); 106 | long setuid(uid_t uid); 107 | long setgid(gid_t gid); 108 | long geteuid(void); 109 | long getegid(void); 110 | long setpgid(pid_t pid, pid_t pgid); 111 | long getppid(void); 112 | long getpgrp(void); 113 | long setsid(void); 114 | long setreuid(uid_t ruid, uid_t euid); 115 | long setregid(gid_t rgid, gid_t egid); 116 | long getgroups(int gidsetsize, gid_t __user *grouplist); 117 | long setgroups(int gidsetsize, gid_t __user *grouplist); 118 | long setresuid(uid_t ruid, uid_t euid, uid_t suid); 119 | long getresuid(uid_t __user *ruid, uid_t __user *euid, uid_t __user *suid); 120 | long setresgid(gid_t rgid, gid_t egid, gid_t sgid); 121 | long getresgid(gid_t __user *rgid, gid_t __user *egid, gid_t __user *sgid); 122 | long getpgid(pid_t pid); 123 | long setfsuid(uid_t uid); 124 | long setfsgid(gid_t gid); 125 | long getsid(pid_t pid); 126 | long capget(cap_user_header_t header, cap_user_data_t dataptr); 127 | long capset(cap_user_header_t header, const cap_user_data_t data); 128 | long rt_sigpending(sigset_t __user *set, size_t sigsetsize); 129 | long rt_sigtimedwait(const sigset_t __user *uthese, siginfo_t __user *uinfo, const struct __kernel_timespec __user *uts, size_t sigsetsize); 130 | long rt_sigqueueinfo(pid_t pid, int sig, siginfo_t __user *uinfo); 131 | long rt_sigsuspend(sigset_t __user *unewset, size_t sigsetsize); 132 | long sigaltstack(const struct sigaltstack __user *uss, struct sigaltstack __user *uoss); 133 | long utime(char __user *filename, struct utimbuf __user *times); 134 | long mknod(const char __user *filename, umode_t mode, unsigned dev); 135 | long personality(unsigned int personality); 136 | long ustat(unsigned dev, struct ustat __user *ubuf); 137 | long statfs(const char __user * path, struct statfs __user *buf); 138 | long fstatfs(unsigned int fd, struct statfs __user *buf); 139 | long sysfs(int option, unsigned long arg1, unsigned long arg2); 140 | long getpriority(int which, int who); 141 | long setpriority(int which, int who, int niceval); 142 | long sched_setparam(pid_t pid, struct sched_param __user *param); 143 | long sched_getparam(pid_t pid, struct sched_param __user *param); 144 | long sched_setscheduler(pid_t pid, int policy, struct sched_param __user *param); 145 | long sched_getscheduler(pid_t pid); 146 | long sched_get_priority_max(int policy); 147 | long sched_get_priority_min(int policy); 148 | long sched_rr_get_interval(pid_t pid, struct __kernel_timespec __user *interval); 149 | long mlock(unsigned long start, size_t len); 150 | long munlock(unsigned long start, size_t len); 151 | long mlockall(int flags); 152 | long munlockall(void); 153 | long vhangup(void); 154 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 155 | long pivot_root(const char __user *new_root, const char __user *put_old); 156 | long ni_syscall(void); 157 | long prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); 158 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 159 | long adjtimex(struct __kernel_timex __user *txc_p); 160 | long setrlimit(unsigned int resource, struct rlimit __user *rlim); 161 | long chroot(const char __user *filename); 162 | long sync(void); 163 | long acct(const char __user *name); 164 | long settimeofday(struct __kernel_old_timeval __user *tv, struct timezone __user *tz); 165 | long mount(char __user *dev_name, char __user *dir_name, char __user *type, unsigned long flags, void __user *data); 166 | long umount(char __user *name, int flags); 167 | long swapon(const char __user *specialfile, int swap_flags); 168 | long swapoff(const char __user *specialfile); 169 | long reboot(int magic1, int magic2, unsigned int cmd, void __user *arg); 170 | long sethostname(char __user *name, int len); 171 | long setdomainname(char __user *name, int len); 172 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 173 | long ioperm(unsigned long from, unsigned long num, int on); 174 | long init_module(void __user *umod, unsigned long len, const char __user *uargs); 175 | long delete_module(const char __user *name_user, unsigned int flags); 176 | long quotactl(unsigned int cmd, const char __user *special, qid_t id, void __user *addr); 177 | long gettid(void); 178 | long readahead(int fd, loff_t offset, size_t count); 179 | long setxattr(const char __user *path, const char __user *name, const void __user *value, size_t size, int flags); 180 | long lsetxattr(const char __user *path, const char __user *name, const void __user *value, size_t size, int flags); 181 | long fsetxattr(int fd, const char __user *name, const void __user *value, size_t size, int flags); 182 | long getxattr(const char __user *path, const char __user *name, void __user *value, size_t size); 183 | long lgetxattr(const char __user *path, const char __user *name, void __user *value, size_t size); 184 | long fgetxattr(int fd, const char __user *name, void __user *value, size_t size); 185 | long listxattr(const char __user *path, char __user *list, size_t size); 186 | long llistxattr(const char __user *path, char __user *list, size_t size); 187 | long flistxattr(int fd, char __user *list, size_t size); 188 | long removexattr(const char __user *path, const char __user *name); 189 | long lremovexattr(const char __user *path, const char __user *name); 190 | long fremovexattr(int fd, const char __user *name); 191 | long tkill(pid_t pid, int sig); 192 | long time(__kernel_old_time_t __user *tloc); 193 | long futex(u32 __user *uaddr, int op, u32 val, const struct __kernel_timespec __user *utime, u32 __user *uaddr2, u32 val3); 194 | long sched_setaffinity(pid_t pid, unsigned int len, unsigned long __user *user_mask_ptr); 195 | long sched_getaffinity(pid_t pid, unsigned int len, unsigned long __user *user_mask_ptr); 196 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 197 | long destroy(aio_context_t ctx); 198 | long getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event __user *events, struct __kernel_timespec __user *timeout); 199 | long submit(aio_context_t, long, struct iocb __user * __user *); 200 | long cancel(aio_context_t ctx_id, struct iocb __user *iocb, struct io_event __user *result); 201 | long epoll_create(int size); 202 | long remap_file_pages(unsigned long start, unsigned long size, unsigned long prot, unsigned long pgoff, unsigned long flags); 203 | long getdents64(unsigned int fd, struct linux_dirent64 __user *dirent, unsigned int count); 204 | long set_tid_address(int __user *tidptr); 205 | long restart_syscall(void); 206 | long semtimedop(int semid, struct sembuf __user *sops, unsigned nsops, const struct __kernel_timespec __user *timeout); 207 | long fadvise64(int fd, loff_t offset, size_t len, int advice); 208 | long timer_create(clockid_t which_clock, struct sigevent __user *timer_event_spec, timer_t __user * created_timer_id); 209 | long timer_settime(timer_t timer_id, int flags, const struct __kernel_itimerspec __user *new_setting, struct __kernel_itimerspec __user *old_setting); 210 | long timer_gettime(timer_t timer_id, struct __kernel_itimerspec __user *setting); 211 | long timer_getoverrun(timer_t timer_id); 212 | long timer_delete(timer_t timer_id); 213 | long clock_settime(clockid_t which_clock, const struct __kernel_timespec __user *tp); 214 | long clock_gettime(clockid_t which_clock, struct __kernel_timespec __user *tp); 215 | long clock_getres(clockid_t which_clock, struct __kernel_timespec __user *tp); 216 | long clock_nanosleep(clockid_t which_clock, int flags, const struct __kernel_timespec __user *rqtp, struct __kernel_timespec __user *rmtp); 217 | long exit_group(int error_code); 218 | long epoll_wait(int epfd, struct epoll_event __user *events, int maxevents, int timeout); 219 | long epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event); 220 | long tgkill(pid_t tgid, pid_t pid, int sig); 221 | long utimes(char __user *filename, struct __kernel_old_timeval __user *utimes); 222 | long mbind(unsigned long start, unsigned long len, unsigned long mode, const unsigned long __user *nmask, unsigned long maxnode, unsigned flags); 223 | long set_mempolicy(int mode, const unsigned long __user *nmask, unsigned long maxnode); 224 | long get_mempolicy(int __user *policy, unsigned long __user *nmask, unsigned long maxnode, unsigned long addr, unsigned long flags); 225 | long mq_open(const char __user *name, int oflag, umode_t mode, struct mq_attr __user *attr); 226 | long mq_unlink(const char __user *name); 227 | long mq_timedsend(mqd_t mqdes, const char __user *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct __kernel_timespec __user *abs_timeout); 228 | long mq_timedreceive(mqd_t mqdes, char __user *msg_ptr, size_t msg_len, unsigned int __user *msg_prio, const struct __kernel_timespec __user *abs_timeout); 229 | long mq_notify(mqd_t mqdes, const struct sigevent __user *notification); 230 | long mq_getsetattr(mqd_t mqdes, const struct mq_attr __user *mqstat, struct mq_attr __user *omqstat); 231 | long kexec_load(unsigned long entry, unsigned long nr_segments, struct kexec_segment __user *segments, unsigned long flags); 232 | long waitid(int which, pid_t pid, struct siginfo __user *infop, int options, struct rusage __user *ru); 233 | long add_key(const char __user *_type, const char __user *_description, const void __user *_payload, size_t plen, key_serial_t destringid); 234 | long request_key(const char __user *_type, const char __user *_description, const char __user *_callout_info, key_serial_t destringid); 235 | long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); 236 | long ioprio_set(int which, int who, int ioprio); 237 | long ioprio_get(int which, int who); 238 | long inotify_init(void); 239 | long inotify_add_watch(int fd, const char __user *path, u32 mask); 240 | long inotify_rm_watch(int fd, __s32 wd); 241 | long migrate_pages(pid_t pid, unsigned long maxnode, const unsigned long __user *from, const unsigned long __user *to); 242 | long openat(int dfd, const char __user *filename, int flags, umode_t mode); 243 | long mkdirat(int dfd, const char __user * pathname, umode_t mode); 244 | long mknodat(int dfd, const char __user * filename, umode_t mode, unsigned dev); 245 | long fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, int flag); 246 | long futimesat(int dfd, const char __user *filename, struct __kernel_old_timeval __user *utimes); 247 | long newfstatat(int dfd, const char __user *filename, struct stat __user *statbuf, int flag); 248 | long unlinkat(int dfd, const char __user * pathname, int flag); 249 | long renameat(int olddfd, const char __user * oldname, int newdfd, const char __user * newname); 250 | long linkat(int olddfd, const char __user *oldname, int newdfd, const char __user *newname, int flags); 251 | long symlinkat(const char __user * oldname, int newdfd, const char __user * newname); 252 | long readlinkat(int dfd, const char __user *path, char __user *buf, int bufsiz); 253 | long fchmodat(int dfd, const char __user * filename, umode_t mode); 254 | long faccessat(int dfd, const char __user *filename, int mode); 255 | long pselect6(int, fd_set __user *, fd_set __user *, fd_set __user *, struct __kernel_timespec __user *, void __user *); 256 | long ppoll(struct pollfd __user *, unsigned int, struct __kernel_timespec __user *, const sigset_t __user *, size_t); 257 | long unshare(unsigned long unshare_flags); 258 | long set_robust_list(struct robust_list_head __user *head, size_t len); 259 | long get_robust_list(int pid, struct robust_list_head __user * __user *head_ptr, size_t __user *len_ptr); 260 | long splice(int fd_in, loff_t __user *off_in, int fd_out, loff_t __user *off_out, size_t len, unsigned int flags); 261 | long tee(int fdin, int fdout, size_t len, unsigned int flags); 262 | long sync_file_range(int fd, loff_t offset, loff_t nbytes, unsigned int flags); 263 | long vmsplice(int fd, const struct iovec __user *iov, unsigned long nr_segs, unsigned int flags); 264 | long move_pages(pid_t pid, unsigned long nr_pages, const void __user * __user *pages, const int __user *nodes, int __user *status, int flags); 265 | long utimensat(int dfd, const char __user *filename, struct __kernel_timespec __user *utimes, int flags); 266 | long epoll_pwait(int epfd, struct epoll_event __user *events, int maxevents, int timeout, const sigset_t __user *sigmask, size_t sigsetsize); 267 | long signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask); 268 | long timerfd_create(int clockid, int flags); 269 | long eventfd(unsigned int count); 270 | long fallocate(int fd, int mode, loff_t offset, loff_t len); 271 | long timerfd_settime(int ufd, int flags, const struct __kernel_itimerspec __user *utmr, struct __kernel_itimerspec __user *otmr); 272 | long timerfd_gettime(int ufd, struct __kernel_itimerspec __user *otmr); 273 | long accept4(int, struct sockaddr __user *, int __user *, int); 274 | long signalfd4(int ufd, sigset_t __user *user_mask, size_t sizemask, int flags); 275 | long eventfd2(unsigned int count, int flags); 276 | long epoll_create1(int flags); 277 | long dup3(unsigned int oldfd, unsigned int newfd, int flags); 278 | long pipe2(int __user *fildes, int flags); 279 | long inotify_init1(int flags); 280 | long preadv(unsigned long fd, const struct iovec __user *vec, unsigned long vlen, unsigned long pos_l, unsigned long pos_h); 281 | long pwritev(unsigned long fd, const struct iovec __user *vec, unsigned long vlen, unsigned long pos_l, unsigned long pos_h); 282 | long rt_tgsigqueueinfo(pid_t tgid, pid_t pid, int sig, siginfo_t __user *uinfo); 283 | long perf_event_open( struct perf_event_attr __user *attr_uptr, pid_t pid, int cpu, int group_fd, unsigned long flags); 284 | long recvmmsg(int fd, struct mmsghdr __user *msg, unsigned int vlen, unsigned flags, struct __kernel_timespec __user *timeout); 285 | long fanotify_init(unsigned int flags, unsigned int event_f_flags); 286 | long fanotify_mark(int fanotify_fd, unsigned int flags, u64 mask, int fd, const char __user *pathname); 287 | long prlimit64(pid_t pid, unsigned int resource, const struct rlimit64 __user *new_rlim, struct rlimit64 __user *old_rlim); 288 | long name_to_handle_at(int dfd, const char __user *name, struct file_handle __user *handle, int __user *mnt_id, int flag); 289 | long open_by_handle_at(int mountdirfd, struct file_handle __user *handle, int flags); 290 | long clock_adjtime(clockid_t which_clock, struct __kernel_timex __user *tx); 291 | long syncfs(int fd); 292 | long sendmmsg(int fd, struct mmsghdr __user *msg, unsigned int vlen, unsigned flags); 293 | long setns(int fd, int nstype); 294 | long getcpu(unsigned __user *cpu, unsigned __user *node, struct getcpu_cache __user *cache); 295 | long process_vm_readv(pid_t pid, const struct iovec __user *lvec, unsigned long liovcnt, const struct iovec __user *rvec, unsigned long riovcnt, unsigned long flags); 296 | long process_vm_writev(pid_t pid, const struct iovec __user *lvec, unsigned long liovcnt, const struct iovec __user *rvec, unsigned long riovcnt, unsigned long flags); 297 | long kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2); 298 | long finit_module(int fd, const char __user *uargs, int flags); 299 | long sched_setattr(pid_t pid, struct sched_attr __user *attr, unsigned int flags); 300 | long sched_getattr(pid_t pid, struct sched_attr __user *attr, unsigned int size, unsigned int flags); 301 | long renameat2(int olddfd, const char __user *oldname, int newdfd, const char __user *newname, unsigned int flags); 302 | long seccomp(unsigned int op, unsigned int flags, void __user *uargs); 303 | long getrandom(char __user *buf, size_t count, unsigned int flags); 304 | long memfd_create(const char __user *uname_ptr, unsigned int flags); 305 | long kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char __user *cmdline_ptr, unsigned long flags); 306 | long bpf(int cmd, union bpf_attr *attr, unsigned int size); 307 | long execveat(int dfd, const char __user *filename, const char __user *const __user *argv, const char __user *const __user *envp, int flags); 308 | long userfaultfd(int flags); 309 | long membarrier(int cmd, unsigned int flags, int cpu_id); 310 | long mlock2(unsigned long start, size_t len, int flags); 311 | long copy_file_range(int fd_in, loff_t __user *off_in, int fd_out, loff_t __user *off_out, size_t len, unsigned int flags); 312 | long preadv2(unsigned long fd, const struct iovec __user *vec, unsigned long vlen, unsigned long pos_l, unsigned long pos_h, rwf_t flags); 313 | long pwritev2(unsigned long fd, const struct iovec __user *vec, unsigned long vlen, unsigned long pos_l, unsigned long pos_h, rwf_t flags); 314 | long pkey_mprotect(unsigned long start, size_t len, unsigned long prot, int pkey); 315 | long pkey_alloc(unsigned long flags, unsigned long init_val); 316 | long pkey_free(int pkey); 317 | long statx(int dfd, const char __user *path, unsigned flags, unsigned mask, struct statx __user *buffer); 318 | long pgetevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event __user *events, struct __kernel_timespec __user *timeout, const struct __aio_sigset *sig); 319 | long rseq(struct rseq __user *rseq, uint32_t rseq_len, int flags, uint32_t sig); 320 | long pidfd_send_signal(int pidfd, int sig, siginfo_t __user *info, unsigned int flags); 321 | long uring_setup(u32 entries, struct io_uring_params __user *p); 322 | long uring_enter(unsigned int fd, u32 to_submit, u32 min_complete, u32 flags, const void __user *argp, size_t argsz); 323 | long uring_register(unsigned int fd, unsigned int op, void __user *arg, unsigned int nr_args); 324 | long open_tree(int dfd, const char __user *path, unsigned flags); 325 | long move_mount(int from_dfd, const char __user *from_path, int to_dfd, const char __user *to_path, unsigned int ms_flags); 326 | long fsopen(const char __user *fs_name, unsigned int flags); 327 | long fsconfig(int fs_fd, unsigned int cmd, const char __user *key, const void __user *value, int aux); 328 | long fsmount(int fs_fd, unsigned int flags, unsigned int ms_flags); 329 | long fspick(int dfd, const char __user *path, unsigned int flags); 330 | long pidfd_open(pid_t pid, unsigned int flags); 331 | long clone3(struct clone_args __user *uargs, size_t size); 332 | long close_range(unsigned int fd, unsigned int max_fd, unsigned int flags); 333 | long openat2(int dfd, const char __user *filename, struct open_how *how, size_t size); 334 | long pidfd_getfd(int pidfd, int fd, unsigned int flags); 335 | long faccessat2(int dfd, const char __user *filename, int mode, int flags); 336 | long process_madvise(int pidfd, const struct iovec __user *vec, size_t vlen, int behavior, unsigned int flags); 337 | long epoll_pwait2(int epfd, struct epoll_event __user *events, int maxevents, const struct __kernel_timespec __user *timeout, const sigset_t __user *sigmask, size_t sigsetsize); 338 | long mount_setattr(int dfd, const char __user *path, unsigned int flags, struct mount_attr __user *uattr, size_t usize); 339 | long quotactl_fd(unsigned int fd, unsigned int cmd, qid_t id, void __user *addr); 340 | long landlock_create_ruleset(const struct landlock_ruleset_attr __user *attr, size_t size, __u32 flags); 341 | long landlock_add_rule(int ruleset_fd, enum landlock_rule_type rule_type, const void __user *rule_attr, __u32 flags); 342 | long landlock_restrict_self(int ruleset_fd, __u32 flags); 343 | long memfd_secret(unsigned int flags); 344 | long process_mrelease(int pidfd, unsigned int flags); 345 | long futex_waitv(struct futex_waitv *waiters, unsigned int nr_futexes, unsigned int flags, struct __kernel_timespec __user *timeout, clockid_t clockid); 346 | long set_mempolicy_home_node(unsigned long start, unsigned long len, unsigned long home_node, unsigned long flags); 347 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 348 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 349 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 350 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 351 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 352 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 353 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 354 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 355 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 356 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 357 | long setup(unsigned nr_reqs, aio_context_t __user *ctx); 358 | -------------------------------------------------------------------------------- /syscall_64.tbl: -------------------------------------------------------------------------------- 1 | # 2 | # 64-bit system call numbers and entry vectors 3 | # 4 | # The format is: 5 | # 6 | # 7 | # The __x64_sys_*() stubs are created on-the-fly for sys_*() system calls 8 | # 9 | # The abi is "common", "64" or "x32" for this file. 10 | # 11 | 0 common read sys_read 12 | 1 common write sys_write 13 | 2 common open sys_open 14 | 3 common close sys_close 15 | 4 common stat sys_newstat 16 | 5 common fstat sys_newfstat 17 | 6 common lstat sys_newlstat 18 | 7 common poll sys_poll 19 | 8 common lseek sys_lseek 20 | 9 common mmap sys_mmap 21 | 10 common mprotect sys_mprotect 22 | 11 common munmap sys_munmap 23 | 12 common brk sys_brk 24 | 13 64 rt_sigaction sys_rt_sigaction 25 | 14 common rt_sigprocmask sys_rt_sigprocmask 26 | 15 64 rt_sigreturn sys_rt_sigreturn 27 | 16 64 ioctl sys_ioctl 28 | 17 common pread64 sys_pread64 29 | 18 common pwrite64 sys_pwrite64 30 | 19 64 readv sys_readv 31 | 20 64 writev sys_writev 32 | 21 common access sys_access 33 | 22 common pipe sys_pipe 34 | 23 common select sys_select 35 | 24 common sched_yield sys_sched_yield 36 | 25 common mremap sys_mremap 37 | 26 common msync sys_msync 38 | 27 common mincore sys_mincore 39 | 28 common madvise sys_madvise 40 | 29 common shmget sys_shmget 41 | 30 common shmat sys_shmat 42 | 31 common shmctl sys_shmctl 43 | 32 common dup sys_dup 44 | 33 common dup2 sys_dup2 45 | 34 common pause sys_pause 46 | 35 common nanosleep sys_nanosleep 47 | 36 common getitimer sys_getitimer 48 | 37 common alarm sys_alarm 49 | 38 common setitimer sys_setitimer 50 | 39 common getpid sys_getpid 51 | 40 common sendfile sys_sendfile64 52 | 41 common socket sys_socket 53 | 42 common connect sys_connect 54 | 43 common accept sys_accept 55 | 44 common sendto sys_sendto 56 | 45 64 recvfrom sys_recvfrom 57 | 46 64 sendmsg sys_sendmsg 58 | 47 64 recvmsg sys_recvmsg 59 | 48 common shutdown sys_shutdown 60 | 49 common bind sys_bind 61 | 50 common listen sys_listen 62 | 51 common getsockname sys_getsockname 63 | 52 common getpeername sys_getpeername 64 | 53 common socketpair sys_socketpair 65 | 54 64 setsockopt sys_setsockopt 66 | 55 64 getsockopt sys_getsockopt 67 | 56 common clone sys_clone 68 | 57 common fork sys_fork 69 | 58 common vfork sys_vfork 70 | 59 64 execve sys_execve 71 | 60 common exit sys_exit 72 | 61 common wait4 sys_wait4 73 | 62 common kill sys_kill 74 | 63 common uname sys_newuname 75 | 64 common semget sys_semget 76 | 65 common semop sys_semop 77 | 66 common semctl sys_semctl 78 | 67 common shmdt sys_shmdt 79 | 68 common msgget sys_msgget 80 | 69 common msgsnd sys_msgsnd 81 | 70 common msgrcv sys_msgrcv 82 | 71 common msgctl sys_msgctl 83 | 72 common fcntl sys_fcntl 84 | 73 common flock sys_flock 85 | 74 common fsync sys_fsync 86 | 75 common fdatasync sys_fdatasync 87 | 76 common truncate sys_truncate 88 | 77 common ftruncate sys_ftruncate 89 | 78 common getdents sys_getdents 90 | 79 common getcwd sys_getcwd 91 | 80 common chdir sys_chdir 92 | 81 common fchdir sys_fchdir 93 | 82 common rename sys_rename 94 | 83 common mkdir sys_mkdir 95 | 84 common rmdir sys_rmdir 96 | 85 common creat sys_creat 97 | 86 common link sys_link 98 | 87 common unlink sys_unlink 99 | 88 common symlink sys_symlink 100 | 89 common readlink sys_readlink 101 | 90 common chmod sys_chmod 102 | 91 common fchmod sys_fchmod 103 | 92 common chown sys_chown 104 | 93 common fchown sys_fchown 105 | 94 common lchown sys_lchown 106 | 95 common umask sys_umask 107 | 96 common gettimeofday sys_gettimeofday 108 | 97 common getrlimit sys_getrlimit 109 | 98 common getrusage sys_getrusage 110 | 99 common sysinfo sys_sysinfo 111 | 100 common times sys_times 112 | 101 64 ptrace sys_ptrace 113 | 102 common getuid sys_getuid 114 | 103 common syslog sys_syslog 115 | 104 common getgid sys_getgid 116 | 105 common setuid sys_setuid 117 | 106 common setgid sys_setgid 118 | 107 common geteuid sys_geteuid 119 | 108 common getegid sys_getegid 120 | 109 common setpgid sys_setpgid 121 | 110 common getppid sys_getppid 122 | 111 common getpgrp sys_getpgrp 123 | 112 common setsid sys_setsid 124 | 113 common setreuid sys_setreuid 125 | 114 common setregid sys_setregid 126 | 115 common getgroups sys_getgroups 127 | 116 common setgroups sys_setgroups 128 | 117 common setresuid sys_setresuid 129 | 118 common getresuid sys_getresuid 130 | 119 common setresgid sys_setresgid 131 | 120 common getresgid sys_getresgid 132 | 121 common getpgid sys_getpgid 133 | 122 common setfsuid sys_setfsuid 134 | 123 common setfsgid sys_setfsgid 135 | 124 common getsid sys_getsid 136 | 125 common capget sys_capget 137 | 126 common capset sys_capset 138 | 127 64 rt_sigpending sys_rt_sigpending 139 | 128 64 rt_sigtimedwait sys_rt_sigtimedwait 140 | 129 64 rt_sigqueueinfo sys_rt_sigqueueinfo 141 | 130 common rt_sigsuspend sys_rt_sigsuspend 142 | 131 64 sigaltstack sys_sigaltstack 143 | 132 common utime sys_utime 144 | 133 common mknod sys_mknod 145 | 134 64 uselib 146 | 135 common personality sys_personality 147 | 136 common ustat sys_ustat 148 | 137 common statfs sys_statfs 149 | 138 common fstatfs sys_fstatfs 150 | 139 common sysfs sys_sysfs 151 | 140 common getpriority sys_getpriority 152 | 141 common setpriority sys_setpriority 153 | 142 common sched_setparam sys_sched_setparam 154 | 143 common sched_getparam sys_sched_getparam 155 | 144 common sched_setscheduler sys_sched_setscheduler 156 | 145 common sched_getscheduler sys_sched_getscheduler 157 | 146 common sched_get_priority_max sys_sched_get_priority_max 158 | 147 common sched_get_priority_min sys_sched_get_priority_min 159 | 148 common sched_rr_get_interval sys_sched_rr_get_interval 160 | 149 common mlock sys_mlock 161 | 150 common munlock sys_munlock 162 | 151 common mlockall sys_mlockall 163 | 152 common munlockall sys_munlockall 164 | 153 common vhangup sys_vhangup 165 | 154 common modify_ldt sys_modify_ldt 166 | 155 common pivot_root sys_pivot_root 167 | 156 64 _sysctl sys_ni_syscall 168 | 157 common prctl sys_prctl 169 | 158 common arch_prctl sys_arch_prctl 170 | 159 common adjtimex sys_adjtimex 171 | 160 common setrlimit sys_setrlimit 172 | 161 common chroot sys_chroot 173 | 162 common sync sys_sync 174 | 163 common acct sys_acct 175 | 164 common settimeofday sys_settimeofday 176 | 165 common mount sys_mount 177 | 166 common umount2 sys_umount 178 | 167 common swapon sys_swapon 179 | 168 common swapoff sys_swapoff 180 | 169 common reboot sys_reboot 181 | 170 common sethostname sys_sethostname 182 | 171 common setdomainname sys_setdomainname 183 | 172 common iopl sys_iopl 184 | 173 common ioperm sys_ioperm 185 | 174 64 create_module 186 | 175 common init_module sys_init_module 187 | 176 common delete_module sys_delete_module 188 | 177 64 get_kernel_syms 189 | 178 64 query_module 190 | 179 common quotactl sys_quotactl 191 | 180 64 nfsservctl 192 | 181 common getpmsg 193 | 182 common putpmsg 194 | 183 common afs_syscall 195 | 184 common tuxcall 196 | 185 common security 197 | 186 common gettid sys_gettid 198 | 187 common readahead sys_readahead 199 | 188 common setxattr sys_setxattr 200 | 189 common lsetxattr sys_lsetxattr 201 | 190 common fsetxattr sys_fsetxattr 202 | 191 common getxattr sys_getxattr 203 | 192 common lgetxattr sys_lgetxattr 204 | 193 common fgetxattr sys_fgetxattr 205 | 194 common listxattr sys_listxattr 206 | 195 common llistxattr sys_llistxattr 207 | 196 common flistxattr sys_flistxattr 208 | 197 common removexattr sys_removexattr 209 | 198 common lremovexattr sys_lremovexattr 210 | 199 common fremovexattr sys_fremovexattr 211 | 200 common tkill sys_tkill 212 | 201 common time sys_time 213 | 202 common futex sys_futex 214 | 203 common sched_setaffinity sys_sched_setaffinity 215 | 204 common sched_getaffinity sys_sched_getaffinity 216 | 205 64 set_thread_area 217 | 206 64 io_setup sys_io_setup 218 | 207 common io_destroy sys_io_destroy 219 | 208 common io_getevents sys_io_getevents 220 | 209 64 io_submit sys_io_submit 221 | 210 common io_cancel sys_io_cancel 222 | 211 64 get_thread_area 223 | 212 common lookup_dcookie 224 | 213 common epoll_create sys_epoll_create 225 | 214 64 epoll_ctl_old 226 | 215 64 epoll_wait_old 227 | 216 common remap_file_pages sys_remap_file_pages 228 | 217 common getdents64 sys_getdents64 229 | 218 common set_tid_address sys_set_tid_address 230 | 219 common restart_syscall sys_restart_syscall 231 | 220 common semtimedop sys_semtimedop 232 | 221 common fadvise64 sys_fadvise64 233 | 222 64 timer_create sys_timer_create 234 | 223 common timer_settime sys_timer_settime 235 | 224 common timer_gettime sys_timer_gettime 236 | 225 common timer_getoverrun sys_timer_getoverrun 237 | 226 common timer_delete sys_timer_delete 238 | 227 common clock_settime sys_clock_settime 239 | 228 common clock_gettime sys_clock_gettime 240 | 229 common clock_getres sys_clock_getres 241 | 230 common clock_nanosleep sys_clock_nanosleep 242 | 231 common exit_group sys_exit_group 243 | 232 common epoll_wait sys_epoll_wait 244 | 233 common epoll_ctl sys_epoll_ctl 245 | 234 common tgkill sys_tgkill 246 | 235 common utimes sys_utimes 247 | 236 64 vserver 248 | 237 common mbind sys_mbind 249 | 238 common set_mempolicy sys_set_mempolicy 250 | 239 common get_mempolicy sys_get_mempolicy 251 | 240 common mq_open sys_mq_open 252 | 241 common mq_unlink sys_mq_unlink 253 | 242 common mq_timedsend sys_mq_timedsend 254 | 243 common mq_timedreceive sys_mq_timedreceive 255 | 244 64 mq_notify sys_mq_notify 256 | 245 common mq_getsetattr sys_mq_getsetattr 257 | 246 64 kexec_load sys_kexec_load 258 | 247 64 waitid sys_waitid 259 | 248 common add_key sys_add_key 260 | 249 common request_key sys_request_key 261 | 250 common keyctl sys_keyctl 262 | 251 common ioprio_set sys_ioprio_set 263 | 252 common ioprio_get sys_ioprio_get 264 | 253 common inotify_init sys_inotify_init 265 | 254 common inotify_add_watch sys_inotify_add_watch 266 | 255 common inotify_rm_watch sys_inotify_rm_watch 267 | 256 common migrate_pages sys_migrate_pages 268 | 257 common openat sys_openat 269 | 258 common mkdirat sys_mkdirat 270 | 259 common mknodat sys_mknodat 271 | 260 common fchownat sys_fchownat 272 | 261 common futimesat sys_futimesat 273 | 262 common newfstatat sys_newfstatat 274 | 263 common unlinkat sys_unlinkat 275 | 264 common renameat sys_renameat 276 | 265 common linkat sys_linkat 277 | 266 common symlinkat sys_symlinkat 278 | 267 common readlinkat sys_readlinkat 279 | 268 common fchmodat sys_fchmodat 280 | 269 common faccessat sys_faccessat 281 | 270 common pselect6 sys_pselect6 282 | 271 common ppoll sys_ppoll 283 | 272 common unshare sys_unshare 284 | 273 64 set_robust_list sys_set_robust_list 285 | 274 64 get_robust_list sys_get_robust_list 286 | 275 common splice sys_splice 287 | 276 common tee sys_tee 288 | 277 common sync_file_range sys_sync_file_range 289 | 278 64 vmsplice sys_vmsplice 290 | 279 64 move_pages sys_move_pages 291 | 280 common utimensat sys_utimensat 292 | 281 common epoll_pwait sys_epoll_pwait 293 | 282 common signalfd sys_signalfd 294 | 283 common timerfd_create sys_timerfd_create 295 | 284 common eventfd sys_eventfd 296 | 285 common fallocate sys_fallocate 297 | 286 common timerfd_settime sys_timerfd_settime 298 | 287 common timerfd_gettime sys_timerfd_gettime 299 | 288 common accept4 sys_accept4 300 | 289 common signalfd4 sys_signalfd4 301 | 290 common eventfd2 sys_eventfd2 302 | 291 common epoll_create1 sys_epoll_create1 303 | 292 common dup3 sys_dup3 304 | 293 common pipe2 sys_pipe2 305 | 294 common inotify_init1 sys_inotify_init1 306 | 295 64 preadv sys_preadv 307 | 296 64 pwritev sys_pwritev 308 | 297 64 rt_tgsigqueueinfo sys_rt_tgsigqueueinfo 309 | 298 common perf_event_open sys_perf_event_open 310 | 299 64 recvmmsg sys_recvmmsg 311 | 300 common fanotify_init sys_fanotify_init 312 | 301 common fanotify_mark sys_fanotify_mark 313 | 302 common prlimit64 sys_prlimit64 314 | 303 common name_to_handle_at sys_name_to_handle_at 315 | 304 common open_by_handle_at sys_open_by_handle_at 316 | 305 common clock_adjtime sys_clock_adjtime 317 | 306 common syncfs sys_syncfs 318 | 307 64 sendmmsg sys_sendmmsg 319 | 308 common setns sys_setns 320 | 309 common getcpu sys_getcpu 321 | 310 64 process_vm_readv sys_process_vm_readv 322 | 311 64 process_vm_writev sys_process_vm_writev 323 | 312 common kcmp sys_kcmp 324 | 313 common finit_module sys_finit_module 325 | 314 common sched_setattr sys_sched_setattr 326 | 315 common sched_getattr sys_sched_getattr 327 | 316 common renameat2 sys_renameat2 328 | 317 common seccomp sys_seccomp 329 | 318 common getrandom sys_getrandom 330 | 319 common memfd_create sys_memfd_create 331 | 320 common kexec_file_load sys_kexec_file_load 332 | 321 common bpf sys_bpf 333 | 322 64 execveat sys_execveat 334 | 323 common userfaultfd sys_userfaultfd 335 | 324 common membarrier sys_membarrier 336 | 325 common mlock2 sys_mlock2 337 | 326 common copy_file_range sys_copy_file_range 338 | 327 64 preadv2 sys_preadv2 339 | 328 64 pwritev2 sys_pwritev2 340 | 329 common pkey_mprotect sys_pkey_mprotect 341 | 330 common pkey_alloc sys_pkey_alloc 342 | 331 common pkey_free sys_pkey_free 343 | 332 common statx sys_statx 344 | 333 common io_pgetevents sys_io_pgetevents 345 | 334 common rseq sys_rseq 346 | # don't use numbers 387 through 423, add new calls after the last 347 | # 'common' entry 348 | 424 common pidfd_send_signal sys_pidfd_send_signal 349 | 425 common io_uring_setup sys_io_uring_setup 350 | 426 common io_uring_enter sys_io_uring_enter 351 | 427 common io_uring_register sys_io_uring_register 352 | 428 common open_tree sys_open_tree 353 | 429 common move_mount sys_move_mount 354 | 430 common fsopen sys_fsopen 355 | 431 common fsconfig sys_fsconfig 356 | 432 common fsmount sys_fsmount 357 | 433 common fspick sys_fspick 358 | 434 common pidfd_open sys_pidfd_open 359 | 435 common clone3 sys_clone3 360 | 436 common close_range sys_close_range 361 | 437 common openat2 sys_openat2 362 | 438 common pidfd_getfd sys_pidfd_getfd 363 | 439 common faccessat2 sys_faccessat2 364 | 440 common process_madvise sys_process_madvise 365 | 441 common epoll_pwait2 sys_epoll_pwait2 366 | 442 common mount_setattr sys_mount_setattr 367 | 443 common quotactl_fd sys_quotactl_fd 368 | 444 common landlock_create_ruleset sys_landlock_create_ruleset 369 | 445 common landlock_add_rule sys_landlock_add_rule 370 | 446 common landlock_restrict_self sys_landlock_restrict_self 371 | 447 common memfd_secret sys_memfd_secret 372 | 448 common process_mrelease sys_process_mrelease 373 | 449 common futex_waitv sys_futex_waitv 374 | 450 common set_mempolicy_home_node sys_set_mempolicy_home_node 375 | 451 common cachestat sys_cachestat 376 | 452 common fchmodat2 sys_fchmodat2 377 | 453 64 map_shadow_stack sys_map_shadow_stack 378 | 454 common futex_wake sys_futex_wake 379 | 455 common futex_wait sys_futex_wait 380 | 456 common futex_requeue sys_futex_requeue 381 | 457 common statmount sys_statmount 382 | 458 common listmount sys_listmount 383 | 459 common lsm_get_self_attr sys_lsm_get_self_attr 384 | 460 common lsm_set_self_attr sys_lsm_set_self_attr 385 | 461 common lsm_list_modules sys_lsm_list_modules 386 | 387 | # 388 | # Due to a historical design error, certain syscalls are numbered differently 389 | # in x32 as compared to native x86_64. These syscalls have numbers 512-547. 390 | # Do not add new syscalls to this range. Numbers 548 and above are available 391 | # for non-x32 use. 392 | # 393 | 512 x32 rt_sigaction compat_sys_rt_sigaction 394 | 513 x32 rt_sigreturn compat_sys_x32_rt_sigreturn 395 | 514 x32 ioctl compat_sys_ioctl 396 | 515 x32 readv sys_readv 397 | 516 x32 writev sys_writev 398 | 517 x32 recvfrom compat_sys_recvfrom 399 | 518 x32 sendmsg compat_sys_sendmsg 400 | 519 x32 recvmsg compat_sys_recvmsg 401 | 520 x32 execve compat_sys_execve 402 | 521 x32 ptrace compat_sys_ptrace 403 | 522 x32 rt_sigpending compat_sys_rt_sigpending 404 | 523 x32 rt_sigtimedwait compat_sys_rt_sigtimedwait_time64 405 | 524 x32 rt_sigqueueinfo compat_sys_rt_sigqueueinfo 406 | 525 x32 sigaltstack compat_sys_sigaltstack 407 | 526 x32 timer_create compat_sys_timer_create 408 | 527 x32 mq_notify compat_sys_mq_notify 409 | 528 x32 kexec_load compat_sys_kexec_load 410 | 529 x32 waitid compat_sys_waitid 411 | 530 x32 set_robust_list compat_sys_set_robust_list 412 | 531 x32 get_robust_list compat_sys_get_robust_list 413 | 532 x32 vmsplice sys_vmsplice 414 | 533 x32 move_pages sys_move_pages 415 | 534 x32 preadv compat_sys_preadv64 416 | 535 x32 pwritev compat_sys_pwritev64 417 | 536 x32 rt_tgsigqueueinfo compat_sys_rt_tgsigqueueinfo 418 | 537 x32 recvmmsg compat_sys_recvmmsg_time64 419 | 538 x32 sendmmsg compat_sys_sendmmsg 420 | 539 x32 process_vm_readv sys_process_vm_readv 421 | 540 x32 process_vm_writev sys_process_vm_writev 422 | 541 x32 setsockopt sys_setsockopt 423 | 542 x32 getsockopt sys_getsockopt 424 | 543 x32 io_setup compat_sys_io_setup 425 | 544 x32 io_submit compat_sys_io_submit 426 | 545 x32 execveat compat_sys_execveat 427 | 546 x32 preadv2 compat_sys_preadv64v2 428 | 547 x32 pwritev2 compat_sys_pwritev64v2 429 | # This is the end of the legacy x32 range. Numbers 548 and above are 430 | # not special and are not to be used for x32-specific syscalls. 431 | -------------------------------------------------------------------------------- /syscall_signature_debian.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from typing import Optional 4 | import subprocess 5 | from pathlib import Path 6 | import logging 7 | from enum import Enum 8 | from dataclasses import dataclass 9 | 10 | logging.basicConfig(level=logging.INFO) 11 | 12 | from asstrace import Signature 13 | 14 | @dataclass 15 | class SyscallTblRecord: 16 | num: int 17 | name: str 18 | fun_name: str 19 | 20 | @staticmethod 21 | def from_line(line: str) -> Optional["SyscallTblRecord"]: 22 | line = line.strip() 23 | if line.startswith("#"): 24 | return None 25 | if len(line.split()) == 3: 26 | logging.debug(f"Skipping unimplemented syscall {line}") 27 | return None 28 | if len(line.split()) != 4: 29 | logging.debug(f"Skipping bad line: '{line}'") 30 | return None 31 | num, is_x32, name, fun_name = line.split() 32 | 33 | if is_x32 == "x32": # legacy syscall numbers, (hopefully) not used 34 | return None 35 | 36 | if not fun_name.startswith(("sys_", "compat_")): 37 | logging.info(f"fun_name was expecting sys_* or compat_*, got {fun_name} instead") 38 | return SyscallTblRecord(num=num, name=name, fun_name=fun_name) 39 | 40 | def parse_syscall_tbl(contents: str) -> list[SyscallTblRecord]: 41 | lines = contents.splitlines() 42 | res = [SyscallTblRecord.from_line(line) for line in lines] 43 | return [x for x in res if x is not None] 44 | 45 | class SyscallCsvField(Enum): 46 | NAME = "name" # e.g mmap 47 | NUM_PARAMS = "num_params" # e.g. 2 for syscall(a, b, c) 48 | NUMBER = "number" # value of 'a' for syscall(a, ...) 49 | FULL = "full" # full sginature string 50 | 51 | 52 | def run_shell(cmd: str) -> tuple[str, str]: 53 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) 54 | stdout, stderr = process.communicate() 55 | if (ecode := process.returncode): 56 | raise ValueError(f"Command <{cmd}> exited with {ecode}") 57 | return stdout, stderr 58 | 59 | def system_find_linux_headers_dir() -> Path: 60 | stdout, _ = run_shell(f"dpkg -S linux-headers-`uname -r`") 61 | lines = stdout.splitlines() 62 | words = [x.split() for x in lines] 63 | 64 | # Output contains only lines like that one below: 65 | # linux-headers-6.2.0-36-generic: /usr/src/linux-headers-6.2.0-36-generic/include/config/INFINIBAND_QIB_DCA 66 | assert all([2 == len(w) for w in words]) 67 | 68 | # Drop 'linux-headers-6.2.0-36-generic:' part. 69 | words = [w[1] for w in words] 70 | 71 | # And '/usr/src/linux-headers-' prefix should be common for each line. 72 | assert all([w.startswith("/usr/src/linux-headers-")] for w in words) 73 | 74 | top_dir = Path("/".join(words[0].split("/")[:4])) 75 | 76 | return top_dir 77 | 78 | 79 | def system_find_files_w_syscall_signatures() -> list[Path]: 80 | top_dir = system_find_linux_headers_dir() 81 | paths = [ (top_dir / "include/linux" / p) for p in ("syscalls.h", "compat.h") ] 82 | for p in paths: 83 | if not p.exists(): 84 | raise ValueError(f"{p} does not exist") 85 | logging.debug(f"File with syscall signatures found: {p}") 86 | return paths 87 | 88 | def _find_start_line(syscall: str, lines: list[str]) -> Optional[int]: 89 | """ 90 | NOTE: Should work as expected if syscall == "". 91 | """ 92 | for i, line in enumerate(lines): 93 | if line.startswith("asmlinkage") and not f"ksys_{syscall}" in line: 94 | # if f"sys_{syscall}" in line or f"compat_{syscall}" in line: <- not always is in the same line unfortunately. 95 | return i 96 | return None 97 | 98 | def _find_end_line(start_line: int, lines: list[str]) -> int: 99 | for i, line in enumerate(lines[start_line:]): 100 | if ";" in line: 101 | return start_line + i 102 | else: 103 | raise ValueError("find_end_line: internal logic error") 104 | 105 | def system_find_signature(tbl: list[SyscallTblRecord], syscall_name: str) -> Signature: 106 | all_signatures = system_find_all_signatures() 107 | matches = [x for x in tbl if x.name == syscall_name] 108 | assert len(matches) == 1 109 | return find_matching_signature(record=matches[0], signatures=all_signatures) 110 | 111 | def system_find_all_signatures() -> list[Signature]: 112 | paths = system_find_files_w_syscall_signatures() 113 | lines = "\n".join(p.read_text() for p in paths).splitlines() 114 | 115 | cur_line, signatures = 0, [] 116 | 117 | while len(lines): 118 | start = _find_start_line(syscall="", lines=lines) 119 | if start is None: 120 | break 121 | end = _find_end_line(start_line=start, lines=lines) 122 | signatures.append(Signature.from_line(" ".join(lines[start:(end + 1)]))) 123 | cur_line = end + 1 124 | lines = lines[cur_line:] 125 | 126 | return signatures 127 | 128 | 129 | def find_matching_signature(record: SyscallTblRecord, signatures: list[Signature]) -> Signature: 130 | """ 131 | raises if no matching signature found, or if found more than one. 132 | """ 133 | if record.fun_name == "sys_mmap": 134 | matches = [x for x in signatures if x.basename(with_sys_prefix=True) == "sys_mmap_pgoff"] 135 | else: 136 | matches = [x for x in signatures if x.basename(with_sys_prefix=True) == record.fun_name] 137 | if len(matches) == 1: 138 | return matches[0] 139 | logging.info(f"Was expecting to find one matching signature named {record.fun_name}, got {len(matches)} instead") 140 | return signatures[0] 141 | 142 | def list_syscalls(tbl: list[SyscallTblRecord], fmt: list[SyscallCsvField]): 143 | all_signatures = system_find_all_signatures() 144 | signatures_tbl_ordered = [find_matching_signature(r, all_signatures) for r in tbl] 145 | 146 | res = [ [] for _ in range(len(tbl)) ] 147 | 148 | for fmt_variant in fmt: 149 | for lst, rec, sig in zip(res, tbl, signatures_tbl_ordered): 150 | 151 | match fmt_variant: 152 | case SyscallCsvField.NAME: 153 | addend = rec.name 154 | case SyscallCsvField.NUM_PARAMS: 155 | addend = f"{sig.num_params}" 156 | case SyscallCsvField.NUMBER: 157 | addend = rec.num 158 | case SyscallCsvField.FULL: 159 | addend = " ".join(sig._orig.split()).replace("asmlinkage ", "").replace("sys_io_", "").replace("sys_", "") 160 | 161 | lst.append(addend) 162 | 163 | for lst in res: 164 | print(",".join(lst)) 165 | 166 | 167 | """ 168 | IMPORTANT NOTE: 169 | 170 | for syscall number we use different ground truth file than for signatures. 171 | 172 | asm/unistd_64.h only contains mapping RAX -> syscall name. 173 | To find a signature I needed to parse include/linux/syscalls.h. 174 | 175 | The thing is that there are discrepancies between those two, e.g the second one contains ifdefs. 176 | 177 | Here we assume (this is true on my machine) that all syscalls from unistd64.h are included in syscalls.h, 178 | but in syscalls.h are redundant ones. 179 | 180 | All syscall present in unistd_64.h are present in syscalls.h. 181 | 182 | Sometimes there is naming discrepancy between those two - e.g. 'mmap' vs 'mmap_pgoff'. 183 | """ 184 | if __name__ == "__main__": 185 | from argparse import ArgumentParser 186 | parser = ArgumentParser(usage="Print syscall signature to stdout.") 187 | parser.add_argument("-t", "--tbl", type=Path, default=Path(__file__).parent / "syscall_64.tbl") 188 | subparsers = parser.add_subparsers(dest='cmd', required=True) 189 | 190 | find = subparsers.add_parser("find", help="find chosen syscall's signature.") 191 | find.add_argument(dest="syscall_name", help="syscall name: e.g write, poll, etc.") 192 | 193 | list = subparsers.add_parser("list", help="write all syscalls to stdout (one word per line).") 194 | list.add_argument("-f", "--fmt", type=SyscallCsvField, nargs="+", choices=[x for x in SyscallCsvField], required=True) 195 | 196 | args = vars(parser.parse_args()) 197 | cmd = args.pop("cmd") 198 | 199 | tbl : Path = args.pop("tbl") 200 | if not tbl.exists(): 201 | raise ValueError(f"Table {tbl} does not exist!") 202 | 203 | args["tbl"] = parse_syscall_tbl(contents=tbl.read_text()) 204 | 205 | if cmd == "find": 206 | signature = system_find_signature(**args) 207 | print(signature.fmt()) 208 | elif cmd == "list": 209 | list_syscalls(**args) -------------------------------------------------------------------------------- /tools/find_pattern_offset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pathlib import Path 4 | import logging 5 | import re 6 | 7 | logging.basicConfig(level=logging.INFO) 8 | 9 | def bytes_pattern_match(pattern: bytes, content: bytes) -> list[int]: 10 | """ 11 | returns all (potentially none) matches offsets. 12 | """ 13 | matches = list(re.finditer(pattern=pattern, string=content)) 14 | return [x.start() for x in matches] 15 | 16 | def file_pattern_match_find_single_offset(content: bytes, pattern: bytes) -> int: 17 | res = bytes_pattern_match(pattern=pattern, content=content) 18 | if len(res) != 1: 19 | msg = f"Was expecting exactly one match, got {len(res)} instead!" 20 | if len(pattern) <= 16: 21 | msg += f" pattern checked: {pattern}" 22 | raise RuntimeError(msg) 23 | return res[0] 24 | 25 | 26 | if __name__ == "__main__": 27 | from argparse import ArgumentParser 28 | parser = ArgumentParser(usage="will raise if could not find exactly one pattern match. on success will print a single hex number, which is file offset of start of the pattern.") 29 | parser.add_argument("content_file", type=Path, help="path containing a single-line human-readable hex data (e.g. 'ab00ff')") 30 | parser.add_argument("pattern_file", type=Path, help="blob file to match against") 31 | 32 | args = parser.parse_args() 33 | 34 | content = args.content_file.read_bytes() 35 | pattern = bytes.fromhex(args.pattern_file.read_text().strip()) 36 | 37 | print(hex(file_pattern_match_find_single_offset(content=content, pattern=pattern) )) 38 | -------------------------------------------------------------------------------- /tools/objdump_wrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | from dataclasses import dataclass, field 5 | from typing import Optional 6 | import subprocess 7 | from pathlib import Path 8 | import logging 9 | import re 10 | import hashlib 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | 14 | # NOQA - mostly generated by LLM. 15 | 16 | # Function for running shell commands 17 | def run_shell(cmd: str) -> tuple[str, str]: 18 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) 19 | stdout, stderr = process.communicate() 20 | if (ecode := process.returncode): 21 | raise ValueError(f"Command <{cmd}> exited with {ecode}") 22 | return stdout, stderr 23 | 24 | @dataclass 25 | class ObjdumpInsnParsed: 26 | insn_name: str 27 | insn_value: int 28 | insn_size_bytes: int 29 | 30 | full_line: str 31 | file_offset: int 32 | 33 | @dataclass 34 | class ObjdumpFunctionParsed: 35 | fn_name: str 36 | start_fileoff_incl: int 37 | insns: list[ObjdumpInsnParsed] = field(default_factory=list) 38 | 39 | @property 40 | def end_fileoff_excl(self) -> int: 41 | assert len(self.insns) 42 | last_insn = self.insns[-1] 43 | return last_insn.file_offset + last_insn.insn_size_bytes 44 | 45 | def contains_fileoff(self, fileoff: int) -> bool: 46 | return (self.start_fileoff_incl <= fileoff < self.end_fileoff_excl) 47 | 48 | def pformat_signature(self) -> str: 49 | return f"{self.fn_name} (start_fileoff={hex(self.start_fileoff_incl)}, end_fileoff={hex(self.end_fileoff_excl)})" 50 | 51 | def coherency_check(self): 52 | if not len(self.insns): 53 | raise ValueError("Function has empty instructions list!") 54 | 55 | # instructions are ordered and non-overlapping. 56 | for a, b in zip(self.insns, self.insns[1:]): 57 | max_insn_size = 15 # 15 is for x86_64, i hope no other arch has longer instructions :) 58 | if a.insn_size_bytes not in range(1, max_insn_size + 1): 59 | raise ValueError(f"function {self.fn_name}: Instruction size should be at least 1 byte and at most {max_insn_size} bytes, not {a.insn_size_bytes} (file offset: {a.file_offset}, full line: {a.full_line})") 60 | if b.file_offset <= a.file_offset: 61 | raise ValueError(f"function {self.fn_name}: Instructions file offset is not strictly ordered: {a}, {b}") 62 | 63 | # function boundary coherence. 64 | self_start_fileoff = self.start_fileoff_incl 65 | if (first_insn_fileoff := self.insns[0].file_offset) != self_start_fileoff: 66 | raise ValueError(f"function {self.fn_name}: start file offset ({hex(self_start_fileoff)}) does not match it's first instruction's ({hex(first_insn_fileoff)})") 67 | 68 | def __repr__(self): 69 | return self.pformat_signature() 70 | 71 | @dataclass 72 | class ObjdumpOutputParsed: 73 | functions : list[ObjdumpFunctionParsed] = field(default_factory=list) 74 | 75 | def coherency_check(self): 76 | fs = self.functions 77 | assert len(fs) 78 | 79 | duplicates = set([f for f in fs if fs.count(f) > 1]) 80 | 81 | if duplicates: 82 | raise RuntimeError(f"Function names should be unique! {duplicates} are not.") 83 | 84 | for f1, f2 in zip(self.functions, self.functions[1:]): 85 | if (s1 :=f1.start_fileoff_incl) >= (s2 := f2.start_fileoff_incl): 86 | raise RuntimeError(f"Function {f1.fn_name} (start={hex(s1)}) is defined before function {f2.fn_name} (start={hex(s2)})") 87 | 88 | for f in self.functions: 89 | f.coherency_check() 90 | 91 | def parse_objdump_output(objdump_output: str) -> ObjdumpOutputParsed: 92 | sections, current_section = {}, None 93 | functions : list[ObjdumpFunctionParsed] = [] 94 | 95 | for line in objdump_output.splitlines(): 96 | if not line.strip() or "file format " in line: 97 | continue 98 | 99 | tokens = line.split() 100 | 101 | # Case 1: Section Header 102 | # Expected format: "Disassembly of section .text:" 103 | if line.startswith("Disassembly of section "): 104 | # Extract the section name (e.g., ".text") 105 | current_section = line.split()[-1][:-1] # Remove the trailing colon 106 | sections[current_section] = [] 107 | continue 108 | 109 | # Case 2: Function Header (not relevant for section processing) 110 | # Expected format: "0000000000001050 (File Offset: 0x1050):" 111 | if len(tokens) == 5 and (fn_name_token := tokens[1]).startswith("<") and fn_name_token.endswith(">") and (fileoff_token := tokens[-1]).endswith("):"): 112 | vma = int(tokens[0], 16) 113 | file_offset = int(fileoff_token[:-2], 16) 114 | vma_minus_fileoff = vma - file_offset 115 | 116 | functions.append(cur_fn := ObjdumpFunctionParsed( 117 | fn_name=fn_name_token[1:-1], 118 | start_fileoff_incl=file_offset, 119 | )) 120 | 121 | del vma, file_offset 122 | continue 123 | 124 | # Case 3: Instruction Line 125 | # Expected format: " 402000: byte1 byte2 push/mov/... tail1 tail2", with undefined amount of byteX and tailX words. 126 | if len(tokens) >= 2 and tokens[0].endswith(":"): 127 | # NOTE: regex is hard to avoid here, as number of words before "insn_name" is not known. i'm not aware of any objdump's CLI switch that could help 128 | pattern = r"\s*(?P[0-9a-f]+):(?P(\s+[0-9a-f][0-9a-f])+)\s*(?P\S*).*" 129 | res = re.fullmatch(pattern, line) 130 | if res is None: 131 | raise RuntimeError(f"internal error: regex mismatch on line '{line}'") 132 | 133 | insn_vma = int(res.group("insn_vma"), 16) 134 | insn_name = res.group("insn_name").strip() 135 | 136 | # insn_bytes[0] is the most significant byte. 137 | insn_bytes : list[str] = list(res.group("insn_bytes").split().__reversed__()) 138 | 139 | insn_size = len(insn_bytes) 140 | 141 | insn_value = int("".join(insn_bytes), 16) 142 | 143 | insn_file_offset = insn_vma - vma_minus_fileoff 144 | 145 | insn = ObjdumpInsnParsed( 146 | insn_name=insn_name, 147 | insn_size_bytes=insn_size, 148 | insn_value=insn_value, 149 | 150 | full_line=line.strip(), 151 | file_offset=insn_file_offset, 152 | ) 153 | 154 | sections[current_section].append(insn) 155 | cur_fn.insns.append(insn) 156 | 157 | # TODO: legacy, maybe will be used in future. 158 | del sections 159 | 160 | res = ObjdumpOutputParsed(functions=functions) 161 | res.coherency_check() 162 | return res 163 | 164 | def count_instructions(objdump_parsed: ObjdumpOutputParsed, range_start: int, range_end: int) -> dict[str, int]: 165 | from collections import defaultdict 166 | res = defaultdict(int) 167 | 168 | for f in objdump_parsed.functions: 169 | for insn in f.insns: 170 | if range_start <= insn.file_offset < range_end: 171 | res[insn.insn_name] += 1 172 | 173 | return dict(res) 174 | 175 | def get_build_id_if_present(elf: Path) -> Optional[str]: 176 | assert elf.exists() 177 | stdout, _ = run_shell(f"file {elf}") 178 | words = stdout.split() 179 | starswith_magic = "BuildID[sha1]=" 180 | matches = [x for x in words if x.startswith(starswith_magic)] 181 | if len(matches) != 1: 182 | raise ValueError(f"Was expecting a single match for word starting with {starswith_magic}, got {len(matches)} instead") 183 | # strip everything before '=' and a trailing comma 184 | res = matches[0].split("=")[-1][:-1] 185 | return res 186 | 187 | def disas(func: ObjdumpFunctionParsed) -> str: 188 | lines = [] 189 | 190 | # function header 191 | lines.append(func.pformat_signature()) 192 | 193 | for insn in func.insns: 194 | lines.append(insn.full_line) 195 | 196 | return "\n".join(lines) 197 | 198 | def addr2line(objdump_output: ObjdumpOutputParsed, file_offset: int) -> ObjdumpFunctionParsed: 199 | matches = [f for f in objdump_output.functions if f.contains_fileoff(file_offset)] 200 | if len(matches) != 1: 201 | raise RuntimeError(f"Was expecting exactly one match, got {len(matches)} instead!") 202 | return matches[0] 203 | 204 | def system_invoke_objdump_or_use_cached(objdump_executable: Path, elf_file: Path, cache_dir: Path = Path(__file__).parent) -> str: 205 | 206 | if build_id := get_build_id_if_present(elf_file): 207 | cache_fname = f"objdump_buildid_{build_id}" 208 | else: 209 | hash = hashlib.md5(elf_file.read_bytes()).hexdigest() 210 | cache_fname = f"objdump_md5_{hash}" 211 | 212 | cache_file = cache_dir / cache_fname 213 | 214 | if not cache_file.exists(): 215 | logging.info(f"cache not yet there (missing {cache_file})") 216 | cmd = f"{objdump_executable} -M no-aliases --disassemble-zeroes --wide -Fd {elf_file}" 217 | stdout, _ = run_shell(cmd) 218 | cache_file.write_text(stdout) 219 | else: 220 | logging.info(f"cache will be reused ({cache_file})") 221 | stdout = cache_file.read_text() 222 | 223 | return stdout 224 | 225 | # Main function to parse arguments and run the script 226 | def main(): 227 | hex_or_min = lambda _val: 0 if _val == "-" else int(_val, 16) 228 | hex_or_max = lambda _val: 0xffffffff_ffffffff if _val == "-" else int(_val, 16) 229 | 230 | parser = argparse.ArgumentParser(description="Parse ELF file and summarize CPU instructions.") 231 | parser.add_argument("-d", "--objdump-executable", type=Path, help="path to 'objdump' executable, useful if using one from cross-toolchain (e.g. RISC-V)", default="objdump") 232 | parser.add_argument("-e", "--elf_file", type=Path, help="Path to the ELF file.", required=True) 233 | 234 | subparsers = parser.add_subparsers(dest="mode", required=True) 235 | 236 | all_parsers = [ 237 | nm_parser := subparsers.add_parser("nm", help="print all the function names, like 'nm' does"), 238 | 239 | count_parser := subparsers.add_parser("count", help="count instructions (grouped by type) in specidfied file offset range"), 240 | disas_parser := subparsers.add_parser("disas", help="disassemble instructions in specidfied file offset range"), 241 | 242 | find_parser := subparsers.add_parser("find", help="for a given file offset, determine what function it belongs to (like 'addr2line')."), 243 | ] 244 | 245 | find_parser.add_argument("offset", type=lambda x: int(x, 16)) 246 | 247 | for x in (count_parser, disas_parser): 248 | func_or_range_parser = x.add_subparsers(dest="func_or_range", required=True) 249 | range_parser = func_or_range_parser.add_parser("range") 250 | range_parser.add_argument("range_start", type=hex_or_min, help="Start of the file offset range (inclusive, in hex) or '-' if RANGE_MIN should be used.") 251 | range_parser.add_argument("range_end", type=hex_or_max, help="End of the file offset range (exclusive, in hex) or '-' if RANGE_MAX should be used.") 252 | func_name_parser = func_or_range_parser.add_parser("func") 253 | func_name_parser.add_argument("func_name") 254 | 255 | args = parser.parse_args() 256 | 257 | # collect objdump 258 | objdump_raw_output = system_invoke_objdump_or_use_cached(objdump_executable=args.objdump_executable, elf_file=args.elf_file) 259 | # parse objdump 260 | parsed_output = parse_objdump_output(objdump_raw_output) 261 | 262 | if args.mode in ["disas", "count"]: 263 | if (func_or_range := args.func_or_range) == "range": 264 | # TODO: not used at all in 'count' mode. 265 | start, end = args.range_start, args.range_end 266 | filtered = [f for f in parsed_output.functions if f.start_fileoff_incl <= end] 267 | filtered = [f for f in filtered if f.end_fileoff_excl > start] 268 | functions_of_interest = [filtered] 269 | del filtered, start, end 270 | elif func_or_range == "func": 271 | func_name = args.func_name 272 | matches = [f for f in parsed_output.functions if f.fn_name == func_name] 273 | if len(matches) != 1: 274 | raise RuntimeError(f"Could not find function <{func_name}>") 275 | functions_of_interest = [matches[0]] 276 | 277 | if (mode := args.mode) == "nm": 278 | for f in parsed_output.functions: 279 | print(f.pformat_signature()) 280 | elif mode == "count": 281 | 282 | # XXX technical debt. 283 | if (func_or_range := args.func_or_range) == "range": 284 | start, end = args.range_start, args.range_end 285 | else: 286 | start, end = functions_of_interest[0].start_fileoff_incl, functions_of_interest[0].end_fileoff_excl 287 | 288 | res = count_instructions(parsed_output, start, end) 289 | print(f"range: [{hex(start)}, {hex(end)}) // {hex(end - start)}={end - start} bytes") 290 | print(f"num_instructions: {sum(res.values())}") 291 | print("instructions summary:") 292 | for insn_name, count in sorted(res.items(), key=lambda x: x[1], reverse=True): 293 | print(f"{insn_name.ljust(10)} {count}") 294 | elif mode == "disas": 295 | res : str = "\n".join(disas(x) for x in functions_of_interest) 296 | print(res) 297 | elif mode == "find": 298 | f = addr2line(objdump_output=parsed_output, file_offset=args.offset) 299 | print(f.pformat_signature()) 300 | 301 | 302 | if __name__ == "__main__": 303 | main() 304 | 305 | -------------------------------------------------------------------------------- /tools/pid_access_mem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import sys 5 | from pathlib import Path 6 | 7 | sys.path.append(str(Path(__file__).parent.parent.resolve())) 8 | from asstrace import _read_or_write_process_memory 9 | 10 | def main(): 11 | parser = argparse.ArgumentParser(description="CLI frontend for memory read/write operations.") 12 | 13 | parser.add_argument( 14 | "operation", choices=["read", "write"], help="Operation to perform: read or write." 15 | ) 16 | parser.add_argument( 17 | "pid", type=int, help="Process ID (PID) of the target process." 18 | ) 19 | parser.add_argument( 20 | "address", type=lambda x: int(x, 16), help="Memory address in hexadecimal format (e.g., 0x1234)." 21 | ) 22 | parser.add_argument( 23 | "value", type=str, help=( 24 | "Hexadecimal string for data (write operation) or count (read operation). " 25 | "If performing a write, provide a hex string like '0a0b15'. " 26 | "If performing a read, provide the count in hex format, e.g., '0x10'." 27 | ) 28 | ) 29 | 30 | args = parser.parse_args() 31 | 32 | pid = args.pid 33 | address = args.address 34 | operation = args.operation 35 | 36 | if operation == "write": 37 | # Ensure the provided data is a valid hex string. 38 | try: 39 | data = bytes.fromhex(args.value) 40 | except ValueError: 41 | print("Error: Invalid hex string for data.", file=sys.stderr) 42 | sys.exit(1) 43 | 44 | _read_or_write_process_memory(address, len(data), data, pid, do_write=True) 45 | elif operation == "read": 46 | # Parse the count as a hexadecimal number. 47 | try: 48 | if not args.value.startswith("0x"): 49 | raise ValueError() 50 | size = int(args.value, 16) 51 | except ValueError: 52 | print("Error: Invalid hex string for count.", file=sys.stderr) 53 | sys.exit(1) 54 | 55 | data = _read_or_write_process_memory(address, size, bytes(), pid, do_write=False) 56 | print(data.hex()) 57 | 58 | if __name__ == "__main__": 59 | main() -------------------------------------------------------------------------------- /tools/pid_vaddr2line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | from pathlib import Path 5 | import logging 6 | import sys 7 | 8 | sys.path.append(str(Path(__file__).parent.parent.resolve())) 9 | 10 | from find_pattern_offset import file_pattern_match_find_single_offset 11 | from objdump_wrapper import system_invoke_objdump_or_use_cached, parse_objdump_output, addr2line 12 | 13 | logging.basicConfig(level=logging.INFO) 14 | 15 | def run_shell(cmd: str) -> tuple[str, str]: 16 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True) 17 | stdout, stderr = process.communicate() 18 | if (ecode := process.returncode): 19 | raise ValueError(f"Command <{cmd}> exited with {ecode}") 20 | return stdout, stderr 21 | 22 | Map_proc_pid_maps = dict[tuple[int, int], tuple[Path, int]] 23 | 24 | def system_proc_pid_maps_get_regions(pid: int) -> dict[tuple[int, int], tuple[Path, int]]: 25 | """ 26 | NOTE: whenever anything more functionality from /proc/PID/maps will be needed, 27 | # please do not reinvent the wheel and use proper parsing, for example https://gist.github.com/fxthomas/3c915909bbf84bc14782cb6adef0f915 instead. 28 | 29 | returns a mapping from (start, end) to (path, file offset). 30 | """ 31 | res = dict() 32 | lines = Path(f"/proc/{pid}/maps").read_text().splitlines() 33 | for l in lines: 34 | tokens = l.split() 35 | if len(tokens) < 6: 36 | continue # don't care about anonymous mappings 37 | start, end = tokens[0].split("-") 38 | start, end = int(start, 16), int(end, 16) 39 | offset = int(tokens[2], 16) 40 | # XXX: here we assume path not to contain spaces 41 | res[(start, end)] = (Path(tokens[-1]), offset) 42 | return res 43 | 44 | def find_offset_within_elf(map: Map_proc_pid_maps, vaddr: int) -> tuple[Path, int]: 45 | """ 46 | for a given @vaddr and /proc/PID/maps @map representation, returns both a path to ELF whose segment was mmapped and contains @offset 47 | and a file offset corresponding to @vaddr. 48 | """ 49 | matches = [] 50 | for region in map: 51 | start, end = region 52 | if start <= vaddr < end: 53 | matches.append(region) 54 | 55 | if len(matches) != 1: 56 | raise RuntimeError(f"Was expecting exactly one match, got {len(matches)} instead") 57 | 58 | region = matches[0] 59 | start, end = region 60 | elf, file_offset = map[region] 61 | 62 | # @start corresponds to @file_offset, and we are @(vaddr - start) bytes above. 63 | seeked_file_offset = file_offset + (vaddr - start) 64 | logging.info(f"vaddr={hex(vaddr)} corresponds to file_off={hex(seeked_file_offset)} in {elf}") 65 | return elf, seeked_file_offset 66 | 67 | 68 | def main(pid: int, vaddr: int, objdump_executable: Path): 69 | 70 | vaddr_elf, file_offset = find_offset_within_elf(map=system_proc_pid_maps_get_regions(pid=pid), vaddr=vaddr) 71 | 72 | objdump_raw_output = system_invoke_objdump_or_use_cached(objdump_executable=objdump_executable, elf_file=vaddr_elf) 73 | objdump_parsed_output = parse_objdump_output(objdump_raw_output) 74 | 75 | f = addr2line(file_offset=file_offset, objdump_output=objdump_parsed_output) 76 | print(f) 77 | 78 | if __name__ == "__main__": 79 | from argparse import ArgumentParser 80 | parser = ArgumentParser(usage="XXX") 81 | parser.add_argument("pid", type=int) 82 | parser.add_argument("vaddr", type=lambda x: int(x, 16)) 83 | parser.add_argument("-d", "--objdump-executable", type=Path, help="path to 'objdump' executable, useful if using one from cross-toolchain (e.g. RISC-V)", default="objdump") 84 | main(**vars(parser.parse_args())) 85 | --------------------------------------------------------------------------------