Skip to content

Abimael10/strace-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 

Repository files navigation

strace-rs

A minimal strace clone in Rust for Linux.

Features

  • Trace system calls for spawned processes or attach to existing processes
  • syscall decoding
  • Supported syscalls: execve, open, openat, read, write, close, brk, mmap, fstat, newfstatat, access, faccessat2, lseek
  • Argument decoding:
    • File paths and strings with proper escaping
    • Open flags: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND, O_NONBLOCK, O_DIRECTORY, O_CLOEXEC
    • mmap protection flags: PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE
    • mmap mapping flags: MAP_SHARED, MAP_PRIVATE, MAP_ANONYMOUS, MAP_FIXED, MAP_POPULATE, MAP_NONBLOCK, MAP_STACK, MAP_HUGETLB
    • Access modes: R_OK, W_OK, X_OK, F_OK
    • execve argv arrays (decodes all arguments)
    • fstat struct output (st_mode, st_size)
    • read() buffer preview (first 32 bytes)
  • Error codes with descriptions: EPERM, ENOENT, ESRCH, EINTR, EIO, EBADF, EAGAIN, ENOMEM, EACCES, EFAULT, EBUSY, EEXIST, EXDEV, ENOTDIR, EISDIR, EINVAL, ENOTTY, EROFS, EPIPE

Installation

cargo build --release
sudo cp target/release/strace-rs /usr/local/bin/

Usage

Trace a command:

strace-rs -- /bin/echo "hello world"

Attach to running process (requires root):

sudo strace-rs -p <PID>

Example Output

execve("/bin/echo", ["/bin/echo", "hello", "world"], 0x7ffd12345678 /* 58 vars */) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=78127, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1234567000
close(3) = 0
write(1, "hello world\n", 12) = 12
+++ exited with 0 +++

Architecture

src/
├── main.rs       - CLI parsing and trace loop
├── ptrace.rs     - ptrace system call wrappers
├── syscall.rs    - Syscall decoding and formatting
└── errors.rs     - Error types

Syscall Decoding Reference

execve

  • Format: execve(path, [argv...], envp /* N vars */)
  • Decodes: Full argv array, environment variable count
  • Example: execve("/bin/ls", ["/bin/ls", "-la"], 0x7ffd... /* 58 vars */) = 0

open/openat

  • Format: openat(dirfd, path, flags) = fd
  • Decodes: AT_FDCWD constant, all common flags
  • Example: openat(AT_FDCWD, "/tmp/test", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK) = 3

read

  • Format: read(fd, "buffer preview...", size) = bytes_read
  • Decodes: First 32 bytes of buffer with proper escaping (octal for binary, backslash escapes for special chars)
  • Example: read(3, "test content\n", 131072) = 13

write

  • Format: write(fd, "data", size) = bytes_written
  • Decodes: Full string content with escaping
  • Example: write(1, "hello\n", 6) = 6

mmap

  • Format: mmap(addr, length, prot, flags, fd, offset) = addr
  • Decodes: All protection flags (PROT_) and mapping flags (MAP_)
  • Example: mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f...

fstat/newfstatat

  • Format: fstat(fd, {st_mode=..., st_size=..., ...}) = 0
  • Decodes: File type (S_IFREG, S_IFDIR, S_IFLNK, S_IFBLK, S_IFCHR, S_IFIFO, S_IFSOCK) and permissions in octal
  • Example: fstat(3, {st_mode=S_IFREG|0644, st_size=1024, ...}) = 0

access/faccessat2

  • Format: access(path, mode) = result
  • Decodes: F_OK (existence), R_OK (read), W_OK (write), X_OK (execute)
  • Example: access("/etc/passwd", R_OK) = 0

close

  • Format: close(fd) = 0
  • Example: close(3) = 0

brk

  • Format: brk(addr) = new_addr
  • Decodes: NULL pointer as "NULL", addresses in hex
  • Example: brk(NULL) = 0x5566778899aa

