Compare commits

..

9 Commits

8 changed files with 219 additions and 81 deletions

View File

@@ -18,10 +18,11 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
- [x] `rt_sigreturn`: we can't use the normal `syscall` interception because we push something onto - [x] `rt_sigreturn`: we can't use the normal `syscall` interception because we push something onto
the stack, so `ucontext` isn't on top anymore. the stack, so `ucontext` isn't on top anymore.
- [x] `/proc/self/exe`: intercept calls to `readlink`/`readlinkat` with that as argument - [x] `/proc/self/exe`: intercept calls to `readlink`/`readlinkat` with that as argument
- [ ] `auxv`: check if that is setup correctly and completely - [x] `auxv`: check if that is setup correctly and completely
- [ ] JIT support: intercept `mmap`, `mprotect` and `mremap` that change pages to be executable - [x] JIT support: intercept `mmap`, `mprotect` and `mremap` that change pages to be executable
- [ ] `SIGILL` patching fallback - [ ] `SIGILL` patching fallback
- [ ] `vdso` handling - [x] `vdso` handling
- [x] check why the libc tests are flaky
## Minor things ## Minor things
@@ -30,6 +31,8 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
- [ ] Ghost page edge case: In all patch strategies, if a range spans multiple pages and we `mmap` - [ ] Ghost page edge case: In all patch strategies, if a range spans multiple pages and we `mmap`
the first one but can't `mmap` the second one we just let the first one mapped. It would be better the first one but can't `mmap` the second one we just let the first one mapped. It would be better
to unmap them to unmap them
- [ ] Right now when patching we mmap a page and may not use it, but we still leave it mapped. This
leaks memory. If we fix this correctly the Ghost page issue is also fixed
- [ ] Re-entrancy for `patchRegion` - [ ] Re-entrancy for `patchRegion`
- when a signal comes, while we are in that function, and we need to patch something due to the - when a signal comes, while we are in that function, and we need to patch something due to the
signal we will deadlock signal we will deadlock
@@ -45,3 +48,5 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
- [ ] `modify_ldt`: check what we need to intercept and change - [ ] `modify_ldt`: check what we need to intercept and change
- [ ] `set_tid_address`: check what we need to intercept and change - [ ] `set_tid_address`: check what we need to intercept and change
- [ ] performance optimizations for patched code? Peephole might be possible - [ ] performance optimizations for patched code? Peephole might be possible
- [ ] maybe add a way to run something after the client is finished
- could be useful for statistics, cleanup(if necessary), or notifying of suppressed warnings

View File

