Strange behavior about std.os.linux.ptrace

Hi, I want to rewrite the following C code in Zig.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>

int main() {
    pid_t child ;
    long orig_eax;
    struct user_regs_struct regs;

    printf("Enter the PID of the process to trace: ");
    scanf("%d", &child);

    int status;
    if (ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1) {
        perror("ptrace");
        exit(1);
    }

    printf("Tracing process %d...\n", child);

    while (1) {
        wait(&status);
        if (WIFEXITED(status)) {
            printf("Child process %d exited.\n", child);
            break;
        }

        orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);

        if (orig_eax == -1) {
            perror("ptrace");
            break;
        }

        printf("Syscall number: %ld\n", orig_eax);

        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
    }

    return 0;
}

However, I noticed that no matter what, my std.os.linux.ptrace(PEEKUSER... return value is always -3 (18446744073709551613).

const std = @import("std");

pub fn main() !void {
    var iter = std.process.args();
    _ = iter.skip();
    var pid = try std.fmt.parseInt(
        std.os.pid_t,
        iter.next() orelse @panic("no pid"),
        10,
    );

    try std.os.ptrace(
        std.os.linux.PTRACE.ATTACH,
        pid,
        0,
        0,
    );
    std.log.info("Tracing process {d}...\n", .{pid});

    while (true) {
        const orig_eax = std.os.linux.ptrace(std.os.linux.PTRACE.PEEKUSER, pid, 8 * std.os.linux.REG.RAX, 0, 0);

        std.log.info("Syscall number: {d}\n", .{@as(i32, @truncate(@as(i64, @bitCast(orig_eax))))});

        _ = std.os.linux.ptrace(std.os.linux.PTRACE.SYSCALL, pid, 0, 0, 0);
    }
}

I searched through Zig’s GitHub issues and didn’t find anything similar. Did I make any mistakes in my code?

My env, zig 0.12.0-dev.888+130227491

Does strace reveal anything interesting about the syscalls that are used?

This reminded me my very old experience :slight_smile:
Students uploaded C-code to a server, which checked the correctness of computations
(numeric methods or so, Runge-Kutta and alike)

A server compiled C-source and then run the executable, but under very-very strong super vision, performed by a program, which I called ‘SCUT’ (from ‘system call cut’) - that scut interrupted a program that tried dangerous (in some sense) syscalls.

Unfortunately, I can not find the source of that scut for the moment, but if I will, I’ll post it :slight_smile:

1 Like

Unless I’m missing something, the Zig code that uses std.os.linux.ptrace is doing a syscall directly while the C code is going through libc. Looking at the musl implementation of ptrace, it seems like you’d need to get the result from the pointer provided as the data param of the syscall.

(this type of thing is probably something the std.os.ptrace API might want to support)

You probably also want to use std.os.errno on the return from std.os.linux.ptrace to convert it into an error enum.

1 Like

I checked the value of std.os.linux.PTRACE.PEEKUSER, and it’s 2. So I tried using an extra variable result as the data parameter.

        var result: i32 = 0;
        if (std.os.linux.PTRACE.PEEKUSER - 1 < 3) {
            const ret = std.os.linux.ptrace(std.os.linux.PTRACE.PEEKUSER, pid, 8 * std.os.REG.RAX, @intFromPtr(&result), 0);
            std.log.info("Syscall number: i32 {d} u32 {d}, ret is {d}", .{ result, @as(u32, @bitCast(result)), ret });
        }

I got following output when trace a simple while sleep program

import time
import os

print(os.getpid())

while True:
    time.sleep(3)

info: Tracing process 202378...

info: Syscall number: i32 1 u32 1, ret is 0
info: Syscall number: i32 1 u32 1, ret is 0
info: Syscall number: i32 1 u32 1, ret is 0
info: Syscall number: i32 1725136512 u32 1725136512, ret is 0
info: Syscall number: i32 1725136512 u32 1725136512, ret is 0
info: Syscall number: i32 1 u32 1, ret is 0
info: Syscall number: i32 1 u32 1, ret is 0
info: Syscall number: i32 1725136512 u32 1725136512, ret is 0
info: Syscall number: i32 1725136512 u32 1725136512, ret is 0
info: Syscall number: i32 1 u32 1, ret is 0

In the C program, the value of ORIG_RAX is 15, whereas in Zig, the value of std.os.REG.RAX is 13. I hardcoded the value, and now I can correctly obtain the system call numbers.

Thank you for everyone’s help.

What should be the equivalent constant for ORIG_RAX. Isn’t it std.os.linux.REG.RAX?

my env

$ uname -mprs
Linux 6.5.8-arch1-1 x86_64 unknown

$ cat /proc/cpuinfo
processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 25
model       : 80
model name  : AMD Ryzen 7 5800H with Radeon Graphics
stepping    : 0
microcode   : 0xa50000c
cpu MHz     : 4161.513
cache size  : 512 KB
physical id : 0
siblings    : 16
core id     : 0
cpu cores   : 8
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 16
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip pku ospke vaes vpclmulqdq rdpid overflow_recov succor smca fsrm
bugs        : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass srso
bogomips    : 6390.10
TLB size    : 2560 4K pages
clflush size    : 64
cache_alignment : 64
address sizes   : 48 bits physical, 48 bits virtual
power management: ts ttp tm hwpstate cpb eff_freq_ro [13] [14]

It seems like the REG constants come from ucontext.h and are different from the constants you want. Instead, the #defines that are in reg.h is what you want. It doesn’t look like the constants you want are defined in the Zig standard library currently.

Here’s musl’s x86_64 reg.h:

and glibc’s:

1 Like

here it is (Feb 2004)
scut2.c.txt (7.6 KB)

1 Like