#include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "arch/traps.h" #include "core/proc.h" #include "arch/x86.h" #include "core/syscall.h" #include "core/uaccess.h" #include "uapi/errno.h" #include "uapi/posix.h" // ----------------------------------------------------------------------- // Argument helpers — read syscall args from the saved register frame. // x86-64 SysV ABI: rdi, rsi, rdx, r10, r8, r9 // ----------------------------------------------------------------------- // Note: address validation goes through uaccess.h (page-table presence + // user bit on every page of the range). There is deliberately no // `addr < myproc()->sz` check: that would assume every valid user pointer // lives in the heap — false for stack addresses and mmap()'d buffers // (both live above myproc()->sz), which made syscalls fail whenever a // Linux-ABI program passed a string/pointer from either region. // // Validated pointers are returned as raw user VAs — the process page // table is live in kernel mode, so they dereference correctly across // page boundaries (unlike a uva2ka() alias, which is only good within // the one page it translated). int fetchint(addr_t addr, int *ip) { return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0; } int fetchaddr(addr_t addr, addr_t *ip) { return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0; } int fetchstr(addr_t addr, char **pp) { long len = strlen_user(addr); if (len < 0) return -1; *pp = (char*)addr; return len; } // /proc/strace. Which argument of a Linux syscall is a path, by number, so // the trace can show the name rather than an address: that is most of what // one wants from a trace of a program that will not start. static int path_arg(uint64 nr) { switch (nr) { case 2: case 4: case 6: case 21: case 59: case 76: case 80: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 92: case 137: case 161: return 0; case 257: case 258: case 260: case 262: case 263: case 264: case 267: case 268: case 269: case 280: case 316: case 332: case 437: case 439: return 1; } return -1; } static void strace_call(struct trapframe *tf); static addr_t fetcharg(int n) { switch (n) { case 0: return myproc()->tf->rdi; case 1: return myproc()->tf->rsi; case 2: return myproc()->tf->rdx; case 3: return myproc()->tf->r10; case 4: return myproc()->tf->r8; case 5: return myproc()->tf->r9; } panic("fetcharg: bad n"); return 0; } int argint(int n, int *ip) { *ip = (int)fetcharg(n); return 0; } int argaddr(int n, addr_t *ip) { *ip = fetcharg(n); return 0; } int argptr(int n, char **pp, int size) { addr_t i; if (argaddr(n, &i) < 0) return -1; if (size < 0) return -1; char *p = user_ptr(i, (uint64)size); if (!p) return -1; *pp = p; return 0; } int argstr(int n, char **pp) { addr_t addr; if (argaddr(n, &addr) < 0) return -1; return fetchstr(addr, pp); } // ----------------------------------------------------------------------- // Dispatch table — auto-generated from table.inc // ----------------------------------------------------------------------- #define SYSCALL(nr, lnr, name) extern addr_t sys_##name(void); #define SYSCALL_ALIAS(lnr, name) #include "table.inc" #undef SYSCALL #undef SYSCALL_ALIAS static addr_t (*syscalls[NSYSCALLS])(void) = { #define SYSCALL(nr, lnr, name) [SYS_##name] = sys_##name, #define SYSCALL_ALIAS(lnr, name) #include "table.inc" #undef SYSCALL #undef SYSCALL_ALIAS }; // ----------------------------------------------------------------------- // Main entry point called from trap.c // ----------------------------------------------------------------------- void syscall(struct trapframe *tf) { if (myproc()->strace) { strace_call(tf); return; } kstack_check_canary("syscall entry"); myproc()->tf = tf; uint64 orig = myproc()->tf->rax; uint64 num = orig; if (myproc()->syscall_map) { num = (num < (uint64)myproc()->map_size) ? myproc()->syscall_map[num] : 0; } if (num > 0 && num < NSYSCALLS && syscalls[num]) { tf->rax = syscalls[num](); } else { tf->rax = -ENOSYS; } if (myproc()->killed) exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL)); deliver_signals(tf); kstack_check_canary("syscall exit"); } // syscall() for a traced process: the same dispatch, with a line in the log // for each call -- the Linux number, four arguments, a path where there is // one, and what came back. The path is copied before the call runs, since // execve replaces the memory it is in. static void strace_call(struct trapframe *tf) { struct proc *p = myproc(); uint64 orig = tf->rax, num = orig; uint64 a0 = tf->rdi, a1 = tf->rsi, a2 = tf->rdx, a3 = tf->r10; char path[64]; int pa = p->syscall_map ? path_arg(orig) : -1; path[0] = 0; if (pa >= 0) { addr_t u = pa == 0 ? a0 : a1; int i; for (i = 0; i < (int)sizeof(path) - 1; i++) { char c; if (copy_from_user(&c, u + i, 1) < 0 || c == 0) break; path[i] = c; } path[i] = 0; } // The result line is written when the call returns, so a call that never // does would leave no trace at all. Everything that can wait gets a line // on the way in as well. switch (orig) { case 0: case 2: case 7: case 23: case 35: case 43: case 45: case 47: case 59: case 61: case 202: case 232: case 257: case 270: case 271: case 281: case 288: if (p->syscall_map) kinfo("strace %d %s: %d(%lx, %lx, %lx, %lx)%s%s%s ...\n", p->pid, p->name, (int)orig, a0, a1, a2, a3, pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : ""); } kstack_check_canary("syscall entry"); p->tf = tf; if (p->syscall_map) num = (num < (uint64)p->map_size) ? p->syscall_map[num] : 0; if (num > 0 && num < NSYSCALLS && syscalls[num]) tf->rax = syscalls[num](); else tf->rax = -ENOSYS; // A successful execve has replaced the program: its name is the new one. kinfo("strace %d %s: %s%d(%lx, %lx, %lx, %lx)%s%s%s = %ld\n", p->pid, p->name, p->syscall_map ? "" : "native ", (int)orig, a0, a1, a2, a3, pa >= 0 ? " \"" : "", path, pa >= 0 ? "\"" : "", (long)tf->rax); if (p->killed) exit(W_EXITCODE(0, p->killsig ? p->killsig : SIGKILL)); deliver_signals(tf); kstack_check_canary("syscall exit"); }