From 8c4a2dc1dfe05a67eb9811b8c8291c5541816a99 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Jul 2024 00:30:06 -0700 Subject: [PATCH] move non-libc stuff out of std.c --- lib/std/c.zig | 35 ++ lib/std/c/darwin.zig | 768 ++++--------------------------------------- src/link/MachO.zig | 647 +++++++++++++++++++++++++++++++++++- 3 files changed, 740 insertions(+), 710 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index c9d772a32f..967392ece3 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -9541,10 +9541,24 @@ pub const CPUFAMILY = darwin.CPUFAMILY; pub const DB_RECORDTYPE = darwin.DB_RECORDTYPE; pub const EXC = darwin.EXC; pub const EXCEPTION = darwin.EXCEPTION; +pub const MACH_MSG_TYPE = darwin.MACH_MSG_TYPE; +pub const MACH_PORT_RIGHT = darwin.MACH_PORT_RIGHT; +pub const MACH_TASK_BASIC_INFO = darwin.MACH_TASK_BASIC_INFO; +pub const MACH_TASK_BASIC_INFO_COUNT = darwin.MACH_TASK_BASIC_INFO_COUNT; +pub const MATTR = darwin.MATTR; pub const NSVersionOfRunTimeLibrary = darwin.NSVersionOfRunTimeLibrary; pub const OPEN_MAX = darwin.OPEN_MAX; +pub const POSIX_SPAWN = darwin.POSIX_SPAWN; +pub const TASK_NULL = darwin.TASK_NULL; +pub const TASK_VM_INFO = darwin.TASK_VM_INFO; +pub const TASK_VM_INFO_COUNT = darwin.TASK_VM_INFO_COUNT; +pub const THREAD_BASIC_INFO = darwin.THREAD_BASIC_INFO; +pub const THREAD_BASIC_INFO_COUNT = darwin.THREAD_BASIC_INFO_COUNT; +pub const THREAD_IDENTIFIER_INFO_COUNT = darwin.THREAD_IDENTIFIER_INFO_COUNT; +pub const THREAD_NULL = darwin.THREAD_NULL; pub const THREAD_STATE_NONE = darwin.THREAD_STATE_NONE; pub const UL = darwin.UL; +pub const VM = darwin.VM; pub const _NSGetExecutablePath = darwin._NSGetExecutablePath; pub const __getdirentries64 = darwin.__getdirentries64; pub const __ulock_wait = darwin.__ulock_wait; @@ -9562,16 +9576,21 @@ pub const dispatch_semaphore_signal = darwin.dispatch_semaphore_signal; pub const dispatch_semaphore_wait = darwin.dispatch_semaphore_wait; pub const dispatch_time = darwin.dispatch_time; pub const fcopyfile = darwin.fcopyfile; +pub const kern_return_t = darwin.kern_return_t; pub const kevent64 = darwin.kevent64; pub const mach_absolute_time = darwin.mach_absolute_time; pub const mach_continuous_time = darwin.mach_continuous_time; pub const mach_hdr = darwin.mach_hdr; pub const mach_host_self = darwin.mach_host_self; pub const mach_msg = darwin.mach_msg; +pub const mach_msg_type_number_t = darwin.mach_msg_type_number_t; pub const mach_port_allocate = darwin.mach_port_allocate; +pub const mach_port_array_t = darwin.mach_port_array_t; pub const mach_port_deallocate = darwin.mach_port_deallocate; pub const mach_port_insert_right = darwin.mach_port_insert_right; +pub const mach_port_name_t = darwin.mach_port_name_t; pub const mach_port_t = darwin.mach_port_t; +pub const mach_task_basic_info = darwin.mach_task_basic_info; pub const mach_task_self = darwin.mach_task_self; pub const mach_timebase_info = darwin.mach_timebase_info; pub const mach_vm_protect = darwin.mach_vm_protect; @@ -9602,10 +9621,12 @@ pub const posix_spawn_file_actions_addinherit_np = darwin.posix_spawn_file_actio pub const posix_spawn_file_actions_addopen = darwin.posix_spawn_file_actions_addopen; pub const posix_spawn_file_actions_destroy = darwin.posix_spawn_file_actions_destroy; pub const posix_spawn_file_actions_init = darwin.posix_spawn_file_actions_init; +pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t; pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy; pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags; pub const posix_spawnattr_init = darwin.posix_spawnattr_init; pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags; +pub const posix_spawnattr_t = darwin.posix_spawnattr_t; pub const posix_spawnp = darwin.posix_spawnp; pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np; pub const pthread_attr_set_qos_class_np = darwin.pthread_attr_set_qos_class_np; @@ -9616,17 +9637,31 @@ pub const sigaddset = darwin.sigaddset; pub const task_for_pid = darwin.task_for_pid; pub const task_get_exception_ports = darwin.task_get_exception_ports; pub const task_info = darwin.task_info; +pub const task_info_t = darwin.task_info_t; pub const task_resume = darwin.task_resume; pub const task_set_exception_ports = darwin.task_set_exception_ports; pub const task_suspend = darwin.task_suspend; pub const task_threads = darwin.task_threads; +pub const task_vm_info_data_t = darwin.task_vm_info_data_t; +pub const thread_basic_info = darwin.thread_basic_info; pub const thread_get_state = darwin.thread_get_state; +pub const thread_identifier_info = darwin.thread_identifier_info; pub const thread_info = darwin.thread_info; +pub const thread_info_t = darwin.thread_info_t; pub const thread_resume = darwin.thread_resume; pub const thread_set_state = darwin.thread_set_state; pub const vm_deallocate = darwin.vm_deallocate; pub const vm_machine_attribute = darwin.vm_machine_attribute; +pub const vm_machine_attribute_val_t = darwin.vm_machine_attribute_val_t; +pub const vm_offset_t = darwin.vm_offset_t; pub const vm_prot_t = darwin.vm_prot_t; +pub const vm_region_basic_info_64 = darwin.vm_region_basic_info_64; +pub const vm_region_extended_info = darwin.vm_region_extended_info; +pub const vm_region_info_t = darwin.vm_region_info_t; +pub const vm_region_recurse_info_t = darwin.vm_region_recurse_info_t; +pub const vm_region_submap_info_64 = darwin.vm_region_submap_info_64; +pub const vm_region_submap_short_info_64 = darwin.vm_region_submap_short_info_64; +pub const vm_region_top_info = darwin.vm_region_top_info; pub const _ksiginfo = netbsd._ksiginfo; pub const _lwp_self = netbsd._lwp_self; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 2f4cd5844a..1e184e4e8e 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -355,31 +355,22 @@ pub const MACH_PORT_RIGHT = enum(mach_port_right_t) { pub const MACH_MSG_TYPE = enum(mach_msg_type_name_t) { /// Must hold receive right MOVE_RECEIVE = 16, - /// Must hold send right(s) MOVE_SEND = 17, - /// Must hold sendonce right MOVE_SEND_ONCE = 18, - /// Must hold send right(s) COPY_SEND = 19, - /// Must hold receive right MAKE_SEND = 20, - /// Must hold receive right MAKE_SEND_ONCE = 21, - /// NOT VALID COPY_RECEIVE = 22, - /// Must hold receive right DISPOSE_RECEIVE = 24, - /// Must hold send right(s) DISPOSE_SEND = 25, - /// Must hold sendonce right DISPOSE_SEND_ONCE = 26, }; @@ -469,38 +460,45 @@ pub const vm_behavior_t = i32; pub const vm32_object_id_t = u32; pub const vm_object_id_t = u64; -pub const VM_INHERIT_SHARE: vm_inherit_t = 0; -pub const VM_INHERIT_COPY: vm_inherit_t = 1; -pub const VM_INHERIT_NONE: vm_inherit_t = 2; -pub const VM_INHERIT_DONATE_COPY: vm_inherit_t = 3; -pub const VM_INHERIT_DEFAULT = VM_INHERIT_COPY; +pub const VM = struct { + pub const INHERIT = struct { + pub const SHARE: vm_inherit_t = 0; + pub const COPY: vm_inherit_t = 1; + pub const NONE: vm_inherit_t = 2; + pub const DONATE_COPY: vm_inherit_t = 3; + pub const DEFAULT = COPY; + }; -pub const VM_BEHAVIOR_DEFAULT: vm_behavior_t = 0; -pub const VM_BEHAVIOR_RANDOM: vm_behavior_t = 1; -pub const VM_BEHAVIOR_SEQUENTIAL: vm_behavior_t = 2; -pub const VM_BEHAVIOR_RSEQNTL: vm_behavior_t = 3; + pub const BEHAVIOR = struct { + pub const DEFAULT: vm_behavior_t = 0; + pub const RANDOM: vm_behavior_t = 1; + pub const SEQUENTIAL: vm_behavior_t = 2; + pub const RSEQNTL: vm_behavior_t = 3; + pub const WILLNEED: vm_behavior_t = 4; + pub const DONTNEED: vm_behavior_t = 5; + pub const FREE: vm_behavior_t = 6; + pub const ZERO_WIRED_PAGES: vm_behavior_t = 7; + pub const REUSABLE: vm_behavior_t = 8; + pub const REUSE: vm_behavior_t = 9; + pub const CAN_REUSE: vm_behavior_t = 10; + pub const PAGEOUT: vm_behavior_t = 11; + }; -pub const VM_BEHAVIOR_WILLNEED: vm_behavior_t = 4; -pub const VM_BEHAVIOR_DONTNEED: vm_behavior_t = 5; -pub const VM_BEHAVIOR_FREE: vm_behavior_t = 6; -pub const VM_BEHAVIOR_ZERO_WIRED_PAGES: vm_behavior_t = 7; -pub const VM_BEHAVIOR_REUSABLE: vm_behavior_t = 8; -pub const VM_BEHAVIOR_REUSE: vm_behavior_t = 9; -pub const VM_BEHAVIOR_CAN_REUSE: vm_behavior_t = 10; -pub const VM_BEHAVIOR_PAGEOUT: vm_behavior_t = 11; + pub const REGION = struct { + pub const BASIC_INFO_64 = 9; + pub const EXTENDED_INFO = 13; + pub const TOP_INFO = 12; + pub const SUBMAP_INFO_COUNT_64: mach_msg_type_number_t = @sizeOf(vm_region_submap_info_64) / @sizeOf(natural_t); + pub const SUBMAP_SHORT_INFO_COUNT_64: mach_msg_type_number_t = @sizeOf(vm_region_submap_short_info_64) / @sizeOf(natural_t); + pub const BASIC_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_basic_info_64) / @sizeOf(c_int); + pub const EXTENDED_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_extended_info) / @sizeOf(natural_t); + pub const TOP_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_top_info) / @sizeOf(natural_t); + }; -pub const VM_REGION_BASIC_INFO_64 = 9; -pub const VM_REGION_EXTENDED_INFO = 13; -pub const VM_REGION_TOP_INFO = 12; -pub const VM_REGION_SUBMAP_INFO_COUNT_64: mach_msg_type_number_t = @sizeOf(vm_region_submap_info_64) / @sizeOf(natural_t); -pub const VM_REGION_SUBMAP_SHORT_INFO_COUNT_64: mach_msg_type_number_t = @sizeOf(vm_region_submap_short_info_64) / @sizeOf(natural_t); -pub const VM_REGION_BASIC_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_basic_info_64) / @sizeOf(c_int); -pub const VM_REGION_EXTENDED_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_extended_info) / @sizeOf(natural_t); -pub const VM_REGION_TOP_INFO_COUNT: mach_msg_type_number_t = @sizeOf(vm_region_top_info) / @sizeOf(natural_t); - -pub fn VM_MAKE_TAG(tag: u8) u32 { - return @as(u32, tag) << 24; -} + pub fn MAKE_TAG(tag: u8) u32 { + return @as(u32, tag) << 24; + } +}; pub const vm_region_basic_info_64 = extern struct { protection: vm_prot_t, @@ -678,29 +676,30 @@ pub const thread_identifier_info = extern struct { dispatch_qaddr: u64, }; -/// Cachability -pub const MATTR_CACHE = 1; -/// Migrability -pub const MATTR_MIGRATE = 2; -/// Replicability -pub const MATTR_REPLICATE = 4; - -/// (Generic) turn attribute off -pub const MATTR_VAL_OFF = 0; -/// (Generic) turn attribute on -pub const MATTR_VAL_ON = 1; -/// (Generic) return current value -pub const MATTR_VAL_GET = 2; -/// Flush from all caches -pub const MATTR_VAL_CACHE_FLUSH = 6; -/// Flush from data caches -pub const MATTR_VAL_DCACHE_FLUSH = 7; -/// Flush from instruction caches -pub const MATTR_VAL_ICACHE_FLUSH = 8; -/// Sync I+D caches -pub const MATTR_VAL_CACHE_SYNC = 9; -/// Get page info (stats) -pub const MATTR_VAL_GET_INFO = 10; +pub const MATTR = struct { + /// Cachability + pub const CACHE = 1; + /// Migrability + pub const MIGRATE = 2; + /// Replicability + pub const REPLICATE = 4; + /// (Generic) turn attribute off + pub const VAL_OFF = 0; + /// (Generic) turn attribute on + pub const VAL_ON = 1; + /// (Generic) return current value + pub const VAL_GET = 2; + /// Flush from all caches + pub const VAL_CACHE_FLUSH = 6; + /// Flush from data caches + pub const VAL_DCACHE_FLUSH = 7; + /// Flush from instruction caches + pub const VAL_ICACHE_FLUSH = 8; + /// Sync I+D caches + pub const VAL_CACHE_SYNC = 9; + /// Get page info (stats) + pub const VAL_GET_INFO = 10; +}; pub const TASK_VM_INFO = 22; pub const TASK_VM_INFO_COUNT: mach_msg_type_number_t = @sizeOf(task_vm_info_data_t) / @sizeOf(natural_t); @@ -804,19 +803,14 @@ pub extern "c" fn task_info( pub const mach_task_basic_info = extern struct { /// Virtual memory size (bytes) virtual_size: mach_vm_size_t, - /// Resident memory size (bytes) resident_size: mach_vm_size_t, - /// Total user run time for terminated threads user_time: time_value_t, - /// Total system run time for terminated threads system_time: time_value_t, - /// Default policy for new threads policy: policy_t, - /// Suspend count for task suspend_count: mach_vm_size_t, }; @@ -925,486 +919,6 @@ pub extern "c" fn os_unfair_lock_trylock(o: os_unfair_lock_t) bool; pub extern "c" fn os_unfair_lock_assert_owner(o: os_unfair_lock_t) void; pub extern "c" fn os_unfair_lock_assert_not_owner(o: os_unfair_lock_t) void; -pub fn getKernError(err: kern_return_t) KernE { - return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); -} - -pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError { - if (std.posix.unexpected_error_tracing) { - std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)}); - std.debug.dumpCurrentStackTrace(null); - } - return error.Unexpected; -} - -pub const MachError = error{ - /// Not enough permissions held to perform the requested kernel - /// call. - PermissionDenied, -} || std.posix.UnexpectedError; - -pub const MachTask = extern struct { - port: mach_port_name_t, - - pub fn isValid(self: MachTask) bool { - return self.port != TASK_NULL; - } - - pub fn pidForTask(self: MachTask) MachError!std.c.pid_t { - var pid: std.c.pid_t = undefined; - switch (getKernError(pid_for_task(self.port, &pid))) { - .SUCCESS => return pid, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub fn allocatePort(self: MachTask, right: MACH_PORT_RIGHT) MachError!MachTask { - var out_port: mach_port_name_t = undefined; - switch (getKernError(mach_port_allocate( - self.port, - @intFromEnum(right), - &out_port, - ))) { - .SUCCESS => return .{ .port = out_port }, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub fn deallocatePort(self: MachTask, port: MachTask) void { - _ = getKernError(mach_port_deallocate(self.port, port.port)); - } - - pub fn insertRight(self: MachTask, port: MachTask, msg: MACH_MSG_TYPE) !void { - switch (getKernError(mach_port_insert_right( - self.port, - port.port, - port.port, - @intFromEnum(msg), - ))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub const PortInfo = struct { - mask: exception_mask_t, - masks: [EXC.TYPES_COUNT]exception_mask_t, - ports: [EXC.TYPES_COUNT]mach_port_t, - behaviors: [EXC.TYPES_COUNT]exception_behavior_t, - flavors: [EXC.TYPES_COUNT]thread_state_flavor_t, - count: mach_msg_type_number_t, - }; - - pub fn getExceptionPorts(self: MachTask, mask: exception_mask_t) !PortInfo { - var info = PortInfo{ - .mask = mask, - .masks = undefined, - .ports = undefined, - .behaviors = undefined, - .flavors = undefined, - .count = 0, - }; - info.count = info.ports.len / @sizeOf(mach_port_t); - - switch (getKernError(task_get_exception_ports( - self.port, - info.mask, - &info.masks, - &info.count, - &info.ports, - &info.behaviors, - &info.flavors, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub fn setExceptionPorts( - self: MachTask, - mask: exception_mask_t, - new_port: MachTask, - behavior: exception_behavior_t, - new_flavor: thread_state_flavor_t, - ) !void { - switch (getKernError(task_set_exception_ports( - self.port, - mask, - new_port.port, - behavior, - new_flavor, - ))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub const RegionInfo = struct { - pub const Tag = enum { - basic, - extended, - top, - }; - - base_addr: u64, - tag: Tag, - info: union { - basic: vm_region_basic_info_64, - extended: vm_region_extended_info, - top: vm_region_top_info, - }, - }; - - pub fn getRegionInfo( - task: MachTask, - address: u64, - len: usize, - tag: RegionInfo.Tag, - ) MachError!RegionInfo { - var info: RegionInfo = .{ - .base_addr = address, - .tag = tag, - .info = undefined, - }; - switch (tag) { - .basic => info.info = .{ .basic = undefined }, - .extended => info.info = .{ .extended = undefined }, - .top => info.info = .{ .top = undefined }, - } - var base_len: mach_vm_size_t = if (len == 1) 2 else len; - var objname: mach_port_t = undefined; - var count: mach_msg_type_number_t = switch (tag) { - .basic => VM_REGION_BASIC_INFO_COUNT, - .extended => VM_REGION_EXTENDED_INFO_COUNT, - .top => VM_REGION_TOP_INFO_COUNT, - }; - switch (getKernError(mach_vm_region( - task.port, - &info.base_addr, - &base_len, - switch (tag) { - .basic => VM_REGION_BASIC_INFO_64, - .extended => VM_REGION_EXTENDED_INFO, - .top => VM_REGION_TOP_INFO, - }, - switch (tag) { - .basic => @as(vm_region_info_t, @ptrCast(&info.info.basic)), - .extended => @as(vm_region_info_t, @ptrCast(&info.info.extended)), - .top => @as(vm_region_info_t, @ptrCast(&info.info.top)), - }, - &count, - &objname, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub const RegionSubmapInfo = struct { - pub const Tag = enum { - short, - full, - }; - - tag: Tag, - base_addr: u64, - info: union { - short: vm_region_submap_short_info_64, - full: vm_region_submap_info_64, - }, - }; - - pub fn getRegionSubmapInfo( - task: MachTask, - address: u64, - len: usize, - nesting_depth: u32, - tag: RegionSubmapInfo.Tag, - ) MachError!RegionSubmapInfo { - var info: RegionSubmapInfo = .{ - .base_addr = address, - .tag = tag, - .info = undefined, - }; - switch (tag) { - .short => info.info = .{ .short = undefined }, - .full => info.info = .{ .full = undefined }, - } - var nesting = nesting_depth; - var base_len: mach_vm_size_t = if (len == 1) 2 else len; - var count: mach_msg_type_number_t = switch (tag) { - .short => VM_REGION_SUBMAP_SHORT_INFO_COUNT_64, - .full => VM_REGION_SUBMAP_INFO_COUNT_64, - }; - switch (getKernError(mach_vm_region_recurse( - task.port, - &info.base_addr, - &base_len, - &nesting, - switch (tag) { - .short => @as(vm_region_recurse_info_t, @ptrCast(&info.info.short)), - .full => @as(vm_region_recurse_info_t, @ptrCast(&info.info.full)), - }, - &count, - ))) { - .SUCCESS => return info, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!vm_prot_t { - const info = try task.getRegionSubmapInfo(address, len, 0, .short); - return info.info.short.protection; - } - - pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void { - return task.setProtectionImpl(address, len, true, prot); - } - - pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: vm_prot_t) MachError!void { - return task.setProtectionImpl(address, len, false, prot); - } - - fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: vm_prot_t) MachError!void { - switch (getKernError(mach_vm_protect(task.port, address, len, @intFromBool(set_max), prot))) { - .SUCCESS => return, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - } - - /// Will write to VM even if current protection attributes specifically prohibit - /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY - /// variant, and resetting after a successful or unsuccessful write. - pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { - const curr_prot = try task.getCurrProtection(address, buf.len); - try task.setCurrProtection( - address, - buf.len, - PROT.READ | PROT.WRITE | PROT.COPY, - ); - defer { - task.setCurrProtection(address, buf.len, curr_prot) catch {}; - } - return task.writeMem(address, buf, arch); - } - - pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { - const count = buf.len; - var total_written: usize = 0; - var curr_addr = address; - const page_size = try getPageSize(task); // TODO we probably can assume value here - var out_buf = buf[0..]; - - while (total_written < count) { - const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); - switch (getKernError(mach_vm_write( - task.port, - curr_addr, - @intFromPtr(out_buf.ptr), - @as(mach_msg_type_number_t, @intCast(curr_size)), - ))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - - switch (arch) { - .aarch64 => { - var mattr_value: vm_machine_attribute_val_t = MATTR_VAL_CACHE_FLUSH; - switch (getKernError(vm_machine_attribute( - task.port, - curr_addr, - curr_size, - MATTR_CACHE, - &mattr_value, - ))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - }, - .x86_64 => {}, - else => unreachable, - } - - out_buf = out_buf[curr_size..]; - total_written += curr_size; - curr_addr += curr_size; - } - - return total_written; - } - - pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { - const count = buf.len; - var total_read: usize = 0; - var curr_addr = address; - const page_size = try getPageSize(task); // TODO we probably can assume value here - var out_buf = buf[0..]; - - while (total_read < count) { - const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); - var curr_bytes_read: mach_msg_type_number_t = 0; - var vm_memory: vm_offset_t = undefined; - switch (getKernError(mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - - @memcpy(out_buf[0..curr_bytes_read], @as([*]const u8, @ptrFromInt(vm_memory))); - _ = vm_deallocate(mach_task_self(), vm_memory, curr_bytes_read); - - out_buf = out_buf[curr_bytes_read..]; - curr_addr += curr_bytes_read; - total_read += curr_bytes_read; - } - - return total_read; - } - - fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { - var left = count; - if (page_size > 0) { - const page_offset = address % page_size; - const bytes_left_in_page = page_size - page_offset; - if (count > bytes_left_in_page) { - left = bytes_left_in_page; - } - } - return left; - } - - fn getPageSize(task: MachTask) MachError!usize { - if (task.isValid()) { - var info_count = TASK_VM_INFO_COUNT; - var vm_info: task_vm_info_data_t = undefined; - switch (getKernError(task_info( - task.port, - TASK_VM_INFO, - @as(task_info_t, @ptrCast(&vm_info)), - &info_count, - ))) { - .SUCCESS => return @as(usize, @intCast(vm_info.page_size)), - else => {}, - } - } - var page_size: vm_size_t = undefined; - switch (getKernError(_host_page_size(mach_host_self(), &page_size))) { - .SUCCESS => return page_size, - else => |err| return unexpectedKernError(err), - } - } - - pub fn basicTaskInfo(task: MachTask) MachError!mach_task_basic_info { - var info: mach_task_basic_info = undefined; - var count = MACH_TASK_BASIC_INFO_COUNT; - switch (getKernError(task_info( - task.port, - MACH_TASK_BASIC_INFO, - @as(task_info_t, @ptrCast(&info)), - &count, - ))) { - .SUCCESS => return info, - else => |err| return unexpectedKernError(err), - } - } - - pub fn @"resume"(task: MachTask) MachError!void { - switch (getKernError(task_resume(task.port))) { - .SUCCESS => {}, - else => |err| return unexpectedKernError(err), - } - } - - pub fn @"suspend"(task: MachTask) MachError!void { - switch (getKernError(task_suspend(task.port))) { - .SUCCESS => {}, - else => |err| return unexpectedKernError(err), - } - } - - const ThreadList = struct { - buf: []MachThread, - - pub fn deinit(list: ThreadList) void { - const self_task = machTaskForSelf(); - _ = vm_deallocate( - self_task.port, - @intFromPtr(list.buf.ptr), - @as(vm_size_t, @intCast(list.buf.len * @sizeOf(mach_port_t))), - ); - } - }; - - pub fn getThreads(task: MachTask) MachError!ThreadList { - var thread_list: mach_port_array_t = undefined; - var thread_count: mach_msg_type_number_t = undefined; - switch (getKernError(task_threads(task.port, &thread_list, &thread_count))) { - .SUCCESS => return ThreadList{ .buf = @as([*]MachThread, @ptrCast(thread_list))[0..thread_count] }, - else => |err| return unexpectedKernError(err), - } - } -}; - -pub const MachThread = extern struct { - port: mach_port_t, - - pub fn isValid(thread: MachThread) bool { - return thread.port != THREAD_NULL; - } - - pub fn getBasicInfo(thread: MachThread) MachError!thread_basic_info { - var info: thread_basic_info = undefined; - var count = THREAD_BASIC_INFO_COUNT; - switch (getKernError(thread_info( - thread.port, - THREAD_BASIC_INFO, - @as(thread_info_t, @ptrCast(&info)), - &count, - ))) { - .SUCCESS => return info, - else => |err| return unexpectedKernError(err), - } - } - - pub fn getIdentifierInfo(thread: MachThread) MachError!thread_identifier_info { - var info: thread_identifier_info = undefined; - var count = THREAD_IDENTIFIER_INFO_COUNT; - switch (getKernError(thread_info( - thread.port, - THREAD_IDENTIFIER_INFO, - @as(thread_info_t, @ptrCast(&info)), - &count, - ))) { - .SUCCESS => return info, - else => |err| return unexpectedKernError(err), - } - } -}; - -pub fn machTaskForPid(pid: std.c.pid_t) MachError!MachTask { - var port: mach_port_name_t = undefined; - switch (getKernError(task_for_pid(mach_task_self(), pid, &port))) { - .SUCCESS => {}, - .FAILURE => return error.PermissionDenied, - else => |err| return unexpectedKernError(err), - } - return MachTask{ .port = port }; -} - -pub fn machTaskForSelf() MachTask { - return .{ .port = mach_task_self() }; -} - pub const os_signpost_id_t = u64; pub const OS_SIGNPOST_ID_NULL: os_signpost_id_t = 0; @@ -1503,168 +1017,6 @@ pub const KEVENT_FLAG_ERROR_EVENTS = 0x002; pub const SYSPROTO_EVENT = 1; pub const SYSPROTO_CONTROL = 2; -/// Kernel return values -pub const KernE = enum(u32) { - SUCCESS = 0, - /// Specified address is not currently valid - INVALID_ADDRESS = 1, - /// Specified memory is valid, but does not permit the - /// required forms of access. - PROTECTION_FAILURE = 2, - /// The address range specified is already in use, or - /// no address range of the size specified could be - /// found. - NO_SPACE = 3, - /// The function requested was not applicable to this - /// type of argument, or an argument is invalid - INVALID_ARGUMENT = 4, - /// The function could not be performed. A catch-all. - FAILURE = 5, - /// A system resource could not be allocated to fulfill - /// this request. This failure may not be permanent. - RESOURCE_SHORTAGE = 6, - /// The task in question does not hold receive rights - /// for the port argument. - NOT_RECEIVER = 7, - /// Bogus access restriction. - NO_ACCESS = 8, - /// During a page fault, the target address refers to a - /// memory object that has been destroyed. This - /// failure is permanent. - MEMORY_FAILURE = 9, - /// During a page fault, the memory object indicated - /// that the data could not be returned. This failure - /// may be temporary; future attempts to access this - /// same data may succeed, as defined by the memory - /// object. - MEMORY_ERROR = 10, - /// The receive right is already a member of the portset. - ALREADY_IN_SET = 11, - /// The receive right is not a member of a port set. - NOT_IN_SET = 12, - /// The name already denotes a right in the task. - NAME_EXISTS = 13, - /// The operation was aborted. Ipc code will - /// catch this and reflect it as a message error. - ABORTED = 14, - /// The name doesn't denote a right in the task. - INVALID_NAME = 15, - /// Target task isn't an active task. - INVALID_TASK = 16, - /// The name denotes a right, but not an appropriate right. - INVALID_RIGHT = 17, - /// A blatant range error. - INVALID_VALUE = 18, - /// Operation would overflow limit on user-references. - UREFS_OVERFLOW = 19, - /// The supplied (port) capability is improper. - INVALID_CAPABILITY = 20, - /// The task already has send or receive rights - /// for the port under another name. - RIGHT_EXISTS = 21, - /// Target host isn't actually a host. - INVALID_HOST = 22, - /// An attempt was made to supply "precious" data - /// for memory that is already present in a - /// memory object. - MEMORY_PRESENT = 23, - /// A page was requested of a memory manager via - /// memory_object_data_request for an object using - /// a MEMORY_OBJECT_COPY_CALL strategy, with the - /// VM_PROT_WANTS_COPY flag being used to specify - /// that the page desired is for a copy of the - /// object, and the memory manager has detected - /// the page was pushed into a copy of the object - /// while the kernel was walking the shadow chain - /// from the copy to the object. This error code - /// is delivered via memory_object_data_error - /// and is handled by the kernel (it forces the - /// kernel to restart the fault). It will not be - /// seen by users. - MEMORY_DATA_MOVED = 24, - /// A strategic copy was attempted of an object - /// upon which a quicker copy is now possible. - /// The caller should retry the copy using - /// vm_object_copy_quickly. This error code - /// is seen only by the kernel. - MEMORY_RESTART_COPY = 25, - /// An argument applied to assert processor set privilege - /// was not a processor set control port. - INVALID_PROCESSOR_SET = 26, - /// The specified scheduling attributes exceed the thread's - /// limits. - POLICY_LIMIT = 27, - /// The specified scheduling policy is not currently - /// enabled for the processor set. - INVALID_POLICY = 28, - /// The external memory manager failed to initialize the - /// memory object. - INVALID_OBJECT = 29, - /// A thread is attempting to wait for an event for which - /// there is already a waiting thread. - ALREADY_WAITING = 30, - /// An attempt was made to destroy the default processor - /// set. - DEFAULT_SET = 31, - /// An attempt was made to fetch an exception port that is - /// protected, or to abort a thread while processing a - /// protected exception. - EXCEPTION_PROTECTED = 32, - /// A ledger was required but not supplied. - INVALID_LEDGER = 33, - /// The port was not a memory cache control port. - INVALID_MEMORY_CONTROL = 34, - /// An argument supplied to assert security privilege - /// was not a host security port. - INVALID_SECURITY = 35, - /// thread_depress_abort was called on a thread which - /// was not currently depressed. - NOT_DEPRESSED = 36, - /// Object has been terminated and is no longer available - TERMINATED = 37, - /// Lock set has been destroyed and is no longer available. - LOCK_SET_DESTROYED = 38, - /// The thread holding the lock terminated before releasing - /// the lock - LOCK_UNSTABLE = 39, - /// The lock is already owned by another thread - LOCK_OWNED = 40, - /// The lock is already owned by the calling thread - LOCK_OWNED_SELF = 41, - /// Semaphore has been destroyed and is no longer available. - SEMAPHORE_DESTROYED = 42, - /// Return from RPC indicating the target server was - /// terminated before it successfully replied - RPC_SERVER_TERMINATED = 43, - /// Terminate an orphaned activation. - RPC_TERMINATE_ORPHAN = 44, - /// Allow an orphaned activation to continue executing. - RPC_CONTINUE_ORPHAN = 45, - /// Empty thread activation (No thread linked to it) - NOT_SUPPORTED = 46, - /// Remote node down or inaccessible. - NODE_DOWN = 47, - /// A signalled thread was not actually waiting. - NOT_WAITING = 48, - /// Some thread-oriented operation (semaphore_wait) timed out - OPERATION_TIMED_OUT = 49, - /// During a page fault, indicates that the page was rejected - /// as a result of a signature check. - CODESIGN_ERROR = 50, - /// The requested property cannot be changed at this time. - POLICY_STATIC = 51, - /// The provided buffer is of insufficient size for the requested data. - INSUFFICIENT_BUFFER_SIZE = 52, - /// Denied by security policy - DENIED = 53, - /// The KC on which the function is operating is missing - MISSING_KC = 54, - /// The KC on which the function is operating is invalid - INVALID_KC = 55, - /// A search or query operation did not return a result - NOT_FOUND = 56, - _, -}; pub const mach_msg_return_t = kern_return_t; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 08cbc9cb67..b9641ee76c 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3779,7 +3779,7 @@ pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols { pub fn ptraceAttach(self: *MachO, pid: std.posix.pid_t) !void { if (!is_hot_update_compatible) return; - const mach_task = try std.c.machTaskForPid(pid); + const mach_task = try machTaskForPid(pid); log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task }); self.hot_state.mach_task = mach_task; @@ -4058,7 +4058,7 @@ pub const LiteralPool = struct { }; const HotUpdateState = struct { - mach_task: ?std.c.MachTask = null, + mach_task: ?MachTask = null, }; pub const SymtabCtx = struct { @@ -4562,3 +4562,646 @@ const UnwindInfo = @import("MachO/UnwindInfo.zig"); const WeakBind = bind.WeakBind; const ZigGotSection = synthetic.ZigGotSection; const ZigObject = @import("MachO/ZigObject.zig"); + +pub const MachError = error{ + /// Not enough permissions held to perform the requested kernel + /// call. + PermissionDenied, +} || std.posix.UnexpectedError; + +pub const MachTask = extern struct { + port: std.c.mach_port_name_t, + + pub fn isValid(self: MachTask) bool { + return self.port != std.c.TASK_NULL; + } + + pub fn pidForTask(self: MachTask) MachError!std.c.pid_t { + var pid: std.c.pid_t = undefined; + switch (getKernError(std.c.pid_for_task(self.port, &pid))) { + .SUCCESS => return pid, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn allocatePort(self: MachTask, right: std.c.MACH_PORT_RIGHT) MachError!MachTask { + var out_port: std.c.mach_port_name_t = undefined; + switch (getKernError(std.c.mach_port_allocate( + self.port, + @intFromEnum(right), + &out_port, + ))) { + .SUCCESS => return .{ .port = out_port }, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn deallocatePort(self: MachTask, port: MachTask) void { + _ = getKernError(std.c.mach_port_deallocate(self.port, port.port)); + } + + pub fn insertRight(self: MachTask, port: MachTask, msg: std.c.MACH_MSG_TYPE) !void { + switch (getKernError(std.c.mach_port_insert_right( + self.port, + port.port, + port.port, + @intFromEnum(msg), + ))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const PortInfo = struct { + mask: std.c.exception_mask_t, + masks: [std.c.EXC.TYPES_COUNT]std.c.exception_mask_t, + ports: [std.c.EXC.TYPES_COUNT]std.c.mach_port_t, + behaviors: [std.c.EXC.TYPES_COUNT]std.c.exception_behavior_t, + flavors: [std.c.EXC.TYPES_COUNT]std.c.thread_state_flavor_t, + count: std.c.mach_msg_type_number_t, + }; + + pub fn getExceptionPorts(self: MachTask, mask: std.c.exception_mask_t) !PortInfo { + var info: PortInfo = .{ + .mask = mask, + .masks = undefined, + .ports = undefined, + .behaviors = undefined, + .flavors = undefined, + .count = 0, + }; + info.count = info.ports.len / @sizeOf(std.c.mach_port_t); + + switch (getKernError(std.c.task_get_exception_ports( + self.port, + info.mask, + &info.masks, + &info.count, + &info.ports, + &info.behaviors, + &info.flavors, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn setExceptionPorts( + self: MachTask, + mask: std.c.exception_mask_t, + new_port: MachTask, + behavior: std.c.exception_behavior_t, + new_flavor: std.c.thread_state_flavor_t, + ) !void { + switch (getKernError(std.c.task_set_exception_ports( + self.port, + mask, + new_port.port, + behavior, + new_flavor, + ))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const RegionInfo = struct { + pub const Tag = enum { + basic, + extended, + top, + }; + + base_addr: u64, + tag: Tag, + info: union { + basic: std.c.vm_region_basic_info_64, + extended: std.c.vm_region_extended_info, + top: std.c.vm_region_top_info, + }, + }; + + pub fn getRegionInfo( + task: MachTask, + address: u64, + len: usize, + tag: RegionInfo.Tag, + ) MachError!RegionInfo { + var info: RegionInfo = .{ + .base_addr = address, + .tag = tag, + .info = undefined, + }; + switch (tag) { + .basic => info.info = .{ .basic = undefined }, + .extended => info.info = .{ .extended = undefined }, + .top => info.info = .{ .top = undefined }, + } + var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; + var objname: std.c.mach_port_t = undefined; + var count: std.c.mach_msg_type_number_t = switch (tag) { + .basic => std.c.VM.REGION.BASIC_INFO_COUNT, + .extended => std.c.VM.REGION.EXTENDED_INFO_COUNT, + .top => std.c.VM.REGION.TOP_INFO_COUNT, + }; + switch (getKernError(std.c.mach_vm_region( + task.port, + &info.base_addr, + &base_len, + switch (tag) { + .basic => std.c.VM.REGION.BASIC_INFO_64, + .extended => std.c.VM.REGION.EXTENDED_INFO, + .top => std.c.VM.REGION.TOP_INFO, + }, + switch (tag) { + .basic => @as(std.c.vm_region_info_t, @ptrCast(&info.info.basic)), + .extended => @as(std.c.vm_region_info_t, @ptrCast(&info.info.extended)), + .top => @as(std.c.vm_region_info_t, @ptrCast(&info.info.top)), + }, + &count, + &objname, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub const RegionSubmapInfo = struct { + pub const Tag = enum { + short, + full, + }; + + tag: Tag, + base_addr: u64, + info: union { + short: std.c.vm_region_submap_short_info_64, + full: std.c.vm_region_submap_info_64, + }, + }; + + pub fn getRegionSubmapInfo( + task: MachTask, + address: u64, + len: usize, + nesting_depth: u32, + tag: RegionSubmapInfo.Tag, + ) MachError!RegionSubmapInfo { + var info: RegionSubmapInfo = .{ + .base_addr = address, + .tag = tag, + .info = undefined, + }; + switch (tag) { + .short => info.info = .{ .short = undefined }, + .full => info.info = .{ .full = undefined }, + } + var nesting = nesting_depth; + var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; + var count: std.c.mach_msg_type_number_t = switch (tag) { + .short => std.c.VM.REGION.SUBMAP_SHORT_INFO_COUNT_64, + .full => std.c.VM.REGION.SUBMAP_INFO_COUNT_64, + }; + switch (getKernError(std.c.mach_vm_region_recurse( + task.port, + &info.base_addr, + &base_len, + &nesting, + switch (tag) { + .short => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.short)), + .full => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.full)), + }, + &count, + ))) { + .SUCCESS => return info, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t { + const info = try task.getRegionSubmapInfo(address, len, 0, .short); + return info.info.short.protection; + } + + pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, true, prot); + } + + pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, false, prot); + } + + fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void { + switch (getKernError(std.c.mach_vm_protect(task.port, address, len, @intFromBool(set_max), prot))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + } + + /// Will write to VM even if current protection attributes specifically prohibit + /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY + /// variant, and resetting after a successful or unsuccessful write. + pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { + const curr_prot = try task.getCurrProtection(address, buf.len); + try task.setCurrProtection( + address, + buf.len, + std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY, + ); + defer { + task.setCurrProtection(address, buf.len, curr_prot) catch {}; + } + return task.writeMem(address, buf, arch); + } + + pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { + const count = buf.len; + var total_written: usize = 0; + var curr_addr = address; + const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here + var out_buf = buf[0..]; + + while (total_written < count) { + const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); + switch (getKernError(std.c.mach_vm_write( + task.port, + curr_addr, + @intFromPtr(out_buf.ptr), + @as(std.c.mach_msg_type_number_t, @intCast(curr_size)), + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + + switch (arch) { + .aarch64 => { + var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR.VAL_CACHE_FLUSH; + switch (getKernError(std.c.vm_machine_attribute( + task.port, + curr_addr, + curr_size, + std.c.MATTR.CACHE, + &mattr_value, + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + }, + .x86_64 => {}, + else => unreachable, + } + + out_buf = out_buf[curr_size..]; + total_written += curr_size; + curr_addr += curr_size; + } + + return total_written; + } + + pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { + const count = buf.len; + var total_read: usize = 0; + var curr_addr = address; + const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here + var out_buf = buf[0..]; + + while (total_read < count) { + const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); + var curr_bytes_read: std.c.mach_msg_type_number_t = 0; + var vm_memory: std.c.vm_offset_t = undefined; + switch (getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + + @memcpy(out_buf[0..curr_bytes_read], @as([*]const u8, @ptrFromInt(vm_memory))); + _ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read); + + out_buf = out_buf[curr_bytes_read..]; + curr_addr += curr_bytes_read; + total_read += curr_bytes_read; + } + + return total_read; + } + + fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { + var left = count; + if (page_size > 0) { + const page_offset = address % page_size; + const bytes_left_in_page = page_size - page_offset; + if (count > bytes_left_in_page) { + left = bytes_left_in_page; + } + } + return left; + } + + fn getPageSize(task: MachTask) MachError!usize { + if (task.isValid()) { + var info_count = std.c.TASK_VM_INFO_COUNT; + var vm_info: std.c.task_vm_info_data_t = undefined; + switch (getKernError(std.c.task_info( + task.port, + std.c.TASK_VM_INFO, + @as(std.c.task_info_t, @ptrCast(&vm_info)), + &info_count, + ))) { + .SUCCESS => return @as(usize, @intCast(vm_info.page_size)), + else => {}, + } + } + var page_size: std.c.vm_size_t = undefined; + switch (getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) { + .SUCCESS => return page_size, + else => |err| return unexpectedKernError(err), + } + } + + pub fn basicTaskInfo(task: MachTask) MachError!std.c.mach_task_basic_info { + var info: std.c.mach_task_basic_info = undefined; + var count = std.c.MACH_TASK_BASIC_INFO_COUNT; + switch (getKernError(std.c.task_info( + task.port, + std.c.MACH_TASK_BASIC_INFO, + @as(std.c.task_info_t, @ptrCast(&info)), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } + + pub fn @"resume"(task: MachTask) MachError!void { + switch (getKernError(std.c.task_resume(task.port))) { + .SUCCESS => {}, + else => |err| return unexpectedKernError(err), + } + } + + pub fn @"suspend"(task: MachTask) MachError!void { + switch (getKernError(std.c.task_suspend(task.port))) { + .SUCCESS => {}, + else => |err| return unexpectedKernError(err), + } + } + + const ThreadList = struct { + buf: []MachThread, + + pub fn deinit(list: ThreadList) void { + const self_task = machTaskForSelf(); + _ = std.c.vm_deallocate( + self_task.port, + @intFromPtr(list.buf.ptr), + @as(std.c.vm_size_t, @intCast(list.buf.len * @sizeOf(std.c.mach_port_t))), + ); + } + }; + + pub fn getThreads(task: MachTask) MachError!ThreadList { + var thread_list: std.c.mach_port_array_t = undefined; + var thread_count: std.c.mach_msg_type_number_t = undefined; + switch (getKernError(std.c.task_threads(task.port, &thread_list, &thread_count))) { + .SUCCESS => return ThreadList{ .buf = @as([*]MachThread, @ptrCast(thread_list))[0..thread_count] }, + else => |err| return unexpectedKernError(err), + } + } +}; + +pub const MachThread = extern struct { + port: std.c.mach_port_t, + + pub fn isValid(thread: MachThread) bool { + return thread.port != std.c.THREAD_NULL; + } + + pub fn getBasicInfo(thread: MachThread) MachError!std.c.thread_basic_info { + var info: std.c.thread_basic_info = undefined; + var count = std.c.THREAD_BASIC_INFO_COUNT; + switch (getKernError(std.c.thread_info( + thread.port, + std.c.THREAD_BASIC_INFO, + @as(std.c.thread_info_t, @ptrCast(&info)), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } + + pub fn getIdentifierInfo(thread: MachThread) MachError!std.c.thread_identifier_info { + var info: std.c.thread_identifier_info = undefined; + var count = std.c.THREAD_IDENTIFIER_INFO_COUNT; + switch (getKernError(std.c.thread_info( + thread.port, + std.c.THREAD_IDENTIFIER_INFO, + @as(std.c.thread_info_t, @ptrCast(&info)), + &count, + ))) { + .SUCCESS => return info, + else => |err| return unexpectedKernError(err), + } + } +}; + +pub fn machTaskForPid(pid: std.c.pid_t) MachError!MachTask { + var port: std.c.mach_port_name_t = undefined; + switch (getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| return unexpectedKernError(err), + } + return MachTask{ .port = port }; +} + +pub fn machTaskForSelf() MachTask { + return .{ .port = std.c.mach_task_self() }; +} + +pub fn getKernError(err: std.c.kern_return_t) KernE { + return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); +} + +pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError { + if (std.posix.unexpected_error_tracing) { + std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)}); + std.debug.dumpCurrentStackTrace(null); + } + return error.Unexpected; +} + +/// Kernel return values +pub const KernE = enum(u32) { + SUCCESS = 0, + /// Specified address is not currently valid + INVALID_ADDRESS = 1, + /// Specified memory is valid, but does not permit the + /// required forms of access. + PROTECTION_FAILURE = 2, + /// The address range specified is already in use, or + /// no address range of the size specified could be + /// found. + NO_SPACE = 3, + /// The function requested was not applicable to this + /// type of argument, or an argument is invalid + INVALID_ARGUMENT = 4, + /// The function could not be performed. A catch-all. + FAILURE = 5, + /// A system resource could not be allocated to fulfill + /// this request. This failure may not be permanent. + RESOURCE_SHORTAGE = 6, + /// The task in question does not hold receive rights + /// for the port argument. + NOT_RECEIVER = 7, + /// Bogus access restriction. + NO_ACCESS = 8, + /// During a page fault, the target address refers to a + /// memory object that has been destroyed. This + /// failure is permanent. + MEMORY_FAILURE = 9, + /// During a page fault, the memory object indicated + /// that the data could not be returned. This failure + /// may be temporary; future attempts to access this + /// same data may succeed, as defined by the memory + /// object. + MEMORY_ERROR = 10, + /// The receive right is already a member of the portset. + ALREADY_IN_SET = 11, + /// The receive right is not a member of a port set. + NOT_IN_SET = 12, + /// The name already denotes a right in the task. + NAME_EXISTS = 13, + /// The operation was aborted. Ipc code will + /// catch this and reflect it as a message error. + ABORTED = 14, + /// The name doesn't denote a right in the task. + INVALID_NAME = 15, + /// Target task isn't an active task. + INVALID_TASK = 16, + /// The name denotes a right, but not an appropriate right. + INVALID_RIGHT = 17, + /// A blatant range error. + INVALID_VALUE = 18, + /// Operation would overflow limit on user-references. + UREFS_OVERFLOW = 19, + /// The supplied (port) capability is improper. + INVALID_CAPABILITY = 20, + /// The task already has send or receive rights + /// for the port under another name. + RIGHT_EXISTS = 21, + /// Target host isn't actually a host. + INVALID_HOST = 22, + /// An attempt was made to supply "precious" data + /// for memory that is already present in a + /// memory object. + MEMORY_PRESENT = 23, + /// A page was requested of a memory manager via + /// memory_object_data_request for an object using + /// a MEMORY_OBJECT_COPY_CALL strategy, with the + /// VM_PROT_WANTS_COPY flag being used to specify + /// that the page desired is for a copy of the + /// object, and the memory manager has detected + /// the page was pushed into a copy of the object + /// while the kernel was walking the shadow chain + /// from the copy to the object. This error code + /// is delivered via memory_object_data_error + /// and is handled by the kernel (it forces the + /// kernel to restart the fault). It will not be + /// seen by users. + MEMORY_DATA_MOVED = 24, + /// A strategic copy was attempted of an object + /// upon which a quicker copy is now possible. + /// The caller should retry the copy using + /// vm_object_copy_quickly. This error code + /// is seen only by the kernel. + MEMORY_RESTART_COPY = 25, + /// An argument applied to assert processor set privilege + /// was not a processor set control port. + INVALID_PROCESSOR_SET = 26, + /// The specified scheduling attributes exceed the thread's + /// limits. + POLICY_LIMIT = 27, + /// The specified scheduling policy is not currently + /// enabled for the processor set. + INVALID_POLICY = 28, + /// The external memory manager failed to initialize the + /// memory object. + INVALID_OBJECT = 29, + /// A thread is attempting to wait for an event for which + /// there is already a waiting thread. + ALREADY_WAITING = 30, + /// An attempt was made to destroy the default processor + /// set. + DEFAULT_SET = 31, + /// An attempt was made to fetch an exception port that is + /// protected, or to abort a thread while processing a + /// protected exception. + EXCEPTION_PROTECTED = 32, + /// A ledger was required but not supplied. + INVALID_LEDGER = 33, + /// The port was not a memory cache control port. + INVALID_MEMORY_CONTROL = 34, + /// An argument supplied to assert security privilege + /// was not a host security port. + INVALID_SECURITY = 35, + /// thread_depress_abort was called on a thread which + /// was not currently depressed. + NOT_DEPRESSED = 36, + /// Object has been terminated and is no longer available + TERMINATED = 37, + /// Lock set has been destroyed and is no longer available. + LOCK_SET_DESTROYED = 38, + /// The thread holding the lock terminated before releasing + /// the lock + LOCK_UNSTABLE = 39, + /// The lock is already owned by another thread + LOCK_OWNED = 40, + /// The lock is already owned by the calling thread + LOCK_OWNED_SELF = 41, + /// Semaphore has been destroyed and is no longer available. + SEMAPHORE_DESTROYED = 42, + /// Return from RPC indicating the target server was + /// terminated before it successfully replied + RPC_SERVER_TERMINATED = 43, + /// Terminate an orphaned activation. + RPC_TERMINATE_ORPHAN = 44, + /// Allow an orphaned activation to continue executing. + RPC_CONTINUE_ORPHAN = 45, + /// Empty thread activation (No thread linked to it) + NOT_SUPPORTED = 46, + /// Remote node down or inaccessible. + NODE_DOWN = 47, + /// A signalled thread was not actually waiting. + NOT_WAITING = 48, + /// Some thread-oriented operation (semaphore_wait) timed out + OPERATION_TIMED_OUT = 49, + /// During a page fault, indicates that the page was rejected + /// as a result of a signature check. + CODESIGN_ERROR = 50, + /// The requested property cannot be changed at this time. + POLICY_STATIC = 51, + /// The provided buffer is of insufficient size for the requested data. + INSUFFICIENT_BUFFER_SIZE = 52, + /// Denied by security policy + DENIED = 53, + /// The KC on which the function is operating is missing + MISSING_KC = 54, + /// The KC on which the function is operating is invalid + INVALID_KC = 55, + /// A search or query operation did not return a result + NOT_FOUND = 56, + _, +};