Commit 633c4a2a60

Jakub Konka <kubkon@jakubkonka.com>
2022-03-13 13:35:37
macos: add Mach task abstraction
`std.os.darwin.MachTask` wraps `mach_port_t` and can be used to issue kernel calls tied to the wrapped Mach kernel port/task.
1 parent 2036af9
Changed files (6)
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,
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 };
+}
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,
         },
     };
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;
src/link/MachO.zig
@@ -4,6 +4,7 @@ const std = @import("std");
 const build_options = @import("build_options");
 const builtin = @import("builtin");
 const assert = std.debug.assert;
+const darwin = std.os.darwin;
 const fmt = std.fmt;
 const fs = std.fs;
 const log = std.log.scoped(.link);
@@ -4382,8 +4383,8 @@ fn populateMissingMetadata(self: *MachO) !void {
                     .vmaddr = pagezero_vmsize,
                     .vmsize = needed_size,
                     .filesize = needed_size,
-                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
-                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
+                    .maxprot = darwin.PROT.READ | darwin.PROT.EXEC,
+                    .initprot = darwin.PROT.READ | darwin.PROT.EXEC,
                 },
             },
         });
@@ -4487,8 +4488,8 @@ fn populateMissingMetadata(self: *MachO) !void {
                     .vmsize = needed_size,
                     .fileoff = fileoff,
                     .filesize = needed_size,
-                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                    .maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
+                    .initprot = darwin.PROT.READ | darwin.PROT.WRITE,
                 },
             },
         });
@@ -4536,8 +4537,8 @@ fn populateMissingMetadata(self: *MachO) !void {
                     .vmsize = needed_size,
                     .fileoff = fileoff,
                     .filesize = needed_size,
-                    .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
-                    .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
+                    .maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
+                    .initprot = darwin.PROT.READ | darwin.PROT.WRITE,
                 },
             },
         });
@@ -4645,8 +4646,8 @@ fn populateMissingMetadata(self: *MachO) !void {
                     .segname = makeStaticString("__LINKEDIT"),
                     .vmaddr = vmaddr,
                     .fileoff = fileoff,
-                    .maxprot = macho.VM_PROT_READ,
-                    .initprot = macho.VM_PROT_READ,
+                    .maxprot = darwin.PROT.READ,
+                    .initprot = darwin.PROT.READ,
                 },
             },
         });
CMakeLists.txt
@@ -460,6 +460,7 @@ set(ZIG_STAGE2_SOURCES
     "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/os.zig"
+    "${CMAKE_SOURCE_DIR}/lib/std/os/darwin.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig"
     "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"