/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2026 Open-IOV contributors */ /* ptrace_ioctl.c - follow a child with PTRACE_SYSCALL and dump the buffer * results of selected UVM ioctls. Needed because libcuda issues raw syscall * instructions (LD_PRELOAD/ltrace never see these). * * Build (guest): gcc -O1 -w -o /tmp/ptio ptrace_ioctl.c * Run: /tmp/ptio /tmp/cdt */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define SYS_IOCTL_X86_64 16 struct syscall_info { unsigned char op; unsigned char pad[3]; unsigned int arch; unsigned long long instruction_pointer; unsigned long long stack_pointer; union { struct { unsigned long long nr; unsigned long long args[6]; } entry; struct { long long rval; unsigned char is_error; } exit; } u; }; static int interesting(unsigned long req) { switch (req) { case 0x1: /* UVM_RESERVE_VA */ case 0x2: /* UVM_RELEASE_VA */ case 0x25: /* UVM_REGISTER_GPU */ case 0x27: /* UVM_PAGEABLE_MEM_ACCESS */ case 0x17: /* UVM_EVENT_CTRL */ case 0x19: /* UVM_REGISTER_MPS_CLIENT */ return 1; default: return 0; } } static void dump_words(pid_t pid, unsigned long addr, int nwords) { int i; fprintf(stderr, " buffer @%#lx:", addr); for (i = 0; i < nwords; i++) { errno = 0; long v = ptrace(PTRACE_PEEKDATA, pid, addr + i * 8, NULL); if (errno) break; fprintf(stderr, " %016lx", (unsigned long)v); } fprintf(stderr, "\n"); } struct saved { pid_t pid; int used; unsigned long req, arg; } saved[64]; static struct saved *slot(pid_t pid) { int i, free_i = -1; for (i = 0; i < 64; i++) { if (saved[i].used && saved[i].pid == pid) return &saved[i]; if (!saved[i].used && free_i < 0) free_i = i; } if (free_i >= 0) { saved[free_i].used = 1; saved[free_i].pid = pid; saved[free_i].req = 0; return &saved[free_i]; } return NULL; } int main(int argc, char **argv) { pid_t pid; int nprint = 0; if (argc < 2) { fprintf(stderr, "usage: %s prog [args...]\n", argv[0]); return 2; } pid = fork(); if (pid == 0) { ptrace(PTRACE_TRACEME, 0, 0, 0); raise(SIGSTOP); execvp(argv[1], &argv[1]); perror("execvp"); _exit(127); } waitpid(pid, NULL, WUNTRACED); ptrace(PTRACE_SETOPTIONS, pid, 0, (void *)(PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_EXITKILL)); ptrace(PTRACE_SYSCALL, pid, 0, 0); fprintf(stderr, "[ptio] attached to pid %d\n", pid); for (;;) { int status; pid_t p = waitpid(-1, &status, __WALL); if (p < 0) { if (errno == EINTR) continue; break; } if (WIFEXITED(status) || WIFSIGNALED(status)) continue; if (!WIFSTOPPED(status)) continue; { int sig = WSTOPSIG(status); struct syscall_info si; memset(&si, 0, sizeof(si)); if ((sig == (SIGTRAP | 0x80)) && ptrace(PTRACE_GET_SYSCALL_INFO, p, sizeof(si), &si) > 0) { struct saved *sv = slot(p); if (si.op == 1 /* entry */) { if (sv && si.u.entry.nr == SYS_IOCTL_X86_64) { sv->req = si.u.entry.args[1]; sv->arg = si.u.entry.args[2]; if (sv->req == 0x25) { fprintf(stderr, "[ptio] ENTER UVM_REGISTER_GPU pid=%d\n", p); dump_words(p, sv->arg, 6); fprintf(stderr, " ^ IN: uuid(2q) | numaEnabled numaNodeId | rmCtrlFd hClient | hSmcPartRef rmStatus\n"); } } else if (sv) { sv->req = 0; } } else if (si.op == 2 /* exit */ && sv && ((sv->req & 0xff) == 0x4629ul || ((sv->req & 0xffff00fful) == 0x0046002bul) || ((sv->req & 0xffff00fful) == 0x0046002aul))) { unsigned int own = (unsigned int)(sv->req & 0xff); long q0 = 0, q1 = 0, q2 = 0, q3 = 0; unsigned long st = 0; errno = 0; q0 = ptrace(PTRACE_PEEKDATA, p, sv->arg, NULL); errno = 0; q1 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 8, NULL); errno = 0; q2 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 16, NULL); errno = 0; q3 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 24, NULL); if (own == 0x29) st = (unsigned long)((q1 >> 32) & 0xfffffffful); else st = (unsigned long)((q3 >> 32) & 0xfffffffful); if (st != 0) fprintf(stderr, "[rmio] pid=%d nr=0x%x status=0x%lx hClient=0x%lx hObj=0x%lx cmd/class=0x%lx size=%lu\n", p, own, st, (unsigned long)(q0 & 0xfffffffful), (unsigned long)((q0 >> 32) & 0xfffffffful), (unsigned long)(q1 & 0xfffffffful), (unsigned long)((q3 >> 32) & 0xfffffffful)); } else if (si.op == 2 /* exit */ && sv && sv->req == 0xc020462aul) { long q0, q1, q2, q3; errno = 0; q1 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 8, NULL); errno = 0; q2 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 16, NULL); errno = 0; q3 = ptrace(PTRACE_PEEKDATA, p, sv->arg + 24, NULL); q0 = ptrace(PTRACE_PEEKDATA, p, sv->arg, NULL); (void)q0; fprintf(stderr, "[rmctl] pid=%d cmd=0x%lx hClient=0x%lx params=%#lx size=%lu status=0x%lx\n", p, (unsigned long)(q1 & 0xfffffffful), (unsigned long)(q0 & 0xfffffffful), (unsigned long)(q2 & 0xfffffffffffful), (unsigned long)((q2 >> 32) & 0xfffffffful), (unsigned long)((q3 >> 32) & 0xfffffffful)); if (((q3 >> 32) & 0xfffffffful) != 0 && (q2 & 0xfffffffffffful) > 0x10000) { fprintf(stderr, " params:"); { int i2; for (i2 = 0; i2 < 6; i2++) { errno = 0; long v = ptrace(PTRACE_PEEKDATA, p, (unsigned long)(q2 & 0xfffffffffffful) + i2 * 8, NULL); if (errno) break; fprintf(stderr, " %016lx", (unsigned long)v); } } fprintf(stderr, "\n"); } } else if (si.op == 2 /* exit */ && sv && interesting(sv->req)) { fprintf(stderr, "[ptio] pid=%d ioctl req=0x%lx arg=%#lx ret=%ld%s\n", p, sv->req, sv->arg, si.u.exit.rval, interesting(sv->req) ? " *" : ""); if (sv->req == 0x25) { dump_words(p, sv->arg, 6); fprintf(stderr, " ^ UVM_REGISTER_GPU: uuid(2q) | numaEnabled numaNodeId rmCtrlFd hClient | hSmcPartRef rmStatus\n"); } else { dump_words(p, sv->arg, 4); } } } ptrace(PTRACE_SYSCALL, p, 0, sig == (SIGTRAP | 0x80) ? 0 : (sig == SIGTRAP ? 0 : sig)); } } return 0; }