lib/sys/src/process/tracing/syscall.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const linux = std.os.linux;
4
5 pub const Operation = enum { open, openat, openat2, creat, rename, renameat, renameat2 };
6 pub const FileCall = struct {
7 operation: Operation,
8 path: u64,
9 path2: u64 = 0,
10 directory: i32 = -100,
11 directory2: i32 = -100,
12 flags: ?u64 = null,
13 };
14 pub const Phase = union(enum) { entry: ?FileCall, exit: i64 };
15 const Info = extern struct {
16 op: u8,
17 pad: [3]u8,
18 arch: u32,
19 ip: u64,
20 sp: u64,
21 data: extern union {
22 entry: extern struct { nr: u64, args: [6]u64 },
23 exit: extern struct { value: i64, failed: u8 },
24 },
25 };
26
27 pub fn inspect(pid: i32) !Phase {
28 var info: Info = std.mem.zeroes(Info);
29 const result = linux.ptrace(
30 linux.PTRACE.GET_SYSCALL_INFO,
31 pid,
32 @sizeOf(Info),
33 @intFromPtr(&info),
34 0,
35 );
36 if (linux.errno(result) != .SUCCESS) return error.SyscallObservationUnavailable;
37 const arch: u32 = switch (builtin.cpu.arch) {
38 .x86_64 => 0xc000003e,
39 .aarch64 => 0xc00000b7,
40 else => return error.UnsupportedSyscallArchitecture,
41 };
42 if (info.arch != arch) return error.UnsupportedTraceeArchitecture;
43 if (info.op == 1 and result < 80) return error.IncompleteSyscallObservation;
44 if (info.op == 2 and result < 33) return error.IncompleteSyscallObservation;
45 if (builtin.cpu.arch == .x86_64 and info.op == 1 and
46 (info.data.entry.nr & 0x40000000) != 0) return error.UnsupportedTraceeArchitecture;
47 return switch (info.op) {
48 1 => .{ .entry = decode(info.data.entry.nr, info.data.entry.args) },
49 2 => .{ .exit = info.data.exit.value },
50 else => error.IncompleteSyscallObservation,
51 };
52 }
53
54 fn number(comptime name: []const u8, nr: u64) bool {
55 if (comptime @hasField(linux.SYS, name)) return nr == @backingInt(@field(linux.SYS, name));
56 return false;
57 }
58
59 fn directory(value: u64) i32 {
60 return @bitCast(@as(u32, @truncate(value)));
61 }
62
63 fn decode(nr: u64, args: [6]u64) ?FileCall {
64 if (number("open", nr)) return .{ .operation = .open, .path = args[0], .flags = args[1] };
65 if (number("creat", nr)) return .{ .operation = .creat, .path = args[0], .flags = 0x241 };
66 if (number("openat", nr) or number("openat2", nr)) return .{
67 .operation = if (number("openat", nr)) .openat else .openat2,
68 .directory = directory(args[0]),
69 .path = args[1],
70 .flags = if (number("openat", nr)) args[2] else null,
71 };
72 if (number("rename", nr)) return .{ .operation = .rename, .path = args[0], .path2 = args[1] };
73 if (number("renameat", nr) or number("renameat2", nr)) return .{
74 .operation = if (number("renameat", nr)) .renameat else .renameat2,
75 .directory = directory(args[0]),
76 .path = args[1],
77 .directory2 = directory(args[2]),
78 .path2 = args[3],
79 .flags = if (number("renameat2", nr)) args[4] else 0,
80 };
81 return null;
82 }
83
84 /// Turns a path argument at a syscall stop into bytes a recorder can write down
85 /// by copying the path out of a stopped tracee's memory at `address` into
86 /// `buffer`, and then returning the bytes before the terminator. A zero or
87 /// out-of-range address, a failed read, and a copy that holds no terminator
88 /// each return null, and the last marks a path longer than the buffer.
89 pub fn path(pid: i32, address: u64, buffer: []u8) ?[]const u8 {
90 if (address == 0 or address > std.math.maxInt(usize)) return null;
91 const local = [_]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }};
92 const remote = [_]std.posix.iovec_const{.{
93 .base = @ptrFromInt(@as(usize, @intCast(address))),
94 .len = buffer.len,
95 }};
96 const result = linux.process_vm_readv(pid, &local, &remote, 0);
97 if (linux.errno(result) != .SUCCESS) return null;
98 const end = std.mem.indexOfScalar(u8, buffer[0..result], 0) orelse return null;
99 return buffer[0..end];
100 }
101
102 test "file syscall decoding retains directory descriptors and write intent" {
103 const value = decode(@backingInt(linux.SYS.openat), .{
104 @as(u64, @bitCast(@as(i64, -100))), 1234, 0x241, 0, 0, 0,
105 }).?;
106 try std.testing.expectEqual(@as(i32, -100), value.directory);
107 try std.testing.expectEqual(@as(?u64, 0x241), value.flags);
108 try std.testing.expectEqual(@as(u64, 1234), value.path);
109 try std.testing.expect(decode(@backingInt(linux.SYS.getpid), @splat(0)) == null);
110 }