@@ -209,9 +209,13 @@ pub const Statistics = struct {
/// Scans a memory region for instructions that require patching and applies the patches /// Scans a memory region for instructions that require patching and applies the patches
/// using a hierarchy of tactics (Direct/Punning -> Successor Eviction -> Neighbor Eviction). /// using a hierarchy of tactics (Direct/Punning -> Successor Eviction -> Neighbor Eviction).
/// ///
/// The region is processed Back-to-Front to ensure that modifications (punning) only /// NOTE: This function leaves the region as R|W and the caller is responsible for changing it to
/// constrain instructions that have already been processed or are locked. /// the desired protection
pub fn patchRegion(region: []align(page_size) u8) !void { pub fn patchRegion(region: []align(page_size) u8) !void {
log.info(
"Patching region: 0x{x} - 0x{x}",
.{ @intFromPtr(region.ptr), @intFromPtr(&region[region.len - 1]) },
);
// For now just do a coarse lock. // For now just do a coarse lock.
// TODO: should we make this more fine grained? // TODO: should we make this more fine grained?
mutex.lock(); mutex.lock();
@@ -296,8 +300,6 @@ pub fn patchRegion(region: []align(page_size) u8) !void {
{ {
// Apply patches. // Apply patches.
try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE); try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE);
defer posix.mprotect(region, posix.PROT.READ | posix.PROT.EXEC) catch
@panic("patchRegion: mprotect back to R|X failed. Can't continue");
var stats = Statistics.empty; var stats = Statistics.empty;
// Used to track which bytes have been modified or used for constraints (punning), // Used to track which bytes have been modified or used for constraints (punning),
@@ -854,7 +856,7 @@ fn ensureRangeWritable(
const gop = try allocated_pages.getOrPut(gpa, page_addr); const gop = try allocated_pages.getOrPut(gpa, page_addr);
if (gop.found_existing) { if (gop.found_existing) {
const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr); const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr);
try posix.mprotect(ptr[0..page_addr], protection); try posix.mprotect(ptr[0..page_size], protection);
} else { } else {
const addr = posix.mmap( const addr = posix.mmap(
@ptrFromInt(page_addr), @ptrFromInt(page_addr),

View File

@@ -6,10 +6,14 @@ const log = std.log.scoped(.disassembler);
const assert = std.debug.assert; const assert = std.debug.assert;
pub const InstructionIterator = struct { pub const InstructionIterator = struct {
/// Maximum number of warnings to print per iterator before suppressing.
pub var max_warnings: u64 = 3;
decoder: zydis.ZydisDecoder, decoder: zydis.ZydisDecoder,
bytes: []const u8, bytes: []const u8,
instruction: zydis.ZydisDecodedInstruction, instruction: zydis.ZydisDecodedInstruction,
operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand, operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand,
warnings: usize = 0,
pub fn init(bytes: []const u8) InstructionIterator { pub fn init(bytes: []const u8) InstructionIterator {
var decoder: zydis.ZydisDecoder = undefined; var decoder: zydis.ZydisDecoder = undefined;
@@ -38,27 +42,33 @@ pub const InstructionIterator = struct {
var address: u64 = @intFromPtr(iterator.bytes.ptr); var address: u64 = @intFromPtr(iterator.bytes.ptr);
while (!zydis.ZYAN_SUCCESS(status)) { while (!zydis.ZYAN_SUCCESS(status)) {
// TODO: handle common padding bytes if (status == zydis.ZYDIS_STATUS_NO_MORE_DATA) {
switch (status) { log.debug("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
zydis.ZYDIS_STATUS_NO_MORE_DATA => { return null;
log.info("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
return null;
},
zydis.ZYDIS_STATUS_ILLEGAL_LOCK => log.warn("next: Got status: ILLEGAL_LOCK. " ++
"Byte stepping, to find next valid instruction begin", .{}),
zydis.ZYDIS_STATUS_DECODING_ERROR => log.warn("next: Got status: DECODING_ERROR. " ++
"Byte stepping, to find next valid instruction begin", .{}),
else => log.warn("next: Got unknown status: 0x{x}. Byte stepping, to find next " ++
"valid instruction begin", .{status}),
} }
// TODO: handle common padding bytes
// TODO: add a flag to instead return an error // TODO: add a flag to instead return an error
iterator.warnings += 1;
if (iterator.warnings <= max_warnings) {
const err_desc = switch (status) {
zydis.ZYDIS_STATUS_ILLEGAL_LOCK => "ILLEGAL_LOCK",
zydis.ZYDIS_STATUS_DECODING_ERROR => "DECODING_ERROR",
zydis.ZYDIS_STATUS_INVALID_MAP => "INVALID_MAP",
else => "UNKNOWN",
};
log.warn(
"next: Got status: {s} (0x{x}). Byte stepping, for next instruction begin",
.{ err_desc, status },
);
if (iterator.warnings == max_warnings) {
log.warn("next: Suppressing further warnings for this disassembly.", .{});
}
}
log.debug( log.debug(
"next: instruction length: {}, address: 0x{x}, bytes: 0x{x}", "next: skipping byte at address: 0x{x}, byte: 0x{x}",
.{ .{ address, iterator.bytes[0] },
iterator.instruction.length,
address,
iterator.bytes[0..iterator.instruction.length],
},
); );
iterator.bytes = iterator.bytes[1..]; iterator.bytes = iterator.bytes[1..];

View File

@@ -71,6 +71,7 @@ pub fn main() !void {
const base = try loadStaticElf(ehdr, &file_reader); const base = try loadStaticElf(ehdr, &file_reader);
const entry = ehdr.entry + if (ehdr.type == .DYN) base else 0; const entry = ehdr.entry + if (ehdr.type == .DYN) base else 0;
log.info("Executable loaded: base=0x{x}, entry=0x{x}", .{ base, entry }); log.info("Executable loaded: base=0x{x}, entry=0x{x}", .{ base, entry });
try patchLoadedElf(base);
// Check for dynamic linker // Check for dynamic linker
var maybe_interp_base: ?usize = null; var maybe_interp_base: ?usize = null;
@@ -102,13 +103,13 @@ pub fn main() !void {
"Interpreter loaded: base=0x{x}, entry=0x{x}", "Interpreter loaded: base=0x{x}, entry=0x{x}",
.{ interp_base, maybe_interp_entry.? }, .{ interp_base, maybe_interp_entry.? },
); );
try patchLoadedElf(interp_base);
interp.close(); interp.close();
} }
var i: usize = 0; var i: usize = 0;
const auxv = std.os.linux.elf_aux_maybe.?; const auxv = std.os.linux.elf_aux_maybe.?;
while (auxv[i].a_type != elf.AT_NULL) : (i += 1) { while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
// TODO: look at other auxv types and check if we need to change them.
auxv[i].a_un.a_val = switch (auxv[i].a_type) { auxv[i].a_un.a_val = switch (auxv[i].a_type) {
elf.AT_PHDR => base + ehdr.phoff, elf.AT_PHDR => base + ehdr.phoff,
elf.AT_PHENT => ehdr.phentsize, elf.AT_PHENT => ehdr.phentsize,
@@ -116,6 +117,21 @@ pub fn main() !void {
elf.AT_BASE => maybe_interp_base orelse auxv[i].a_un.a_val, elf.AT_BASE => maybe_interp_base orelse auxv[i].a_un.a_val,
elf.AT_ENTRY => entry, elf.AT_ENTRY => entry,
elf.AT_EXECFN => @intFromPtr(std.os.argv[arg_index]), elf.AT_EXECFN => @intFromPtr(std.os.argv[arg_index]),
elf.AT_SYSINFO_EHDR => blk: {
log.info("Found vDSO at 0x{x}", .{auxv[i].a_un.a_val});
try patchLoadedElf(auxv[i].a_un.a_val);
break :blk auxv[i].a_un.a_val;
},
elf.AT_EXECFD => {
@panic("Got AT_EXECFD auxv value");
// TODO: handle AT_EXECFD, when needed
// The SysV ABI Specification says:
// > At process creation the system may pass control to an interpreter program. When
// > this happens, the system places either an entry of type AT_EXECFD or one of
// > type AT_PHDR in the auxiliary vector. The entry for type AT_EXECFD uses the
// > a_val member to contain a file descriptor open to read the application
// > programs object file.
},
else => auxv[i].a_un.a_val, else => auxv[i].a_un.a_val,
}; };
} }
@@ -210,16 +226,45 @@ fn loadStaticElf(ehdr: elf.Header, file_reader: *std.fs.File.Reader) !usize {
return UnfinishedReadError.UnfinishedRead; return UnfinishedReadError.UnfinishedRead;
const protections = elfToMmapProt(phdr.p_flags); const protections = elfToMmapProt(phdr.p_flags);
if (protections & posix.PROT.EXEC > 0) {
log.info("Patching executable segment", .{});
try Patcher.patchRegion(ptr);
}
try posix.mprotect(ptr, protections); try posix.mprotect(ptr, protections);
} }
log.debug("loadElf returning base: 0x{x}", .{@intFromPtr(base.ptr)}); log.debug("loadElf returning base: 0x{x}", .{@intFromPtr(base.ptr)});
return @intFromPtr(base.ptr); return @intFromPtr(base.ptr);
} }
fn patchLoadedElf(base: usize) !void {
const ehdr = @as(*const elf.Ehdr, @ptrFromInt(base));
if (!mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
const phoff = ehdr.e_phoff;
const phnum = ehdr.e_phnum;
const phentsize = ehdr.e_phentsize;
var i: usize = 0;
while (i < phnum) : (i += 1) {
const phdr_ptr = base + phoff + (i * phentsize);
const phdr = @as(*const elf.Phdr, @ptrFromInt(phdr_ptr));
if (phdr.p_type != elf.PT_LOAD) continue;
if ((phdr.p_flags & elf.PF_X) == 0) continue;
// Determine VMA
// For ET_EXEC, p_vaddr is absolute.
// For ET_DYN, p_vaddr is offset from base.
const vaddr = if (ehdr.e_type == elf.ET.DYN) base + phdr.p_vaddr else phdr.p_vaddr;
const memsz = phdr.p_memsz;
const page_start = mem.alignBackward(usize, vaddr, page_size);
const page_end = mem.alignForward(usize, vaddr + memsz, page_size);
const size = page_end - page_start;
const region = @as([*]align(page_size) u8, @ptrFromInt(page_start))[0..size];
try Patcher.patchRegion(region);
try posix.mprotect(region, elfToMmapProt(phdr.p_flags));
}
}
/// Converts ELF program header protection flags to mmap protection flags. /// Converts ELF program header protection flags to mmap protection flags.
fn elfToMmapProt(elf_prot: u64) u32 { fn elfToMmapProt(elf_prot: u64) u32 {
var result: u32 = posix.PROT.NONE; var result: u32 = posix.PROT.NONE;
@@ -288,10 +333,9 @@ test "nolibc_nopie_exit" {
test "nolibc_pie_exit" { test "nolibc_pie_exit" {
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_exit") }, ""); try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_exit") }, "");
} }
// BUG: This one is flaky test "libc_pie_exit" {
// test "libc_pie_exit" { try testHelper(&.{ flicker_path, getTestExePath("libc_pie_exit") }, "");
// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_exit") }, ""); }
// }
test "nolibc_nopie_helloWorld" { test "nolibc_nopie_helloWorld" {
try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_helloWorld") }, "Hello World!\n"); try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_helloWorld") }, "Hello World!\n");
@@ -299,10 +343,9 @@ test "nolibc_nopie_helloWorld" {
test "nolibc_pie_helloWorld" { test "nolibc_pie_helloWorld" {
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_helloWorld") }, "Hello World!\n"); try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_helloWorld") }, "Hello World!\n");
} }
// BUG: This one is flaky test "libc_pie_helloWorld" {
// test "libc_pie_helloWorld" { try testHelper(&.{ flicker_path, getTestExePath("libc_pie_helloWorld") }, "Hello World!\n");
// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_helloWorld") }, "Hello World!\n"); }
// }
test "nolibc_nopie_printArgs" { test "nolibc_nopie_printArgs" {
try testPrintArgs("nolibc_nopie_printArgs"); try testPrintArgs("nolibc_nopie_printArgs");
@@ -310,10 +353,9 @@ test "nolibc_nopie_printArgs" {
test "nolibc_pie_printArgs" { test "nolibc_pie_printArgs" {
try testPrintArgs("nolibc_pie_printArgs"); try testPrintArgs("nolibc_pie_printArgs");
} }
// BUG: This one is flaky test "libc_pie_printArgs" {
// test "libc_pie_printArgs" { try testPrintArgs("libc_pie_printArgs");
// try testPrintArgs("libc_pie_printArgs"); }
// }
test "nolibc_nopie_readlink" { test "nolibc_nopie_readlink" {
try testReadlink("nolibc_nopie_readlink"); try testReadlink("nolibc_nopie_readlink");
@@ -321,10 +363,9 @@ test "nolibc_nopie_readlink" {
test "nolibc_pie_readlink" { test "nolibc_pie_readlink" {
try testReadlink("nolibc_pie_readlink"); try testReadlink("nolibc_pie_readlink");
} }
// BUG: This one just outputs the path to the flicker executable and is likely also flaky test "libc_pie_readlink" {
// test "libc_pie_readlink" { try testReadlink("libc_pie_readlink");
// try testReadlink("libc_pie_readlink"); }
// }
test "nolibc_nopie_clone_raw" { test "nolibc_nopie_clone_raw" {
try testHelper( try testHelper(
@@ -352,10 +393,6 @@ test "nolibc_pie_clone_no_new_stack" {
); );
} }
test "echo" {
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
}
test "nolibc_nopie_fork" { test "nolibc_nopie_fork" {
try testHelper( try testHelper(
&.{ flicker_path, getTestExePath("nolibc_nopie_fork") }, &.{ flicker_path, getTestExePath("nolibc_nopie_fork") },
@@ -368,13 +405,12 @@ test "nolibc_pie_fork" {
"Child: I'm alive!\nParent: Child died.\n", "Child: I'm alive!\nParent: Child died.\n",
); );
} }
// BUG: This one is flaky test "libc_pie_fork" {
// test "libc_pie_fork" { try testHelper(
// try testHelper( &.{ flicker_path, getTestExePath("libc_pie_fork") },
// &.{ flicker_path, getTestExePath("libc_pie_fork") }, "Child: I'm alive!\nParent: Child died.\n",
// "Child: I'm alive!\nParent: Child died.\n", );
// ); }
// }
test "nolibc_nopie_signal_handler" { test "nolibc_nopie_signal_handler" {
try testHelper( try testHelper(
@@ -389,6 +425,29 @@ test "nolibc_pie_signal_handler" {
); );
} }
test "nolibc_nopie_vdso_clock" {
try testHelper(
&.{ flicker_path, getTestExePath("nolibc_nopie_vdso_clock") },
"Time gotten\n",
);
}
test "nolibc_pie_vdso_clock" {
try testHelper(
&.{ flicker_path, getTestExePath("nolibc_pie_vdso_clock") },
"Time gotten\n",
);
}
test "libc_pie_vdso_clock" {
try testHelper(
&.{ flicker_path, getTestExePath("libc_pie_vdso_clock") },
"Time gotten\n",
);
}
test "echo" {
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
}
fn testPrintArgs(comptime name: []const u8) !void { fn testPrintArgs(comptime name: []const u8) !void {
const exe_path = getTestExePath(name); const exe_path = getTestExePath(name);
const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" }; const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" };

View File

@@ -1,8 +1,13 @@
const std = @import("std"); const std = @import("std");
const linux = std.os.linux; const linux = std.os.linux;
const posix = std.posix;
const Patcher = @import("Patcher.zig"); const Patcher = @import("Patcher.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const page_size = std.heap.pageSize();
const log = std.log.scoped(.syscalls);
/// Represents the stack layout pushed by `syscallEntry` before calling the handler. /// Represents the stack layout pushed by `syscallEntry` before calling the handler.
pub const SavedContext = extern struct { pub const SavedContext = extern struct {
padding: u64, // Result of `sub $8, %rsp` for alignment padding: u64, // Result of `sub $8, %rsp` for alignment
@@ -74,38 +79,93 @@ export fn syscall_handler(ctx: *SavedContext) callconv(.c) void {
asm volatile ( asm volatile (
\\ mov %[rsp], %%rsp \\ mov %[rsp], %%rsp
\\ syscall \\ syscall
\\ ud2
: :
: [rsp] "r" (rsp_orig), : [rsp] "r" (rsp_orig),
[number] "{rax}" (ctx.rax), [number] "{rax}" (ctx.rax),
: .{ .memory = true } : .{ .memory = true });
);
unreachable; unreachable;
}, },
.execve, .execveat => |s| { .mmap => {
// TODO: option to persist across new processes // mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
std.debug.print("syscall {} called\n", .{s});
const prot: u32 = @intCast(ctx.rdx);
// Execute the syscall first to get the address (rax)
ctx.rax = executeSyscall(ctx);
const addr = ctx.rax;
var len = ctx.rsi;
const flags: linux.MAP = @bitCast(@as(u32, @intCast(ctx.r10)));
const fd: linux.fd_t = @bitCast(@as(u32, @truncate(ctx.r8)));
const offset = ctx.r9;
const is_error = @as(i64, @bitCast(ctx.rax)) < 0;
if (is_error) return;
if ((prot & posix.PROT.EXEC) == 0) return;
// If file-backed (not anonymous), clamp len to file size to avoid SIGBUS
if (!flags.ANONYMOUS) {
var stat: linux.Stat = undefined;
if (0 == linux.fstat(fd, &stat) and linux.S.ISREG(stat.mode)) {
const file_size: u64 = @intCast(stat.size);
len = if (offset >= file_size) 0 else @min(len, file_size - offset);
}
}
if (len <= 0) return;
// mmap addresses are always page aligned
const ptr = @as([*]align(page_size) u8, @ptrFromInt(addr));
// Check if we can patch it
Patcher.patchRegion(ptr[0..len]) catch |err| {
std.log.warn("JIT Patching failed: {}", .{err});
};
// patchRegion leaves it as RW. We need to restore to requested prot.
_ = linux.syscall3(.mprotect, addr, len, prot);
return;
}, },
.prctl, .arch_prctl, .set_tid_address => |s| { .mprotect => {
// mprotect(void *addr, size_t len, int prot)
// TODO: cleanup trampolines, when removing X
const prot: u32 = @intCast(ctx.rdx);
if ((prot & posix.PROT.EXEC) != 0) {
const addr = ctx.rdi;
const len = ctx.rsi;
// mprotect requires addr to be page aligned.
if (len > 0 and std.mem.isAligned(addr, page_size)) {
const ptr = @as([*]align(page_size) u8, @ptrFromInt(addr));
Patcher.patchRegion(ptr[0..len]) catch |err| {
std.log.warn("mprotect Patching failed: {}", .{err});
};
// patchRegion leaves it R|W.
}
}
ctx.rax = executeSyscall(ctx);
return;
},
.execve, .execveat => {
// TODO: option to persist across new processes
ctx.rax = executeSyscall(ctx);
return;
},
.prctl, .arch_prctl, .set_tid_address => {
// TODO: what do we need to handle from these? // TODO: what do we need to handle from these?
// process name // process name
// fs base(gs?) // fs base(gs?)
// thread id pointers // thread id pointers
std.debug.print("syscall {} called\n", .{s}); ctx.rax = executeSyscall(ctx);
}, return;
.mmap, .mprotect => {
// TODO: JIT support
// TODO: cleanup
}, },
.munmap, .mremap => { .munmap, .mremap => {
// TODO: cleanup // TODO: cleanup
ctx.rax = executeSyscall(ctx);
return;
},
else => {
// Write result back to the saved RAX so it is restored to the application.
ctx.rax = executeSyscall(ctx);
return;
}, },
else => {},
} }
unreachable;
// Write result back to the saved RAX so it is restored to the application.
ctx.rax = executeSyscall(ctx);
} }
inline fn executeSyscall(ctx: *SavedContext) u64 { inline fn executeSyscall(ctx: *SavedContext) u64 {

View File

@@ -30,9 +30,6 @@ pub fn main() !void {
\\ mov $60, %%rax # SYS_exit \\ mov $60, %%rax # SYS_exit
\\ syscall \\ syscall
\\ \\
\\ # Should not be reached
\\ ud2
\\
\\ 1: \\ 1:
\\ # Parent Path continues \\ # Parent Path continues
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),

View File

@@ -34,9 +34,6 @@ pub fn main() !void {
\\ mov $60, %%rax # SYS_exit \\ mov $60, %%rax # SYS_exit
\\ syscall \\ syscall
\\ \\
\\ # Should not be reached
\\ ud2
\\
\\ 1: \\ 1:
\\ # Parent Path continues \\ # Parent Path continues
: [ret] "={rax}" (-> usize), : [ret] "={rax}" (-> usize),

8
src/test/vdso_clock.zig Normal file
View File

@@ -0,0 +1,8 @@
const std = @import("std");
pub fn main() !void {
_ = try std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC);
const msg = "Time gotten\n";
_ = try std.posix.write(1, msg);
}