diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/std/c/darwin.zig | 25 | ||||
| -rw-r--r-- | lib/std/macho.zig | 41 | ||||
| -rw-r--r-- | lib/std/os.zig | 2 | ||||
| -rw-r--r-- | lib/std/os/darwin.zig | 216 |
4 files changed, 245 insertions, 39 deletions
diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 4a461cda97..04a17b6f4f 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -201,9 +201,9 @@ pub const vm_object_id_t = u64; pub const vm_region_submap_info_64 = extern struct { // present across protection - protection: std.macho.vm_prot_t, + protection: vm_prot_t, // max avail through vm_prot - max_protection: std.macho.vm_prot_t, + max_protection: vm_prot_t, // behavior of map/obj on fork inheritance: vm_inherit_t, // offset into object/map @@ -391,6 +391,7 @@ pub const task_vm_info = extern struct { }; pub const task_vm_info_data_t = task_vm_info; +pub const vm_prot_t = c_int; pub const boolean_t = c_int; pub extern "c" fn mach_vm_protect( @@ -398,7 +399,7 @@ pub extern "c" fn mach_vm_protect( address: mach_vm_address_t, size: mach_vm_size_t, set_maximum: boolean_t, - new_protection: std.macho.vm_prot_t, + new_protection: vm_prot_t, ) kern_return_t; pub extern "c" fn mach_port_deallocate(target_tport: mach_port_name_t, task: mach_port_name_t) kern_return_t; @@ -920,13 +921,19 @@ pub const STDERR_FILENO = 2; pub const PROT = struct { /// [MC2] no permissions - pub const NONE = 0x00; + pub const NONE: vm_prot_t = 0x00; /// [MC2] pages can be read - pub const READ = 0x01; + pub const READ: vm_prot_t = 0x01; /// [MC2] pages can be written - pub const WRITE = 0x02; + pub const WRITE: vm_prot_t = 0x02; /// [MC2] pages can be executed - pub const EXEC = 0x04; + pub const EXEC: vm_prot_t = 0x04; + /// When a caller finds that they cannot obtain write permission on a + /// mapped entry, the following flag can be used. The entry will be + /// made "needs copy" effectively copying the object (using COW), + /// and write permission will be added to the maximum protections for + /// the associated entry. + pub const COPY: vm_prot_t = 0x10; }; pub const MAP = struct { @@ -1771,6 +1778,10 @@ pub const E = enum(u16) { _, }; +pub fn getKernError(err: kern_return_t) KernE { + return @intToEnum(KernE, err); +} + /// Kernel return values pub const KernE = enum(u8) { SUCCESS = 0, diff --git a/lib/std/macho.zig b/lib/std/macho.zig index a6017ff8b4..5ab7449b37 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -2,12 +2,16 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const io = std.io; +const os = std.os.darwin; const mem = std.mem; const meta = std.meta; const testing = std.testing; const Allocator = mem.Allocator; +pub const cpu_type_t = os.integer_t; +pub const cpu_subtype_t = os.integer_t; + pub const mach_header = extern struct { magic: u32, cputype: cpu_type_t, @@ -601,10 +605,10 @@ pub const segment_command = extern struct { filesize: u32, /// maximum VM protection - maxprot: vm_prot_t, + maxprot: os.vm_prot_t, /// initial VM protection - initprot: vm_prot_t, + initprot: os.vm_prot_t, /// number of sections in segment nsects: u32, @@ -638,10 +642,10 @@ pub const segment_command_64 = extern struct { filesize: u64 = 0, /// maximum VM protection - maxprot: vm_prot_t = VM_PROT_NONE, + maxprot: os.vm_prot_t = os.PROT.NONE, /// initial VM protection - initprot: vm_prot_t = VM_PROT_NONE, + initprot: os.vm_prot_t = os.PROT.NONE, /// number of sections in segment nsects: u32 = 0, @@ -1438,11 +1442,6 @@ pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15; /// 32-bit offsets to initializers pub const S_INIT_FUNC_OFFSETS = 0x16; -pub const cpu_type_t = integer_t; -pub const cpu_subtype_t = integer_t; -pub const integer_t = c_int; -pub const vm_prot_t = c_int; - /// CPU type targeting 64-bit Intel-based Macs pub const CPU_TYPE_X86_64: cpu_type_t = 0x01000007; @@ -1455,26 +1454,6 @@ pub const CPU_SUBTYPE_X86_64_ALL: cpu_subtype_t = 0x3; /// All ARM-based Macs pub const CPU_SUBTYPE_ARM_ALL: cpu_subtype_t = 0x0; -// Protection values defined as bits within the vm_prot_t type -/// No VM protection -pub const VM_PROT_NONE: vm_prot_t = 0x0; - -/// VM read permission -pub const VM_PROT_READ: vm_prot_t = 0x1; - -/// VM write permission -pub const VM_PROT_WRITE: vm_prot_t = 0x2; - -/// VM execute permission -pub const VM_PROT_EXECUTE: vm_prot_t = 0x4; - -/// When a caller finds that they cannot obtain write permission on a -/// mapped entry, the following flag can be used. The entry will be -/// made "needs copy" effectively copying the object (using COW), -/// and write permission will be added to the maximum protections for -/// the associated entry. -pub const VM_PROT_COPY: vm_prot_t = 0x10; - // The following are used to encode rebasing information pub const REBASE_TYPE_POINTER: u8 = 1; pub const REBASE_TYPE_TEXT_ABSOLUTE32: u8 = 2; @@ -2169,8 +2148,8 @@ test "read-write segment command" { .vmaddr = 4294967296, .vmsize = 294912, .filesize = 294912, - .maxprot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, - .initprot = VM_PROT_EXECUTE | VM_PROT_READ, + .maxprot = os.PROT.READ | os.PROT.WRITE | os.PROT.EXEC, + .initprot = os.PROT.EXEC | os.PROT.READ, .nsects = 1, }, }; diff --git a/lib/std/os.zig b/lib/std/os.zig index 241c38dedf..9f7636d0a5 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator; const Preopen = std.fs.wasi.Preopen; const PreopenList = std.fs.wasi.PreopenList; -pub const darwin = std.c; +pub const darwin = @import("os/darwin.zig"); pub const dragonfly = std.c; pub const freebsd = std.c; pub const haiku = std.c; diff --git a/lib/std/os/darwin.zig b/lib/std/os/darwin.zig new file mode 100644 index 0000000000..b4c395835d --- /dev/null +++ b/lib/std/os/darwin.zig @@ -0,0 +1,216 @@ +const std = @import("std"); +const log = std.log; +const mem = std.mem; +const os = @This(); + +pub usingnamespace @import("../c.zig"); + +pub const MachError = error{ + /// Not enough permissions held to perform the requested kernel + /// call. + PermissionDenied, + /// Kernel returned an unhandled and unexpected error code. + /// This is a catch-all for any yet unobserved kernel response + /// to some Mach message. + Unexpected, +}; + +pub const MachTask = struct { + port: os.mach_port_name_t, + + pub fn isValid(self: MachTask) bool { + return self.port != 0; + } + + pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!os.vm_prot_t { + var base_addr = address; + var base_len: os.mach_vm_size_t = if (len == 1) 2 else len; + var objname: os.mach_port_t = undefined; + var info: os.vm_region_submap_info_64 = undefined; + var count: os.mach_msg_type_number_t = os.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + switch (os.getKernError(os.mach_vm_region( + task.port, + &base_addr, + &base_len, + os.VM_REGION_BASIC_INFO_64, + @ptrCast(os.vm_region_info_t, &info), + &count, + &objname, + ))) { + .SUCCESS => return info.protection, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + } + + pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: os.vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, true, prot); + } + + pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: os.vm_prot_t) MachError!void { + return task.setProtectionImpl(address, len, false, prot); + } + + fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: os.vm_prot_t) MachError!void { + switch (os.getKernError(os.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { + .SUCCESS => return, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + } + + /// 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, + os.PROT.READ | os.PROT.WRITE | os.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 (os.getKernError(os.mach_vm_write( + task.port, + curr_addr, + @ptrToInt(out_buf.ptr), + @intCast(os.mach_msg_type_number_t, curr_size), + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + + switch (arch) { + .aarch64 => { + var mattr_value: os.vm_machine_attribute_val_t = os.MATTR_VAL_CACHE_FLUSH; + switch (os.getKernError(os.vm_machine_attribute( + task.port, + curr_addr, + curr_size, + os.MATTR_CACHE, + &mattr_value, + ))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + }, + .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: os.mach_msg_type_number_t = 0; + var vm_memory: os.vm_offset_t = undefined; + switch (os.getKernError(os.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + + @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read); + _ = os.vm_deallocate(os.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 = os.TASK_VM_INFO_COUNT; + var vm_info: os.task_vm_info_data_t = undefined; + switch (os.getKernError(os.task_info( + task.port, + os.TASK_VM_INFO, + @ptrCast(os.task_info_t, &vm_info), + &info_count, + ))) { + .SUCCESS => return @intCast(usize, vm_info.page_size), + else => {}, + } + } + var page_size: os.vm_size_t = undefined; + switch (os.getKernError(os._host_page_size(os.mach_host_self(), &page_size))) { + .SUCCESS => return page_size, + else => |err| { + log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + } +}; + +pub fn machTaskForPid(pid: os.pid_t) MachError!MachTask { + var port: os.mach_port_name_t = undefined; + switch (os.getKernError(os.task_for_pid(os.mach_task_self(), pid, &port))) { + .SUCCESS => {}, + .FAILURE => return error.PermissionDenied, + else => |err| { + log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)}); + return error.Unexpected; + }, + } + return MachTask{ .port = port }; +} |
