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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions examples/strace-grate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## strace-grate

strace-grate is a lightweight utility for tracing system calls. It outputs detailed system call traces along with passed arguments (print paths, associated file descriptors, etc) and their return values.

### strace APIs

strace-grate provides a macro named `DEFINE_HANDLER` that allows user to specify syscalls to be traced (all syscalls are traced by default). It facilitates syscall interposing, logging and forwarding.

Defination:

```
Arguments:
// 1st: syscall name
// 2nd: syscall number
// 3rd - 8th: ARG type (ARG_INT || ARG_PTR || ARG_STR)
//
// NOTE: if unsure of ARG_TYPE follow:
// https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/

DEFINE_HANDLER(syscall_name, syscall_number, ARG_TYPE_1, ..., ARG_TYPE_6)
```

Example:
```
DEFINE_HANDLER(read, 0, ARG_INT, ARG_PTR, ARG_INT)
```

## Building

Uses [lind_compile](https://github.com/Lind-Project/lind-wasm/blob/main/scripts/lind_compile) script with `--compile-grate` flag.

`lind_compile --compile-grate src/strace_grate.c src/strace.c`

or use the `compile_grate.sh` script to build strace grate.

## Implementation

### Interposing

Lind's [threei](https://github.com/Lind-Project/lind-wasm/tree/main/src/threei) subsystem enables syscall interposition. Each syscall is implemented using the `DEFINE_HANDLER` macro, which generates a dedicated handler function for that syscall.

Every handler maintains a `log_buffer` used to record relevant execution details. When syscalls pass arguments of type string (e.g., pointers to paths or filenames), the handler invokes the `copy_data_between_cages()` helper from threei to safely dereference the value. This allows the system to resolve and log meaningful data such as file paths.

After preprocessing arguments, the handler forwards the syscall to the target cage (i.e., the application running under strace-grate) using `make_threei_call()`. Once the syscall completes, the handler logs the return value and writes the contents of `log_buffer` to `stderr`.

### Syscall Handler Table

All syscall handlers are stored in a centralized syscall handler table `syscall_handler_table`. The `DEFINE_HANDLER` macro not only generates the handler function `<name>_grate` but also defines a corresponding `register_<name>()` function.

This registration function ensures it is executed automatically at grate startup. During this initialization phase, each constructor inserts its associated handler into the table by assigning the function pointer to the appropriate syscall index

## Testing

To validate the implementation, a script is provided that runs Lind's unit test suite under strace-grate. Because strace-grate intercepts and logs all syscalls supported by Lind, this approach ensures comprehensive testing of the interposition layer and verifies that syscall handling behaves correctly across a wide range of scenarios.

## Example Usage:

`lind_run strace_grate.cwasm app.cwasm`

## Future Work

- Flags for tracing user specified system calls.
- Hex to ASCII dump of arguments (e.g flags, modes, etc).
- Add syscall counter and error logging.
6 changes: 6 additions & 0 deletions examples/strace-grate/compile_grate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail

cd "$(dirname "$0")"
lind_compile --compile-grate src/strace_grate.c src/strace.c
127 changes: 127 additions & 0 deletions examples/strace-grate/src/strace.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "strace.h"

// table for storing syscall handlers
syscall_handler_t syscall_handler_table[MAX_SYSCALLS] = {0};

// defined syscall handlers
//
// args:
// 1st: syscall name
// 2nd: syscall number
// 3rd - 8th: ARG type (ARG_INT || ARG_PTR || ARG_STR)
//
// NOTE: if unsure of ARG_TYPE follow:
// https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/
//
// defines handler for all syscalls supported by lind

DEFINE_HANDLER(read, 0, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(write, 1, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(open, 2, ARG_STR, ARG_INT, ARG_INT)
DEFINE_HANDLER(close, 3, ARG_INT)
DEFINE_HANDLER(stat, 4, ARG_STR, ARG_PTR)
DEFINE_HANDLER(fstat, 5, ARG_INT, ARG_PTR)
DEFINE_HANDLER(poll, 7, ARG_PTR, ARG_INT, ARG_INT)
DEFINE_HANDLER(lseek, 8, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(mmap, 9, ARG_PTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(mprotect, 10, ARG_PTR, ARG_INT, ARG_INT)
DEFINE_HANDLER(munmap, 11, ARG_PTR, ARG_INT)
DEFINE_HANDLER(brk, 12, ARG_PTR)
DEFINE_HANDLER(sigaction, 13, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(sigprocmask, 14, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(ioctl, 16, ARG_INT, ARG_INT, ARG_PTR)
DEFINE_HANDLER(pread, 17, ARG_INT, ARG_PTR, ARG_INT, ARG_INT)
DEFINE_HANDLER(pwrite, 18, ARG_INT, ARG_PTR, ARG_INT, ARG_INT)
DEFINE_HANDLER(writev, 20, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(access, 21, ARG_STR, ARG_INT)
DEFINE_HANDLER(pipe, 22, ARG_PTR)
DEFINE_HANDLER(select, 23, ARG_INT, ARG_PTR, ARG_PTR, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(sched_yield, 24)
DEFINE_HANDLER(shmget, 29, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(shmat, 30, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(shmctl, 31, ARG_INT, ARG_INT, ARG_PTR)
DEFINE_HANDLER(dup, 32, ARG_INT)
DEFINE_HANDLER(dup2, 33, ARG_INT, ARG_INT)
DEFINE_HANDLER(nanosleep, 35, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(setitimer, 38, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(getpid, 39)
DEFINE_HANDLER(socket, 41, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(connect, 42, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(accept, 43, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(sendto, 44, ARG_INT, ARG_PTR, ARG_INT, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(recvfrom, 45, ARG_INT, ARG_PTR, ARG_INT, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(shutdown, 48, ARG_INT, ARG_INT)
DEFINE_HANDLER(bind, 49, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(listen, 50, ARG_INT, ARG_INT)
DEFINE_HANDLER(getsockname, 51, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(getpeername, 52, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(socketpair, 53, ARG_INT, ARG_INT, ARG_INT, ARG_PTR)
DEFINE_HANDLER(setsockopt, 54, ARG_INT, ARG_INT, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(getsockopt, 55, ARG_INT, ARG_INT, ARG_INT, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(clone, 56, ARG_INT, ARG_PTR, ARG_PTR, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(fork, 57)
DEFINE_HANDLER(exec, 59, ARG_STR, ARG_PTR, ARG_PTR)
DEFINE_HANDLER(exit, 60, ARG_INT)
DEFINE_HANDLER(waitpid, 61, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(kill, 62, ARG_INT, ARG_INT)
DEFINE_HANDLER(shmdt, 67, ARG_PTR)
DEFINE_HANDLER(fcntl, 72, ARG_INT, ARG_INT, ARG_PTR)
DEFINE_HANDLER(flock, 73, ARG_INT, ARG_INT)
DEFINE_HANDLER(fsync, 74, ARG_INT)
DEFINE_HANDLER(fdatasync, 75, ARG_INT)
DEFINE_HANDLER(truncate, 76, ARG_STR, ARG_INT)
DEFINE_HANDLER(ftruncate, 77, ARG_INT, ARG_INT)
DEFINE_HANDLER(getdents, 78, ARG_INT, ARG_PTR, ARG_INT)
DEFINE_HANDLER(getcwd, 79, ARG_PTR, ARG_INT)
DEFINE_HANDLER(chdir, 80, ARG_STR)
DEFINE_HANDLER(fchdir, 81, ARG_INT)
DEFINE_HANDLER(rename, 82, ARG_STR, ARG_STR)
DEFINE_HANDLER(mkdir, 83, ARG_STR, ARG_INT)
DEFINE_HANDLER(rmdir, 84, ARG_STR)
DEFINE_HANDLER(link, 86, ARG_STR, ARG_STR)
DEFINE_HANDLER(unlink, 87, ARG_STR)
DEFINE_HANDLER(readlink, 89, ARG_STR, ARG_PTR, ARG_INT)
DEFINE_HANDLER(chmod, 90, ARG_STR, ARG_INT)
DEFINE_HANDLER(fchmod, 91, ARG_INT, ARG_INT)
DEFINE_HANDLER(getuid, 102)
DEFINE_HANDLER(getgid, 104)
DEFINE_HANDLER(geteuid, 107)
DEFINE_HANDLER(getegid, 108)
DEFINE_HANDLER(getppid, 110)
DEFINE_HANDLER(statfs, 137, ARG_STR, ARG_PTR)
DEFINE_HANDLER(fstatfs, 138, ARG_INT, ARG_PTR)
DEFINE_HANDLER(gethostname, 170, ARG_PTR, ARG_INT)
DEFINE_HANDLER(futex, 202, ARG_PTR, ARG_INT, ARG_INT, ARG_PTR, ARG_PTR, ARG_INT)
DEFINE_HANDLER(epoll_create, 213, ARG_INT)
DEFINE_HANDLER(clock_gettime, 228, ARG_INT, ARG_PTR)
DEFINE_HANDLER(epoll_wait, 232, ARG_INT, ARG_PTR, ARG_INT, ARG_INT)
DEFINE_HANDLER(epoll_ctl, 233, ARG_INT, ARG_INT, ARG_INT, ARG_PTR)
DEFINE_HANDLER(unlinkat, 263, ARG_INT, ARG_STR, ARG_INT)
DEFINE_HANDLER(readlinkat, 267, ARG_INT, ARG_STR, ARG_PTR, ARG_INT)
DEFINE_HANDLER(sync_file_range, 277, ARG_INT, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(epoll_create1, 291, ARG_INT)
DEFINE_HANDLER(dup3, 292, ARG_INT, ARG_INT, ARG_INT)
DEFINE_HANDLER(pipe2, 293, ARG_PTR, ARG_INT)
DEFINE_HANDLER(getrandom, 318, ARG_PTR, ARG_INT, ARG_INT)

// dispatcher function
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid,
uint64_t arg1, uint64_t arg1cage,
uint64_t arg2, uint64_t arg2cage,
uint64_t arg3, uint64_t arg3cage,
uint64_t arg4, uint64_t arg4cage,
uint64_t arg5, uint64_t arg5cage,
uint64_t arg6, uint64_t arg6cage) {

if (fn_ptr_uint == 0) {
return -1;
}

syscall_handler_t fn = (syscall_handler_t)(uintptr_t)fn_ptr_uint;

return fn(cageid, arg1, arg1cage, arg2, arg2cage, arg3, arg3cage,
arg4, arg4cage, arg5, arg5cage, arg6, arg6cage);
}
90 changes: 90 additions & 0 deletions examples/strace-grate/src/strace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#ifndef STRACE_H
#define STRACE_H

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <lind_syscall.h>

#define ARG_INT 0
#define ARG_STR 1
#define ARG_PTR 2
#define MAX_SYSCALLS 334

// function ptr for storing syscall handlers
typedef int (*syscall_handler_t)(uint64_t, uint64_t, uint64_t, uint64_t,
uint64_t, uint64_t, uint64_t, uint64_t,
uint64_t, uint64_t, uint64_t, uint64_t, uint64_t);

// table for storing syscall handlers
extern syscall_handler_t syscall_handler_table[MAX_SYSCALLS];

// macro for defining syscall handlers dynamically
#define DEFINE_HANDLER(name, num, ...) \
/* function defination for syscall handler */ \
int name##_grate(uint64_t cageid, uint64_t arg1, uint64_t arg1cage, \
uint64_t arg2, uint64_t arg2cage, \
uint64_t arg3, uint64_t arg3cage, \
uint64_t arg4, uint64_t arg4cage, \
uint64_t arg5, uint64_t arg5cage, \
uint64_t arg6, uint64_t arg6cage) { \
int thiscage = getpid(); \
int types[] = {__VA_ARGS__}; \
int argsnum = sizeof(types) / sizeof(int); \
uint64_t args[] = {arg1, arg2, arg3, arg4, arg5, arg6}; \
uint64_t argcages[] = {arg1cage, arg2cage, arg3cage, arg4cage, \
arg5cage, arg6cage}; \
\
char log_buffer[2048]; \
int offset = 0; \
\
/* log buffer to print syscall with args and ret val */ \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, "%s(", #name); \
\
for (int i = 0; i < argsnum; i++) { \
if (i > 0) \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, ", "); \
\
if (types[i] == ARG_STR && args[i] != 0) { \
char *buf = malloc(256); \
if (buf) { \
copy_data_between_cages(thiscage, argcages[i], \
args[i], argcages[i], \
(uint64_t)buf, thiscage, \
256, 1); \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, \
"\"%s\"", buf); \
free(buf); \
} else { \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, \
"0x%lx", (unsigned long)args[i]); \
} \
} else if (types[i] == ARG_PTR) { \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, \
"0x%lx", (unsigned long)args[i]); \
} else { \
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, \
"%ld", (long)args[i]); \
} \
} \
\
offset += snprintf(log_buffer + offset, sizeof(log_buffer) - offset, ")"); \
\
/* forward interposed syscall */ \
int ret = make_threei_call(num, 0, \
thiscage, arg1cage, \
arg1, arg1cage, arg2, arg2cage, \
arg3, arg3cage, arg4, arg4cage, \
arg5, arg5cage, arg6, arg6cage, 0); \
\
fprintf(stderr, "%s = %d\n", log_buffer, ret); \
return ret; \
} \
\
/* constructor to store handler address in the table */ \
__attribute__((constructor)) static void register_##name() { \
syscall_handler_table[num] = &name##_grate; \
}

#endif
63 changes: 63 additions & 0 deletions examples/strace-grate/src/strace_grate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/mman.h>
#include "strace.h"

int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <cage_binary> [args...]\n", argv[0]);
exit(EXIT_FAILURE);
}

// using semaphores for synchronizing the grate and cage.
//
// this ensures that all the initalization is done by the grate.
sem_t *sem = mmap(NULL, sizeof(*sem), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANON, -1, 0);

int grateid = getpid();
pid_t cageid = fork();

if (cageid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (cageid == 0) {
// wait for grate to register handlers
sem_wait(sem);

if (execv(argv[1], &argv[1]) == -1) {
perror("execv failed");
exit(EXIT_FAILURE);
}
}

// loop to register syscall handlers
for (int i = 0; i < MAX_SYSCALLS; i++) {
if (syscall_handler_table[i] != NULL) {
uint64_t fn_ptr = (uint64_t)(uintptr_t)syscall_handler_table[i];
register_handler(cageid, i, grateid, fn_ptr);
}
}

// resume execution of the cage
sem_post(sem);

int status;
int w;

while (1) {
w = wait(&status);
if (w > 0) {
break;
}
}

sem_destroy(sem);
munmap(sem, sizeof(*sem));

return 0;
}
Loading