lseek

  • Format: lseek(fd) = offset
  • Example: lseek(3) = 0

Requirements

  • Linux with ptrace support
  • Rust 1.70+
  • Root or CAP_SYS_PTRACE capability for attaching to processes
  • x86_64 architecture

Build

cargo build --release

Testing

Verify output matches C's strace:

# Basic file operations
strace cat /etc/hostname 2>&1 | grep "^openat"
strace-rs -- cat /etc/hostname 2>&1 | grep "^openat"

# File creation with flags
strace touch /tmp/test 2>&1 | grep "^openat.*test"
strace-rs -- touch /tmp/test 2>&1 | grep "^openat.*test"

# mmap operations
strace cat /etc/hostname 2>&1 | grep "^mmap"
strace-rs -- cat /etc/hostname 2>&1 | grep "^mmap"

# execve with arguments
strace /bin/echo arg1 arg2 2>&1 | grep "^execve"
strace-rs -- /bin/echo arg1 arg2 2>&1 | grep "^execve"

# fstat struct decoding
strace cat /etc/hostname 2>&1 | grep "^fstat"
strace-rs -- cat /etc/hostname 2>&1 | grep "^fstat"

# read buffer preview
echo "test" > /tmp/test && strace cat /tmp/test 2>&1 | grep "read.*test"
echo "test" > /tmp/test && strace-rs -- cat /tmp/test 2>&1 | grep "read.*test"

Limitations

  • x86_64 only
  • No fork following
  • No syscall filtering
  • No timestamps or statistics
  • No JSON output
  • Limited to common syscalls (extensible architecture allows adding more)

These limitations maintain minimal codebase while covering 95%+ of typical tracing needs.

How It Works

  1. Attach: Fork/exec new process or attach to existing PID via ptrace
  2. Configure: Set PTRACE_O_TRACESYSGOOD to distinguish syscalls from signals
  3. Trace Loop:
    • Wait for ptrace events via waitpid()
    • On syscall entry: Read registers, decode arguments, store state
    • On syscall exit: Read post-syscall memory (for fstat/read), format output
    • On signal: Forward to tracee (except SIGTRAP used by ptrace)
    • On exit: Print exit message
  4. Entry/Exit Detection: Boolean state per PID tracks alternation
  5. Memory Access: ptrace reads tracee memory for strings, buffers, and structs

Implementation Details

Register Layout (x86_64)

  • Syscall number: orig_rax
  • Args 0-2: rdi, rsi, rdx
  • Args 3-5: r10, r8, r9
  • Return value: rax

Memory Reading

  • Strings: Read up to 256 bytes, find null terminator
  • Buffers: Read actual size, preview first 32 bytes
  • Structs: Read fixed-size structures (e.g., 144 bytes for stat)
  • Safety: Size limits prevent excessive allocations
  • Error handling: Graceful fallback to hex address on read failure

Post-Syscall Processing

For syscalls like fstat and read, arguments point to memory that's only valid after the syscall completes:

  • Store entry registers at syscall-enter
  • At syscall-exit, use stored pointers to read memory
  • Format final output with decoded memory content

File Type Decoding (fstat)

Uses bitmask on st_mode:

  • 0x8000 → S_IFREG (regular file)
  • 0x4000 → S_IFDIR (directory)
  • 0xA000 → S_IFLNK (symbolic link)
  • 0x6000 → S_IFBLK (block device)
  • 0x2000 → S_IFCHR (character device)
  • 0x1000 → S_IFIFO (FIFO/pipe)
  • 0xC000 → S_IFSOCK (socket)

Permissions extracted from lower 9 bits, displayed in octal.

Buffer Escaping (read/write)

  • Printable ASCII (0x20-0x7E): Displayed as-is
  • Special chars: \n, \r, \t, \, "
  • Binary data: Octal escapes (\NNN)
  • Null terminators: Stop string reading

License

MIT

About

syscall trace utility for Linux written in Rust

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages