- /dev/ptmx and /dev/pts/N: Unix98 pairs with a full line discipline, window sizes, job control and hangup (kernel/dev/pty.c). - The console speaks scroll regions, insert/delete line, SU/SD, RI/IND/NEL, DECSC/DECRC; swallows charset designations and private CSI sequences; and LF only returns the carriage under ONLCR. - setsid() drops the controlling terminal, so a pane's pty becomes its ctty. - pipe2() honours O_NONBLOCK and O_CLOEXEC (libevent's signal pipe). - File modes are stored and enforced: chmod, fchmod(at), umask, creation modes; /tmp is 1777. - /proc/stat has real user/system/idle; /proc/<pid>/task; kthread flag. - SO_PEERCRED, IOV_MAX 1024. - /proc/strace: name a program and its syscalls go to the kernel log. - World gains htop, btop, tmux, mc, ncdu, fastfetch, games, X toys, NetSurf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
225 lines
6.6 KiB
C
225 lines
6.6 KiB
C
#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");
|
|
}
|