A thread is a struct proc that shares three counted objects with others of its group: the address space (struct mm: page tables, heap top, VMAs and a lock for changing them), the descriptor table, and the signal dispositions. clone() shares what its flags say and fork() copies. - clone(): pthread_create's shape (VM, FILES, SIGHAND, THREAD, SETTLS, PARENT_SETTID, CHILD_CLEARTID) and posix_spawn's (VM, VFORK, a stack). - futex: WAIT, WAIT_BITSET with timeouts, WAKE, REQUEUE, CMP_REQUEUE, keyed by the word's kernel address so shared pages meet. set_tid_address keeps the word that exit zeroes and wakes, for pthread_join. - exit ends a thread; exit_group, a fatal signal, or the leader exiting ends them all. reapd frees exited threads; the leader is reaped by its parent once the rest are gone. getpid is the group, gettid the thread. - Stops and resumes reach every thread; other process signals the leader. - exec kills the other threads and unshares the dispositions. - No TLB shootdown, so pages unmapped from a shared address space are freed only once every CPU has reloaded CR3 or is idle. - procfs: /proc lists processes, task/ lists threads, comm, Threads:, Tgid. - The console honours VMIN 0 / VTIME (btop's input drain hung on it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
580 lines
16 KiB
C
580 lines
16 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/memlayout.h"
|
|
#include "core/hwinfo.h"
|
|
#include "arch/mmu.h"
|
|
#include "core/proc.h"
|
|
#include "arch/x86.h"
|
|
#include "core/syscall.h"
|
|
#include "uapi/posix.h"
|
|
#include "core/uaccess.h"
|
|
#include "uapi/errno.h"
|
|
#include "dev/acpi.h"
|
|
#include "fs/fat.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/buf.h"
|
|
|
|
addr_t sys_fork(void) { return fork(); }
|
|
addr_t sys_wait(void) { return wait(); }
|
|
|
|
// _exit(status)/exit_group(status): the low 8 bits of the argument are what
|
|
// wait() reports back through WEXITSTATUS.
|
|
addr_t
|
|
sys_exit(void)
|
|
{
|
|
int code = 0;
|
|
argint(0, &code);
|
|
// Linux's exit is one thread's: pthread_exit, and a thread returning
|
|
// from its start function. The program ends with exit_group -- or here,
|
|
// when this is the last thread, or the native ABI, which has no threads.
|
|
struct proc *p = myproc();
|
|
if (p->syscall_map && p->tgid != p->pid && proc_thread_count(p->tgid) > 1)
|
|
thread_exit();
|
|
exit(W_EXITCODE(code, 0));
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_exit_group(void)
|
|
{
|
|
int code = 0;
|
|
argint(0, &code);
|
|
exit(W_EXITCODE(code, 0));
|
|
return 0;
|
|
}
|
|
|
|
|
|
addr_t
|
|
sys_kill(void)
|
|
{
|
|
int pid, sig;
|
|
argint(0, &pid);
|
|
argint(1, &sig);
|
|
|
|
// POSIX target selection:
|
|
// pid > 0 -> that process
|
|
// pid == 0 -> every process in the caller's own group
|
|
// pid == -1 -> every process we may signal (approximated as our group)
|
|
// pid < -1 -> the process group with id -pid
|
|
if (pid == 0 || pid == -1)
|
|
return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0;
|
|
if (pid < -1)
|
|
return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0;
|
|
|
|
// sig == 0: existence check only
|
|
if (sig == 0)
|
|
return proc_exists(pid) ? 0 : -ESRCH;
|
|
|
|
// Raise the signal for real: pending bit + wakeup, handler delivery on
|
|
// the target's next syscall boundary, default action if unhandled.
|
|
return sigsend(pid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
// Defined with getrusage() further down; wait4() needs it too.
|
|
static void rusage_fill(struct rusage *ru, uint64 ut, uint64 st);
|
|
|
|
// A process is its thread group: getpid is the group's id, gettid the thread's.
|
|
addr_t sys_getpid(void) { return myproc()->tgid; }
|
|
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->tgid : 1; }
|
|
addr_t sys_gettid(void) { return myproc()->pid; }
|
|
|
|
addr_t
|
|
sys_waitpid(void)
|
|
{
|
|
int pid, options;
|
|
addr_t statusp;
|
|
// status is optional (NULL is standard, valid usage — waitpid(pid, NULL,
|
|
// WNOHANG) — so this must NOT go through argptr(), which rejects a NULL/
|
|
// unmapped pointer outright).
|
|
if (argint(0, &pid) < 0 || argaddr(1, &statusp) < 0 || argint(2, &options) < 0)
|
|
return -1;
|
|
int status;
|
|
int wpid = waitpid(pid, &status, options, 0, 0);
|
|
if (wpid > 0 && statusp)
|
|
put_user(status, statusp);
|
|
return wpid;
|
|
}
|
|
|
|
// wait4(2) -- and its fourth argument is a `struct rusage *`.
|
|
//
|
|
// It was read off the stack and thrown away. So a caller that passed a buffer
|
|
// got it back untouched, and read whatever had been there before: zsh's
|
|
// `time` reported 16777216.01s user for every command, which is 2^24 seconds,
|
|
// which is uninitialised memory that happened to look like a number. The
|
|
// figure did not vary with the workload because it was never written.
|
|
//
|
|
// wait3() is the same syscall with pid -1, and it is the one shells actually
|
|
// call, so this is the path every `time` goes through.
|
|
addr_t
|
|
sys_wait4(void)
|
|
{
|
|
int pid, options;
|
|
addr_t statusp, rup;
|
|
uint64 ut = 0, st = 0;
|
|
|
|
argint(0, &pid);
|
|
argaddr(1, &statusp);
|
|
argint(2, &options);
|
|
argaddr(3, &rup);
|
|
|
|
int status;
|
|
int wpid = waitpid(pid, &status, options, &ut, &st);
|
|
if (wpid <= 0)
|
|
return wpid;
|
|
if (statusp)
|
|
put_user(status, statusp);
|
|
if (rup) {
|
|
struct rusage *ru = (struct rusage*)user_ptr(rup, sizeof(*ru));
|
|
if (ru)
|
|
rusage_fill(ru, ut, st);
|
|
}
|
|
return wpid;
|
|
}
|
|
|
|
addr_t
|
|
sys_clone(void)
|
|
{
|
|
uint64 flags, stack, ptid, ctid, tls;
|
|
argaddr(0, &flags);
|
|
argaddr(1, &stack);
|
|
argaddr(2, &ptid);
|
|
argaddr(3, &ctid);
|
|
argaddr(4, &tls);
|
|
return clone_proc(flags, stack, ptid, ctid, tls);
|
|
}
|
|
|
|
addr_t
|
|
sys_sleep(void)
|
|
{
|
|
int n;
|
|
uint ticks0;
|
|
if (argint(0, &n) < 0)
|
|
return -1;
|
|
acquire(&tickslock);
|
|
ticks0 = ticks;
|
|
while (ticks - ticks0 < (uint)n) {
|
|
if (myproc()->killed) { release(&tickslock); return -1; }
|
|
sleep(&ticks, &tickslock);
|
|
}
|
|
release(&tickslock);
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_uptime(void)
|
|
{
|
|
uint xticks;
|
|
acquire(&tickslock);
|
|
xticks = ticks;
|
|
release(&tickslock);
|
|
return xticks;
|
|
}
|
|
|
|
addr_t sys_reboot(void)
|
|
{
|
|
cprintf("System is rebooting...\n");
|
|
// A stick left mounted should come back readable, and marked clean.
|
|
schfs_ksync_all();
|
|
fat_ksync_all(1);
|
|
// The FADT's reset register first: it is the one the board's designer
|
|
// wired up. Then the PCI reset port and the keyboard controller, which
|
|
// are what nearly every PC has had since there were PCs.
|
|
acpi_reboot();
|
|
outb(0xCF9, 0x06);
|
|
outb(0x64, 0xFE);
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_shutdown(void)
|
|
{
|
|
cprintf("System is shutting down...\n");
|
|
// S5, through the firmware's own PM1 registers and its _PTS. The port
|
|
// writes after it are QEMU's, Bochs' and VirtualBox's fixed addresses,
|
|
// kept for a machine whose AML did not load; a real board answers none
|
|
// of them.
|
|
schfs_ksync_all();
|
|
fat_ksync_all(1);
|
|
acpi_poweroff();
|
|
outw(0x604, 0x2000);
|
|
outw(0xB004, 0x2000);
|
|
outw(0x4004, 0x3400);
|
|
return 0;
|
|
}
|
|
|
|
// --- Sessions and process groups ---------------------------------------
|
|
// Job control is defined entirely in terms of these. They used to be stubs
|
|
// returning the caller's own pid, which made every process look like its own
|
|
// group leader and left a shell's wait loop unable to make progress.
|
|
|
|
addr_t sys_setsid(void) { return proc_setsid(); }
|
|
|
|
addr_t
|
|
sys_getsid(void)
|
|
{
|
|
int pid;
|
|
argint(0, &pid);
|
|
if (pid == 0 || pid == myproc()->pid)
|
|
return myproc()->sid;
|
|
return proc_exists(pid) ? (addr_t)myproc()->sid : (addr_t)-ESRCH;
|
|
}
|
|
|
|
addr_t
|
|
sys_getpgid(void)
|
|
{
|
|
int pid;
|
|
argint(0, &pid);
|
|
if (pid == 0 || pid == myproc()->pid)
|
|
return myproc()->pgid;
|
|
int g = proc_pgid(pid);
|
|
return g < 0 ? (addr_t)-ESRCH : (addr_t)g;
|
|
}
|
|
|
|
addr_t
|
|
sys_setpgid(void)
|
|
{
|
|
int pid, pgid;
|
|
argint(0, &pid);
|
|
argint(1, &pgid);
|
|
return proc_setpgid(pid, pgid);
|
|
}
|
|
|
|
addr_t sys_getpgrp(void) { return myproc()->pgid; }
|
|
|
|
// --- User and group identity -------------------------------------------
|
|
// Tracked for real now. Nothing *enforces* permissions with these yet -- the
|
|
// on-disk format has no owner field until SchFS -- but they are no longer
|
|
// invented, so getuid() after setuid() reports what actually happened.
|
|
|
|
addr_t sys_getuid(void) { return myproc()->uid; }
|
|
addr_t sys_geteuid(void) { return myproc()->euid; }
|
|
addr_t sys_getgid(void) { return myproc()->gid; }
|
|
addr_t sys_getegid(void) { return myproc()->egid; }
|
|
|
|
// Privileged (euid 0) callers may set any id; an unprivileged caller may only
|
|
// switch between its real and saved ids. Standard POSIX rule.
|
|
addr_t
|
|
sys_setuid(void)
|
|
{
|
|
int uid;
|
|
argint(0, &uid);
|
|
if (uid < 0)
|
|
return -EINVAL;
|
|
if (myproc()->euid == 0) {
|
|
myproc()->uid = myproc()->euid = myproc()->suid = (uint)uid;
|
|
return 0;
|
|
}
|
|
if ((uint)uid != myproc()->uid && (uint)uid != myproc()->suid)
|
|
return -EPERM;
|
|
myproc()->euid = (uint)uid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setgid(void)
|
|
{
|
|
int gid;
|
|
argint(0, &gid);
|
|
if (gid < 0)
|
|
return -EINVAL;
|
|
if (myproc()->euid == 0) {
|
|
myproc()->gid = myproc()->egid = myproc()->sgid = (uint)gid;
|
|
return 0;
|
|
}
|
|
if ((uint)gid != myproc()->gid && (uint)gid != myproc()->sgid)
|
|
return -EPERM;
|
|
myproc()->egid = (uint)gid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_umask(void)
|
|
{
|
|
int mask;
|
|
argint(0, &mask);
|
|
uint old = myproc()->umask;
|
|
myproc()->umask = (uint)mask & 0777;
|
|
return old;
|
|
}
|
|
|
|
addr_t
|
|
sys_setreuid(void)
|
|
{
|
|
int ruid, euid;
|
|
argint(0, &ruid); argint(1, &euid);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (ruid >= 0) myproc()->uid = (uint)ruid;
|
|
if (euid >= 0) myproc()->euid = (uint)euid;
|
|
myproc()->suid = myproc()->euid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setregid(void)
|
|
{
|
|
int rgid, egid;
|
|
argint(0, &rgid); argint(1, &egid);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (rgid >= 0) myproc()->gid = (uint)rgid;
|
|
if (egid >= 0) myproc()->egid = (uint)egid;
|
|
myproc()->sgid = myproc()->egid;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setresuid(void)
|
|
{
|
|
int r, e, sv;
|
|
argint(0, &r); argint(1, &e); argint(2, &sv);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (r >= 0) myproc()->uid = (uint)r;
|
|
if (e >= 0) myproc()->euid = (uint)e;
|
|
if (sv >= 0) myproc()->suid = (uint)sv;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_setresgid(void)
|
|
{
|
|
int r, e, sv;
|
|
argint(0, &r); argint(1, &e); argint(2, &sv);
|
|
if (myproc()->euid != 0)
|
|
return -EPERM;
|
|
if (r >= 0) myproc()->gid = (uint)r;
|
|
if (e >= 0) myproc()->egid = (uint)e;
|
|
if (sv >= 0) myproc()->sgid = (uint)sv;
|
|
return 0;
|
|
}
|
|
|
|
// Linux-specific; the fs id follows the effective id here.
|
|
addr_t sys_setfsuid(void) { return myproc()->euid; }
|
|
addr_t sys_setfsgid(void) { return myproc()->egid; }
|
|
|
|
addr_t
|
|
sys_getresuid(void)
|
|
{
|
|
addr_t rp, ep, sp;
|
|
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
|
if (rp) put_user(myproc()->uid, rp);
|
|
if (ep) put_user(myproc()->euid, ep);
|
|
if (sp) put_user(myproc()->suid, sp);
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_getresgid(void)
|
|
{
|
|
addr_t rp, ep, sp;
|
|
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
|
if (rp) put_user(myproc()->gid, rp);
|
|
if (ep) put_user(myproc()->egid, ep);
|
|
if (sp) put_user(myproc()->sgid, sp);
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_getgroups(void)
|
|
{
|
|
int size;
|
|
argint(0, &size);
|
|
return 0; // no supplementary groups
|
|
}
|
|
addr_t sys_setgroups(void) { return 0; }
|
|
|
|
// --- Process/scheduling extensions ---
|
|
|
|
// A real yield. It returned 0 without giving anything up, which cost nothing
|
|
// while every timer tick was a yield anyway; with the fair scheduler a tick
|
|
// only preempts once the slice is spent, so a spin-and-yield loop (musl's
|
|
// locks, a polling driver in userland) would hold the CPU for all of it.
|
|
addr_t sys_sched_yield(void) { yield(); return 0; }
|
|
|
|
// Every processor that started, since nothing here pins a process anywhere.
|
|
//
|
|
// This wrote "CPU 0" and returned 0 -- and the return value is the number of
|
|
// bytes of mask the kernel wrote, which musl uses to clear the rest. Zero
|
|
// meant all of it, so the mask came back empty and nproc, make -j and every
|
|
// thread pool sized from sysconf(_SC_NPROCESSORS_ONLN) saw one processor.
|
|
addr_t sys_sched_getaffinity(void)
|
|
{
|
|
int pid, cpusetsize;
|
|
addr_t maskp;
|
|
uchar m[(NCPU + 63) / 64 * 8];
|
|
argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp);
|
|
if (cpusetsize < (int)sizeof m || (cpusetsize & 7))
|
|
return -EINVAL;
|
|
memset(m, 0, sizeof m);
|
|
for (int i = 0; i < ncpu && i < NCPU; i++)
|
|
if (cpus[i].started)
|
|
m[i / 8] |= 1 << (i % 8);
|
|
if (copy_to_user(maskp, m, sizeof m) < 0)
|
|
return -EFAULT;
|
|
return sizeof m;
|
|
}
|
|
addr_t sys_sched_setaffinity(void) { return 0; }
|
|
|
|
addr_t sys_prctl(void)
|
|
{
|
|
int op;
|
|
addr_t arg2;
|
|
argint(0, &op);
|
|
argaddr(1, &arg2);
|
|
if (op == PR_SET_NAME) {
|
|
char *name;
|
|
if (argstr(1, &name) < 0) return -1;
|
|
safestrcpy(myproc()->name, name, sizeof(myproc()->name));
|
|
return 0;
|
|
}
|
|
if (op == PR_GET_NAME) {
|
|
char *buf = (char*)user_ptr(arg2, 16);
|
|
if (!buf) return -EFAULT;
|
|
safestrcpy(buf, myproc()->name, 16);
|
|
return 0;
|
|
}
|
|
return 0; // all other prctl ops: silently succeed
|
|
}
|
|
|
|
addr_t sys_tgkill(void)
|
|
{
|
|
int tgid, tid, sig;
|
|
argint(0, &tgid); argint(1, &tid); argint(2, &sig);
|
|
// To one thread: musl's pthread_kill, and raise(), which is tgkill on
|
|
// the calling thread.
|
|
if (tid <= 0) return -EINVAL;
|
|
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
|
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
addr_t sys_tkill(void)
|
|
{
|
|
int tid, sig;
|
|
argint(0, &tid); argint(1, &sig);
|
|
if (tid <= 0) return 0;
|
|
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
|
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
|
}
|
|
|
|
addr_t sys_pause(void)
|
|
{
|
|
// Sleep until next timer tick and return -EINTR (no real signal delivery).
|
|
acquire(&tickslock);
|
|
uint t0 = ticks;
|
|
while (ticks == t0 && !myproc()->killed)
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
return -EINTR;
|
|
}
|
|
|
|
// getrusage(2).
|
|
//
|
|
// This used to ignore `who` entirely and report the machine's *uptime* as the
|
|
// caller's user time. Every process of every age answered with the same
|
|
// number, and RUSAGE_CHILDREN answered with it too -- so a shell timing a
|
|
// command got the age of the machine, and `time` on a pipeline was
|
|
// meaningless rather than merely imprecise.
|
|
//
|
|
// Ticks, converted at the boundary. The kernel counts whole timer ticks
|
|
// because that is what the sampling gives it; microseconds are what the
|
|
// interface asks for, so the multiply happens here rather than pretending to a
|
|
// resolution the counter does not have.
|
|
static void
|
|
rusage_fill(struct rusage *ru, uint64 ut, uint64 st)
|
|
{
|
|
memset(ru, 0, sizeof(*ru));
|
|
ru->ru_utime.tv_sec = (long)(ut / HZ);
|
|
ru->ru_utime.tv_usec = (long)((ut % HZ) * (1000000 / HZ));
|
|
ru->ru_stime.tv_sec = (long)(st / HZ);
|
|
ru->ru_stime.tv_usec = (long)((st % HZ) * (1000000 / HZ));
|
|
}
|
|
|
|
addr_t sys_getrusage(void)
|
|
{
|
|
int who;
|
|
struct rusage *ru;
|
|
argint(0, &who);
|
|
if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -EFAULT;
|
|
|
|
switch (who) {
|
|
case RUSAGE_SELF:
|
|
rusage_fill(ru, myproc()->utime, myproc()->stime);
|
|
return 0;
|
|
case RUSAGE_CHILDREN:
|
|
rusage_fill(ru, myproc()->cutime, myproc()->cstime);
|
|
return 0;
|
|
default:
|
|
// RUSAGE_THREAD is -1 and would be RUSAGE_SELF here, since a process
|
|
// has exactly one thread. It is refused rather than aliased: answering
|
|
// it would imply threads exist.
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
addr_t sys_times(void)
|
|
{
|
|
addr_t bufaddr;
|
|
argaddr(0, &bufaddr);
|
|
// The tms fields are in clock ticks, which is what the kernel counts, so
|
|
// this one needs no conversion -- USER_HZ and HZ are both 100. They used
|
|
// to be memset to zero, which is why `times()` agreed with nothing.
|
|
if (bufaddr) {
|
|
struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t));
|
|
if (t) {
|
|
t->tms_utime = (long)myproc()->utime;
|
|
t->tms_stime = (long)myproc()->stime;
|
|
t->tms_cutime = (long)myproc()->cutime;
|
|
t->tms_cstime = (long)myproc()->cstime;
|
|
}
|
|
}
|
|
// USER_HZ units, measured from the TSC.
|
|
//
|
|
// musl answers sysconf(_SC_CLK_TCK) with 100 unconditionally, so a shell's
|
|
// `time` builtin divides whatever this returns by 100. Deriving it from
|
|
// the tick count made `time` under-report whenever ticks were being lost
|
|
// -- which is how a 20-second command came to print 3.12s.
|
|
uint64 tsec, tusec;
|
|
monotonic(&tsec, &tusec);
|
|
return (addr_t)(tsec * USER_HZ + (tusec * USER_HZ) / 1000000);
|
|
}
|
|
|
|
addr_t sys_sysinfo(void)
|
|
{
|
|
addr_t bufaddr;
|
|
argaddr(0, &bufaddr);
|
|
struct sysinfo *si = (struct sysinfo*)user_ptr(bufaddr, sizeof(*si));
|
|
if (!si) return -EFAULT;
|
|
memset(si, 0, sizeof(*si));
|
|
// TSC, like everything else that reports elapsed time.
|
|
uint64 upsec, upusec;
|
|
monotonic(&upsec, &upusec);
|
|
si->uptime = (long)upsec;
|
|
// Real values: the RAM the page allocator was given, and how much of it is
|
|
// still on the free list. These used to be the constants 256MB/128MB, and
|
|
// then PHYSTOP, an address, which counted the PCI hole as memory.
|
|
si->totalram = ram_total_bytes();
|
|
si->freeram = kalloc_free_bytes();
|
|
// The buffer cache, so free(1) does not count it as used by nothing.
|
|
uint nbuf, maxbuf;
|
|
bcache_size(&nbuf, &maxbuf);
|
|
si->bufferram = (uint64)nbuf * BSIZE;
|
|
si->mem_unit = 1;
|
|
si->procs = (uint16)proc_num();
|
|
return 0;
|
|
}
|
|
|
|
addr_t sys_waitid(void)
|
|
{
|
|
// Simplified: wait for any child, ignore idtype/id/options.
|
|
int wpid = wait();
|
|
if (wpid < 0) return -ECHILD;
|
|
// siginfo_t pointer (arg 2) — zero-fill if provided.
|
|
addr_t infop;
|
|
argaddr(2, &infop);
|
|
if (infop) {
|
|
char *p = (char*)user_ptr(infop, 128); // sizeof(siginfo_t)
|
|
if (p) memset(p, 0, 128);
|
|
}
|
|
return 0;
|
|
}
|