diff --git a/build.zig b/build.zig index 303495f6ba..60172eaebd 100644 --- a/build.zig +++ b/build.zig @@ -152,6 +152,7 @@ pub fn build(b: *std.Build) !void { if (only_install_lib_files) return; + const entitlements = b.option([]const u8, "entitlements", "Path to entitlements file for hot-code swapping without sudo on macOS"); const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source"); const tracy_callstack = b.option(bool, "tracy-callstack", "Include callstack information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null); const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null); @@ -173,6 +174,7 @@ pub fn build(b: *std.Build) !void { exe.pie = pie; exe.sanitize_thread = sanitize_thread; exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false; + exe.entitlements = entitlements; exe.install(); const compile_step = b.step("compile", "Build the self-hosted compiler"); diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 75267cc171..eefa68e6a9 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -203,47 +203,6 @@ pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) kern_retu pub extern "c" fn malloc_size(?*const anyopaque) usize; pub extern "c" fn posix_memalign(memptr: *?*anyopaque, alignment: usize, size: usize) c_int; -pub const posix_spawnattr_t = *opaque {}; -pub const posix_spawn_file_actions_t = *opaque {}; -pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int; -pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int; -pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int; -pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) c_int; -pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int; -pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int; -pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; -pub extern "c" fn posix_spawn_file_actions_addopen( - actions: *posix_spawn_file_actions_t, - filedes: fd_t, - path: [*:0]const u8, - oflag: c_int, - mode: mode_t, -) c_int; -pub extern "c" fn posix_spawn_file_actions_adddup2( - actions: *posix_spawn_file_actions_t, - filedes: fd_t, - newfiledes: fd_t, -) c_int; -pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; -pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int; -pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; -pub extern "c" fn posix_spawn( - pid: *pid_t, - path: [*:0]const u8, - actions: ?*const posix_spawn_file_actions_t, - attr: ?*const posix_spawnattr_t, - argv: [*:null]?[*:0]const u8, - env: [*:null]?[*:0]const u8, -) c_int; -pub extern "c" fn posix_spawnp( - pid: *pid_t, - path: [*:0]const u8, - actions: ?*const posix_spawn_file_actions_t, - attr: ?*const posix_spawnattr_t, - argv: [*:null]?[*:0]const u8, - env: [*:null]?[*:0]const u8, -) c_int; - pub extern "c" fn kevent64( kq: c_int, changelist: [*]const kevent64_s, @@ -2176,18 +2135,6 @@ pub const E = enum(u16) { _, }; -pub fn getKernError(err: kern_return_t) KernE { - return @intToEnum(KernE, @truncate(u32, @intCast(usize, err))); -} - -pub fn unexpectedKernError(err: KernE) std.os.UnexpectedError { - if (std.os.unexpected_error_tracing) { - std.debug.print("unexpected errno: {d}\n", .{@enumToInt(err)}); - std.debug.dumpCurrentStackTrace(null); - } - return error.Unexpected; -} - /// Kernel return values pub const KernE = enum(u32) { SUCCESS = 0, @@ -3063,38 +3010,320 @@ pub const CPUFAMILY = enum(u32) { _, }; -pub const POSIX_SPAWN_RESETIDS = 0x0001; -pub const POSIX_SPAWN_SETPGROUP = 0x0002; -pub const POSIX_SPAWN_SETSIGDEF = 0x0004; -pub const POSIX_SPAWN_SETSIGMASK = 0x0008; -pub const POSIX_SPAWN_SETEXEC = 0x0040; -pub const POSIX_SPAWN_START_SUSPENDED = 0x0080; -pub const _POSIX_SPAWN_DISABLE_ASLR = 0x0100; -pub const POSIX_SPAWN_SETSID = 0x0400; -pub const _POSIX_SPAWN_RESLIDE = 0x0800; -pub const POSIX_SPAWN_CLOEXEC_DEFAULT = 0x4000; - -pub const PT_TRACE_ME = 0; -pub const PT_READ_I = 1; -pub const PT_READ_D = 2; -pub const PT_READ_U = 3; -pub const PT_WRITE_I = 4; -pub const PT_WRITE_D = 5; -pub const PT_WRITE_U = 6; -pub const PT_CONTINUE = 7; -pub const PT_KILL = 8; -pub const PT_STEP = 9; -pub const PT_DETACH = 11; -pub const PT_SIGEXC = 12; -pub const PT_THUPDATE = 13; -pub const PT_ATTACHEXC = 14; -pub const PT_FORCEQUOTA = 30; -pub const PT_DENY_ATTACH = 31; +pub const PT = struct { + pub const TRACE_ME = 0; + pub const READ_I = 1; + pub const READ_D = 2; + pub const READ_U = 3; + pub const WRITE_I = 4; + pub const WRITE_D = 5; + pub const WRITE_U = 6; + pub const CONTINUE = 7; + pub const KILL = 8; + pub const STEP = 9; + pub const DETACH = 11; + pub const SIGEXC = 12; + pub const THUPDATE = 13; + pub const ATTACHEXC = 14; + pub const FORCEQUOTA = 30; + pub const DENY_ATTACH = 31; +}; pub const caddr_t = ?[*]u8; pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) c_int; +pub const POSIX_SPAWN = struct { + pub const RESETIDS = 0x0001; + pub const SETPGROUP = 0x0002; + pub const SETSIGDEF = 0x0004; + pub const SETSIGMASK = 0x0008; + pub const SETEXEC = 0x0040; + pub const START_SUSPENDED = 0x0080; + pub const DISABLE_ASLR = 0x0100; + pub const SETSID = 0x0400; + pub const RESLIDE = 0x0800; + pub const CLOEXEC_DEFAULT = 0x4000; +}; + +pub const posix_spawnattr_t = *opaque {}; +pub const posix_spawn_file_actions_t = *opaque {}; +pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int; +pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int; +pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int; +pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) c_int; +pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int; +pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int; +pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn_file_actions_addopen( + actions: *posix_spawn_file_actions_t, + filedes: fd_t, + path: [*:0]const u8, + oflag: c_int, + mode: mode_t, +) c_int; +pub extern "c" fn posix_spawn_file_actions_adddup2( + actions: *posix_spawn_file_actions_t, + filedes: fd_t, + newfiledes: fd_t, +) c_int; +pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int; +pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn( + pid: *pid_t, + path: [*:0]const u8, + actions: ?*const posix_spawn_file_actions_t, + attr: ?*const posix_spawnattr_t, + argv: [*:null]?[*:0]const u8, + env: [*:null]?[*:0]const u8, +) c_int; +pub extern "c" fn posix_spawnp( + pid: *pid_t, + path: [*:0]const u8, + actions: ?*const posix_spawn_file_actions_t, + attr: ?*const posix_spawnattr_t, + argv: [*:null]?[*:0]const u8, + env: [*:null]?[*:0]const u8, +) c_int; + +pub const PosixSpawn = struct { + const errno = std.os.errno; + const unexpectedErrno = std.os.unexpectedErrno; + + pub const Error = error{ + SystemResources, + InvalidFileDescriptor, + NameTooLong, + TooBig, + PermissionDenied, + InputOutput, + FileSystem, + FileNotFound, + InvalidExe, + NotDir, + FileBusy, + /// Returned when the child fails to execute either in the pre-exec() initialization step, or + /// when exec(3) is invoked. + ChildExecFailed, + } || std.os.UnexpectedError; + + pub const Attr = struct { + attr: posix_spawnattr_t, + + pub fn init() Error!Attr { + var attr: posix_spawnattr_t = undefined; + switch (errno(posix_spawnattr_init(&attr))) { + .SUCCESS => return Attr{ .attr = attr }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Attr) void { + defer self.* = undefined; + switch (errno(posix_spawnattr_destroy(&self.attr))) { + .SUCCESS => return, + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } + } + + pub fn get(self: Attr) Error!u16 { + var flags: c_short = undefined; + switch (errno(posix_spawnattr_getflags(&self.attr, &flags))) { + .SUCCESS => return @bitCast(u16, flags), + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn set(self: *Attr, flags: u16) Error!void { + switch (errno(posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) { + .SUCCESS => return, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + }; + + pub const Actions = struct { + actions: posix_spawn_file_actions_t, + + pub fn init() Error!Actions { + var actions: posix_spawn_file_actions_t = undefined; + switch (errno(posix_spawn_file_actions_init(&actions))) { + .SUCCESS => return Actions{ .actions = actions }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Actions) void { + defer self.* = undefined; + switch (errno(posix_spawn_file_actions_destroy(&self.actions))) { + .SUCCESS => return, + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } + } + + pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void { + const posix_path = try std.os.toPosixPath(path); + return self.openZ(fd, &posix_path, flags, mode); + } + + pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void { + switch (errno(posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn close(self: *Actions, fd: fd_t) Error!void { + switch (errno(posix_spawn_file_actions_addclose(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void { + switch (errno(posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn inherit(self: *Actions, fd: fd_t) Error!void { + switch (errno(posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn chdir(self: *Actions, path: []const u8) Error!void { + const posix_path = try std.os.toPosixPath(path); + return self.chdirZ(&posix_path); + } + + pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { + switch (errno(posix_spawn_file_actions_addchdir_np(&self.actions, path))) { + .SUCCESS => return, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .BADF => unreachable, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn fchdir(self: *Actions, fd: fd_t) Error!void { + switch (errno(posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + }; + + pub fn spawn( + path: []const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + const posix_path = try std.os.toPosixPath(path); + return spawnZ(&posix_path, actions, attr, argv, envp); + } + + pub fn spawnZ( + path: [*:0]const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + var pid: pid_t = undefined; + switch (errno(posix_spawn( + &pid, + path, + if (actions) |a| &a.actions else null, + if (attr) |a| &a.attr else null, + argv, + envp, + ))) { + .SUCCESS => return pid, + .@"2BIG" => return error.TooBig, + .NOMEM => return error.SystemResources, + .BADF => return error.InvalidFileDescriptor, + .ACCES => return error.PermissionDenied, + .IO => return error.InputOutput, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADEXEC => return error.InvalidExe, + .FAULT => unreachable, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn waitpid(pid: pid_t, flags: u32) Error!std.os.WaitPidResult { + var status: c_int = undefined; + while (true) { + const rc = waitpid(pid, &status, @intCast(c_int, flags)); + switch (errno(rc)) { + .SUCCESS => return std.os.WaitPidResult{ + .pid = @intCast(pid_t, rc), + .status = @bitCast(u32, status), + }, + .INTR => continue, + .CHILD => return error.ChildExecFailed, + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } + } +}; + +pub fn getKernError(err: kern_return_t) KernE { + return @intToEnum(KernE, @truncate(u32, @intCast(usize, err))); +} + +pub fn unexpectedKernError(err: KernE) std.os.UnexpectedError { + if (std.os.unexpected_error_tracing) { + std.debug.print("unexpected error: {d}\n", .{@enumToInt(err)}); + std.debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + pub const MachError = error{ /// Not enough permissions held to perform the requested kernel /// call. diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 0c0c1a15cc..8f695b14b7 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -656,6 +656,10 @@ pub const segment_command_64 = extern struct { pub fn segName(seg: *const segment_command_64) []const u8 { return parseName(&seg.segname); } + + pub fn isWriteable(seg: segment_command_64) bool { + return seg.initprot & PROT.WRITE != 0; + } }; pub const PROT = struct { diff --git a/lib/std/os.zig b/lib/std/os.zig index 25cc4e34c4..d49db10a2f 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -7129,22 +7129,49 @@ pub fn timerfd_gettime(fd: i32) TimerFdGetError!linux.itimerspec { pub const PtraceError = error{ DeviceBusy, + InputOutput, + Overflow, ProcessNotFound, PermissionDenied, } || UnexpectedError; -/// TODO on other OSes -pub fn ptrace(request: i32, pid: pid_t, addr: ?[*]u8, signal: i32) PtraceError!void { - switch (builtin.os.tag) { - .macos, .ios, .tvos, .watchos => {}, - else => @compileError("TODO implement ptrace"), - } - return switch (errno(system.ptrace(request, pid, addr, signal))) { - .SUCCESS => {}, - .SRCH => error.ProcessNotFound, - .INVAL => unreachable, - .PERM => error.PermissionDenied, - .BUSY => error.DeviceBusy, - else => |err| return unexpectedErrno(err), +pub fn ptrace(request: u32, pid: pid_t, addr: usize, signal: usize) PtraceError!void { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) + @compileError("Unsupported OS"); + + return switch (builtin.os.tag) { + .linux => switch (errno(linux.ptrace(request, pid, addr, signal, 0))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .FAULT => unreachable, + .INVAL => unreachable, + .IO => return error.InputOutput, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, + + .macos, .ios, .tvos, .watchos => switch (errno(darwin.ptrace( + math.cast(i32, request) orelse return error.Overflow, + pid, + @intToPtr(?[*]u8, addr), + math.cast(i32, signal) orelse return error.Overflow, + ))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .INVAL => unreachable, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, + + else => switch (errno(system.ptrace(request, pid, addr, signal))) { + .SUCCESS => {}, + .SRCH => error.ProcessNotFound, + .INVAL => unreachable, + .PERM => error.PermissionDenied, + .BUSY => error.DeviceBusy, + else => |err| return unexpectedErrno(err), + }, }; } diff --git a/src/link.zig b/src/link.zig index a919ffa999..ae6c02c08b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -389,11 +389,11 @@ pub const File = struct { try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{}); try emit.directory.handle.rename(tmp_sub_path, emit.sub_path); switch (builtin.os.tag) { - .linux => { - switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0))) { - .SUCCESS => {}, - else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), - } + .linux => std.os.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| { + log.warn("ptrace failure: {s}", .{@errorName(err)}); + }, + .macos => base.cast(MachO).?.ptraceAttach(pid) catch |err| { + log.warn("attaching failed with error: {s}", .{@errorName(err)}); }, else => return error.HotSwapUnavailableOnHostOperatingSystem, } @@ -430,11 +430,11 @@ pub const File = struct { if (base.child_pid) |pid| { switch (builtin.os.tag) { - .linux => { - switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0))) { - .SUCCESS => {}, - else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}), - } + .linux => std.os.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0) catch |err| { + log.warn("ptrace failure: {s}", .{@errorName(err)}); + }, + .macos => base.cast(MachO).?.ptraceDetach(pid) catch |err| { + log.warn("detaching failed with error: {s}", .{@errorName(err)}); }, else => return error.HotSwapUnavailableOnHostOperatingSystem, } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2f76c49667..151b947141 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -221,6 +221,14 @@ lazy_bindings: BindingTable = .{}, /// Table of tracked Decls. decls: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, DeclMetadata) = .{}, +/// Hot-code swapping state. +hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, + +const is_hot_update_compatible = switch (builtin.target.os.tag) { + .macos => true, + else => false, +}; + const DeclMetadata = struct { atom: Atom.Index, section: u8, @@ -300,6 +308,10 @@ pub const SymbolWithLoc = struct { } }; +const HotUpdateState = struct { + mach_task: ?std.os.darwin.MachTask = null, +}; + /// When allocating, the ideal_capacity is calculated by /// actual_capacity + (actual_capacity / ideal_factor) const ideal_factor = 3; @@ -584,7 +596,26 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try self.allocateSpecialSymbols(); for (self.relocs.keys()) |atom_index| { - try Atom.resolveRelocations(self, atom_index); + const relocs = self.relocs.get(atom_index).?; + const needs_update = for (relocs.items) |reloc| { + if (reloc.dirty) break true; + } else false; + + if (!needs_update) continue; + + const atom = self.getAtom(atom_index); + const sym = atom.getSymbol(self); + const section = self.sections.get(sym.n_sect - 1).header; + const file_offset = section.offset + sym.n_value - section.addr; + + var code = std.ArrayList(u8).init(self.base.allocator); + defer code.deinit(); + try code.resize(math.cast(usize, atom.size) orelse return error.Overflow); + + const amt = try self.base.file.?.preadAll(code.items, file_offset); + if (amt != code.items.len) return error.InputOutput; + + try self.writeAtom(atom_index, code.items); } if (build_options.enable_logging) { @@ -1052,14 +1083,40 @@ pub fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: } } -pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []const u8) !void { +pub fn writeAtom(self: *MachO, atom_index: Atom.Index, code: []u8) !void { const atom = self.getAtom(atom_index); const sym = atom.getSymbol(self); const section = self.sections.get(sym.n_sect - 1); const file_offset = section.header.offset + sym.n_value - section.header.addr; log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset }); + + if (self.relocs.get(atom_index)) |relocs| { + try Atom.resolveRelocations(self, atom_index, relocs.items, code); + } + + if (is_hot_update_compatible) { + if (self.base.child_pid) |pid| blk: { + const task = self.hot_state.mach_task orelse { + log.warn("cannot hot swap: no Mach task acquired for child process with pid {d}", .{pid}); + break :blk; + }; + self.updateAtomInMemory(task, section.segment_index, sym.n_value, code) catch |err| { + log.warn("cannot hot swap: writing to memory failed: {s}", .{@errorName(err)}); + }; + } + } + try self.base.file.?.pwriteAll(code, file_offset); - try Atom.resolveRelocations(self, atom_index); +} + +fn updateAtomInMemory(self: *MachO, task: std.os.darwin.MachTask, segment_index: u8, addr: u64, code: []const u8) !void { + const segment = self.segments.items[segment_index]; + const cpu_arch = self.base.options.target.cpu.arch; + const nwritten = if (!segment.isWriteable()) + try task.writeMemProtected(addr, code, cpu_arch) + else + try task.writeMem(addr, code, cpu_arch); + if (nwritten != code.len) return error.InputOutput; } fn writePtrWidthAtom(self: *MachO, atom_index: Atom.Index) !void { @@ -1068,6 +1125,7 @@ fn writePtrWidthAtom(self: *MachO, atom_index: Atom.Index) !void { } fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void { + log.debug("marking relocs dirty by target: {}", .{target}); // TODO: reverse-lookup might come in handy here for (self.relocs.values()) |*relocs| { for (relocs.items) |*reloc| { @@ -1078,6 +1136,7 @@ fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void { } fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void { + log.debug("marking relocs dirty by address: {x}", .{addr}); for (self.relocs.values()) |*relocs| { for (relocs.items) |*reloc| { const target_atom_index = reloc.getTargetAtomIndex(self) orelse continue; @@ -1702,6 +1761,8 @@ pub fn resolveDyldStubBinder(self: *MachO) !void { if (self.dyld_stub_binder_index != null) return; if (self.unresolved.count() == 0) return; // no need for a stub binder if we don't have any imports + log.debug("resolving dyld_stub_binder", .{}); + const gpa = self.base.allocator; const sym_index = try self.allocateSymbol(); const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null }; @@ -2063,7 +2124,7 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv else try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none); - const code = switch (res) { + var code = switch (res) { .ok => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; @@ -2115,7 +2176,7 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Modu const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none, .{ .parent_atom_index = self.getAtom(atom_index).getSymbolIndex().?, }); - const code = switch (res) { + var code = switch (res) { .ok => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; @@ -2202,7 +2263,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl_index: Module.Decl.Index) .parent_atom_index = atom.getSymbolIndex().?, }); - const code = switch (res) { + var code = switch (res) { .ok => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; @@ -2375,7 +2436,7 @@ pub fn getOutputSection(self: *MachO, sect: macho.section_64) !?u8 { return sect_id; } -fn updateDeclCode(self: *MachO, decl_index: Module.Decl.Index, code: []const u8) !u64 { +fn updateDeclCode(self: *MachO, decl_index: Module.Decl.Index, code: []u8) !u64 { const gpa = self.base.allocator; const mod = self.base.options.module.?; const decl = mod.declPtr(decl_index); @@ -2788,6 +2849,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u8, self.segments.items.len); + try self.segments.append(gpa, .{ .segname = makeStaticString("__LINKEDIT"), .maxprot = macho.PROT.READ, @@ -3760,6 +3822,32 @@ pub fn allocatedVirtualSize(self: *MachO, start: u64) u64 { return min_pos - start; } +pub fn ptraceAttach(self: *MachO, pid: std.os.pid_t) !void { + if (!is_hot_update_compatible) return; + + const mach_task = try std.os.darwin.machTaskForPid(pid); + log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task }); + self.hot_state.mach_task = mach_task; + + // TODO start exception handler in another thread + + // TODO enable ones we register for exceptions + // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0); +} + +pub fn ptraceDetach(self: *MachO, pid: std.os.pid_t) !void { + if (!is_hot_update_compatible) return; + + _ = pid; + + // TODO stop exception handler + + // TODO see comment in ptraceAttach + // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0); + + self.hot_state.mach_task = null; +} + pub fn makeStaticString(bytes: []const u8) [16]u8 { var buf = [_]u8{0} ** 16; assert(bytes.len <= buf.len); diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 5fb94b7c13..36511dfd78 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -183,19 +183,11 @@ pub fn addLazyBinding(macho_file: *MachO, atom_index: Index, binding: Binding) ! try gop.value_ptr.append(gpa, binding); } -pub fn resolveRelocations(macho_file: *MachO, atom_index: Index) !void { - const atom = macho_file.getAtom(atom_index); - const relocs = macho_file.relocs.get(atom_index) orelse return; - const source_sym = atom.getSymbol(macho_file); - const source_section = macho_file.sections.get(source_sym.n_sect - 1).header; - const file_offset = source_section.offset + source_sym.n_value - source_section.addr; - - log.debug("relocating '{s}'", .{atom.getName(macho_file)}); - - for (relocs.items) |*reloc| { +pub fn resolveRelocations(macho_file: *MachO, atom_index: Index, relocs: []Relocation, code: []u8) !void { + log.debug("relocating '{s}'", .{macho_file.getAtom(atom_index).getName(macho_file)}); + for (relocs) |*reloc| { if (!reloc.dirty) continue; - - try reloc.resolve(macho_file, atom_index, file_offset); + try reloc.resolve(macho_file, atom_index, code); reloc.dirty = false; } } diff --git a/src/link/MachO/Relocation.zig b/src/link/MachO/Relocation.zig index 07e5cf1aa2..6beae8e8ea 100644 --- a/src/link/MachO/Relocation.zig +++ b/src/link/MachO/Relocation.zig @@ -50,7 +50,7 @@ pub fn getTargetAtomIndex(self: Relocation, macho_file: *MachO) ?Atom.Index { return macho_file.getAtomIndexForSymbol(self.target); } -pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, base_offset: u64) !void { +pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, code: []u8) !void { const arch = macho_file.base.options.target.cpu.arch; const atom = macho_file.getAtom(atom_index); const source_sym = atom.getSymbol(macho_file); @@ -68,42 +68,28 @@ pub fn resolve(self: Relocation, macho_file: *MachO, atom_index: Atom.Index, bas }); switch (arch) { - .aarch64 => return self.resolveAarch64(macho_file, source_addr, target_addr, base_offset), - .x86_64 => return self.resolveX8664(macho_file, source_addr, target_addr, base_offset), + .aarch64 => return self.resolveAarch64(source_addr, target_addr, code), + .x86_64 => return self.resolveX8664(source_addr, target_addr, code), else => unreachable, } } fn resolveAarch64( self: Relocation, - macho_file: *MachO, source_addr: u64, target_addr: i64, - base_offset: u64, + code: []u8, ) !void { const rel_type = @intToEnum(macho.reloc_type_arm64, self.type); if (rel_type == .ARM64_RELOC_UNSIGNED) { - var buffer: [@sizeOf(u64)]u8 = undefined; - const code = blk: { - switch (self.length) { - 2 => { - mem.writeIntLittle(u32, buffer[0..4], @truncate(u32, @bitCast(u64, target_addr))); - break :blk buffer[0..4]; - }, - 3 => { - mem.writeIntLittle(u64, &buffer, @bitCast(u64, target_addr)); - break :blk &buffer; - }, - else => unreachable, - } + return switch (self.length) { + 2 => mem.writeIntLittle(u32, code[self.offset..][0..4], @truncate(u32, @bitCast(u64, target_addr))), + 3 => mem.writeIntLittle(u64, code[self.offset..][0..8], @bitCast(u64, target_addr)), + else => unreachable, }; - return macho_file.base.file.?.pwriteAll(code, base_offset + self.offset); } - var buffer: [@sizeOf(u32)]u8 = undefined; - const amt = try macho_file.base.file.?.preadAll(&buffer, base_offset + self.offset); - if (amt != buffer.len) return error.InputOutput; - + var buffer = code[self.offset..][0..4]; switch (rel_type) { .ARM64_RELOC_BRANCH26 => { const displacement = math.cast( @@ -114,10 +100,10 @@ fn resolveAarch64( .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.unconditional_branch_immediate, - ), &buffer), + ), buffer), }; inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2)); - mem.writeIntLittle(u32, &buffer, inst.toU32()); + mem.writeIntLittle(u32, buffer, inst.toU32()); }, .ARM64_RELOC_PAGE21, .ARM64_RELOC_GOT_LOAD_PAGE21, @@ -130,31 +116,31 @@ fn resolveAarch64( .pc_relative_address = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.pc_relative_address, - ), &buffer), + ), buffer), }; inst.pc_relative_address.immhi = @truncate(u19, pages >> 2); inst.pc_relative_address.immlo = @truncate(u2, pages); - mem.writeIntLittle(u32, &buffer, inst.toU32()); + mem.writeIntLittle(u32, buffer, inst.toU32()); }, .ARM64_RELOC_PAGEOFF12, .ARM64_RELOC_GOT_LOAD_PAGEOFF12, => { const narrowed = @truncate(u12, @intCast(u64, target_addr)); - if (isArithmeticOp(&buffer)) { + if (isArithmeticOp(buffer)) { var inst = aarch64.Instruction{ .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.add_subtract_immediate, - ), &buffer), + ), buffer), }; inst.add_subtract_immediate.imm12 = narrowed; - mem.writeIntLittle(u32, &buffer, inst.toU32()); + mem.writeIntLittle(u32, buffer, inst.toU32()); } else { var inst = aarch64.Instruction{ .load_store_register = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.load_store_register, - ), &buffer), + ), buffer), }; const offset: u12 = blk: { if (inst.load_store_register.size == 0) { @@ -170,7 +156,7 @@ fn resolveAarch64( } }; inst.load_store_register.offset = offset; - mem.writeIntLittle(u32, &buffer, inst.toU32()); + mem.writeIntLittle(u32, buffer, inst.toU32()); } }, .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { @@ -180,11 +166,11 @@ fn resolveAarch64( size: u2, }; const reg_info: RegInfo = blk: { - if (isArithmeticOp(&buffer)) { + if (isArithmeticOp(buffer)) { const inst = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.add_subtract_immediate, - ), &buffer); + ), buffer); break :blk .{ .rd = inst.rd, .rn = inst.rn, @@ -194,7 +180,7 @@ fn resolveAarch64( const inst = mem.bytesToValue(meta.TagPayload( aarch64.Instruction, aarch64.Instruction.load_store_register, - ), &buffer); + ), buffer); break :blk .{ .rd = inst.rt, .rn = inst.rn, @@ -214,72 +200,62 @@ fn resolveAarch64( .sf = @truncate(u1, reg_info.size), }, }; - mem.writeIntLittle(u32, &buffer, inst.toU32()); + mem.writeIntLittle(u32, buffer, inst.toU32()); }, .ARM64_RELOC_POINTER_TO_GOT => { const result = @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr)); - mem.writeIntLittle(i32, &buffer, result); + mem.writeIntLittle(i32, buffer, result); }, .ARM64_RELOC_SUBTRACTOR => unreachable, .ARM64_RELOC_ADDEND => unreachable, .ARM64_RELOC_UNSIGNED => unreachable, } - try macho_file.base.file.?.pwriteAll(&buffer, base_offset + self.offset); } fn resolveX8664( self: Relocation, - macho_file: *MachO, source_addr: u64, target_addr: i64, - base_offset: u64, + code: []u8, ) !void { const rel_type = @intToEnum(macho.reloc_type_x86_64, self.type); - var buffer: [@sizeOf(u64)]u8 = undefined; - const code = blk: { - switch (rel_type) { - .X86_64_RELOC_BRANCH, - .X86_64_RELOC_GOT, - .X86_64_RELOC_GOT_LOAD, - .X86_64_RELOC_TLV, - => { - const displacement = @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4); - mem.writeIntLittle(u32, buffer[0..4], @bitCast(u32, displacement)); - break :blk buffer[0..4]; - }, - .X86_64_RELOC_SIGNED, - .X86_64_RELOC_SIGNED_1, - .X86_64_RELOC_SIGNED_2, - .X86_64_RELOC_SIGNED_4, - => { - const correction: u3 = switch (rel_type) { - .X86_64_RELOC_SIGNED => 0, - .X86_64_RELOC_SIGNED_1 => 1, - .X86_64_RELOC_SIGNED_2 => 2, - .X86_64_RELOC_SIGNED_4 => 4, - else => unreachable, - }; - const displacement = @intCast(i32, target_addr - @intCast(i64, source_addr + correction + 4)); - mem.writeIntLittle(u32, buffer[0..4], @bitCast(u32, displacement)); - break :blk buffer[0..4]; - }, - .X86_64_RELOC_UNSIGNED => { - switch (self.length) { - 2 => { - mem.writeIntLittle(u32, buffer[0..4], @truncate(u32, @bitCast(u64, target_addr))); - break :blk buffer[0..4]; - }, - 3 => { - mem.writeIntLittle(u64, buffer[0..8], @bitCast(u64, target_addr)); - break :blk &buffer; - }, - else => unreachable, - } - }, - .X86_64_RELOC_SUBTRACTOR => unreachable, - } - }; - try macho_file.base.file.?.pwriteAll(code, base_offset + self.offset); + switch (rel_type) { + .X86_64_RELOC_BRANCH, + .X86_64_RELOC_GOT, + .X86_64_RELOC_GOT_LOAD, + .X86_64_RELOC_TLV, + => { + const displacement = @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4); + mem.writeIntLittle(u32, code[self.offset..][0..4], @bitCast(u32, displacement)); + }, + .X86_64_RELOC_SIGNED, + .X86_64_RELOC_SIGNED_1, + .X86_64_RELOC_SIGNED_2, + .X86_64_RELOC_SIGNED_4, + => { + const correction: u3 = switch (rel_type) { + .X86_64_RELOC_SIGNED => 0, + .X86_64_RELOC_SIGNED_1 => 1, + .X86_64_RELOC_SIGNED_2 => 2, + .X86_64_RELOC_SIGNED_4 => 4, + else => unreachable, + }; + const displacement = @intCast(i32, target_addr - @intCast(i64, source_addr + correction + 4)); + mem.writeIntLittle(u32, code[self.offset..][0..4], @bitCast(u32, displacement)); + }, + .X86_64_RELOC_UNSIGNED => { + switch (self.length) { + 2 => { + mem.writeIntLittle(u32, code[self.offset..][0..4], @truncate(u32, @bitCast(u64, target_addr))); + }, + 3 => { + mem.writeIntLittle(u64, code[self.offset..][0..8], @bitCast(u64, target_addr)); + }, + else => unreachable, + } + }, + .X86_64_RELOC_SUBTRACTOR => unreachable, + } } inline fn isArithmeticOp(inst: *const [4]u8) bool { diff --git a/src/main.zig b/src/main.zig index db28a5b09b..961d649d38 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3320,21 +3320,20 @@ fn buildOutputType( try server.listen(.{ .in = ip4_addr }); - while (true) { - const conn = try server.accept(); - defer conn.stream.close(); + const conn = try server.accept(); + defer conn.stream.close(); - try serve( - comp, - .{ .handle = conn.stream.handle }, - .{ .handle = conn.stream.handle }, - test_exec_args.items, - self_exe_path, - arg_mode, - all_args, - runtime_args_start, - ); - } + try serve( + comp, + .{ .handle = conn.stream.handle }, + .{ .handle = conn.stream.handle }, + test_exec_args.items, + self_exe_path, + arg_mode, + all_args, + runtime_args_start, + ); + return cleanExit(); }, } @@ -3465,9 +3464,7 @@ fn serve( const hdr = try server.receiveMessage(); switch (hdr.tag) { - .exit => { - return cleanExit(); - }, + .exit => return, .update => { assert(main_progress_node.recently_updated_child == null); tracy.frameMark(); @@ -3851,15 +3848,43 @@ fn runOrTestHotSwap( if (runtime_args_start) |i| { try argv.appendSlice(all_args[i..]); } - var child = std.ChildProcess.init(argv.items, gpa); - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + switch (builtin.target.os.tag) { + .macos, .ios, .tvos, .watchos => { + const PosixSpawn = std.os.darwin.PosixSpawn; - try child.spawn(); + var attr = try PosixSpawn.Attr.init(); + defer attr.deinit(); - return child.id; + // ASLR is probably a good default for better debugging experience/programming + // with hot-code updates in mind. However, we can also make it work with ASLR on. + const flags: u16 = std.os.darwin.POSIX_SPAWN.SETSIGDEF | + std.os.darwin.POSIX_SPAWN.SETSIGMASK | + std.os.darwin.POSIX_SPAWN.DISABLE_ASLR; + try attr.set(flags); + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]u8, argv.items.len, null); + for (argv.items, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const pid = try PosixSpawn.spawn(argv.items[0], null, attr, argv_buf, std.c.environ); + return pid; + }, + else => { + var child = std.ChildProcess.init(argv.items, gpa); + + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + try child.spawn(); + + return child.id; + }, + } } const AfterUpdateHook = union(enum) {