| ... | ... | @@ -1,214 +1,219 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | | const c = std.c.darwin; |
| 2 | const builtin = @import("builtin"); |
| 3 | 3 | const log = std.log; |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | |
| 6 | | pub const MachError = error{ |
| 7 | | /// Not enough permissions held to perform the requested kernel |
| 8 | | /// call. |
| 9 | | PermissionDenied, |
| 10 | | /// Kernel returned an unhandled and unexpected error code. |
| 11 | | /// This is a catch-all for any yet unobserved kernel response |
| 12 | | /// to some Mach message. |
| 13 | | Unexpected, |
| 14 | | }; |
| 15 | | |
| 16 | | pub const MachTask = struct { |
| 17 | | port: c.mach_port_name_t, |
| 18 | | |
| 19 | | pub fn isValid(self: MachTask) bool { |
| 20 | | return self.port != 0; |
| 21 | | } |
| 22 | | |
| 23 | | pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!c.vm_prot_t { |
| 24 | | var base_addr = address; |
| 25 | | var base_len: c.mach_vm_size_t = if (len == 1) 2 else len; |
| 26 | | var objname: c.mach_port_t = undefined; |
| 27 | | var info: c.vm_region_submap_info_64 = undefined; |
| 28 | | var count: c.mach_msg_type_number_t = c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; |
| 29 | | switch (c.getKernError(c.mach_vm_region( |
| 30 | | task.port, |
| 31 | | &base_addr, |
| 32 | | &base_len, |
| 33 | | c.VM_REGION_BASIC_INFO_64, |
| 34 | | @ptrCast(c.vm_region_info_t, &info), |
| 35 | | &count, |
| 36 | | &objname, |
| 37 | | ))) { |
| 38 | | .SUCCESS => return info.protection, |
| 39 | | .FAILURE => return error.PermissionDenied, |
| 40 | | else => |err| { |
| 41 | | log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)}); |
| 42 | | return error.Unexpected; |
| 43 | | }, |
| 44 | | } |
| 45 | | } |
| 46 | | |
| 47 | | pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: c.vm_prot_t) MachError!void { |
| 48 | | return task.setProtectionImpl(address, len, true, prot); |
| 49 | | } |
| 50 | | |
| 51 | | pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: c.vm_prot_t) MachError!void { |
| 52 | | return task.setProtectionImpl(address, len, false, prot); |
| 53 | | } |
| 54 | | |
| 55 | | fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: c.vm_prot_t) MachError!void { |
| 56 | | switch (c.getKernError(c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { |
| 57 | | .SUCCESS => return, |
| 58 | | .FAILURE => return error.PermissionDenied, |
| 59 | | else => |err| { |
| 60 | | log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)}); |
| 61 | | return error.Unexpected; |
| 62 | | }, |
| 63 | | } |
| 64 | | } |
| 65 | | |
| 66 | | /// Will write to VM even if current protection attributes specifically prohibit |
| 67 | | /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY |
| 68 | | /// variant, and resetting after a successful or unsuccessful write. |
| 69 | | pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { |
| 70 | | const curr_prot = try task.getCurrProtection(address, buf.len); |
| 71 | | try task.setCurrProtection( |
| 72 | | address, |
| 73 | | buf.len, |
| 74 | | c.PROT.READ | c.PROT.WRITE | c.PROT.COPY, |
| 75 | | ); |
| 76 | | defer { |
| 77 | | task.setCurrProtection(address, buf.len, curr_prot) catch {}; |
| 6 | pub usingnamespace std.c; |
| 7 | pub usingnamespace mach_task; |
| 8 | |
| 9 | const mach_task = if (builtin.target.isDarwin()) struct { |
| 10 | pub const MachError = error{ |
| 11 | /// Not enough permissions held to perform the requested kernel |
| 12 | /// call. |
| 13 | PermissionDenied, |
| 14 | /// Kernel returned an unhandled and unexpected error code. |
| 15 | /// This is a catch-all for any yet unobserved kernel response |
| 16 | /// to some Mach message. |
| 17 | Unexpected, |
| 18 | }; |
| 19 | |
| 20 | pub const MachTask = struct { |
| 21 | port: std.c.mach_port_name_t, |
| 22 | |
| 23 | pub fn isValid(self: MachTask) bool { |
| 24 | return self.port != 0; |
| 78 | 25 | } |
| 79 | | return task.writeMem(address, buf, arch); |
| 80 | | } |
| 81 | 26 | |
| 82 | | pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { |
| 83 | | const count = buf.len; |
| 84 | | var total_written: usize = 0; |
| 85 | | var curr_addr = address; |
| 86 | | const page_size = try getPageSize(task); // TODO we probably can assume value here |
| 87 | | var out_buf = buf[0..]; |
| 88 | | |
| 89 | | while (total_written < count) { |
| 90 | | const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); |
| 91 | | switch (c.getKernError(c.mach_vm_write( |
| 27 | pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t { |
| 28 | var base_addr = address; |
| 29 | var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; |
| 30 | var objname: std.c.mach_port_t = undefined; |
| 31 | var info: std.c.vm_region_submap_info_64 = undefined; |
| 32 | var count: std.c.mach_msg_type_number_t = std.c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; |
| 33 | switch (std.c.getKernError(std.c.mach_vm_region( |
| 92 | 34 | task.port, |
| 93 | | curr_addr, |
| 94 | | @ptrToInt(out_buf.ptr), |
| 95 | | @intCast(c.mach_msg_type_number_t, curr_size), |
| 35 | &base_addr, |
| 36 | &base_len, |
| 37 | std.c.VM_REGION_BASIC_INFO_64, |
| 38 | @ptrCast(std.c.vm_region_info_t, &info), |
| 39 | &count, |
| 40 | &objname, |
| 96 | 41 | ))) { |
| 97 | | .SUCCESS => {}, |
| 42 | .SUCCESS => return info.protection, |
| 98 | 43 | .FAILURE => return error.PermissionDenied, |
| 99 | 44 | else => |err| { |
| 100 | | log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)}); |
| 45 | log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)}); |
| 101 | 46 | return error.Unexpected; |
| 102 | 47 | }, |
| 103 | 48 | } |
| 49 | } |
| 104 | 50 | |
| 105 | | switch (arch) { |
| 106 | | .aarch64 => { |
| 107 | | var mattr_value: c.vm_machine_attribute_val_t = c.MATTR_VAL_CACHE_FLUSH; |
| 108 | | switch (c.getKernError(c.vm_machine_attribute( |
| 109 | | task.port, |
| 110 | | curr_addr, |
| 111 | | curr_size, |
| 112 | | c.MATTR_CACHE, |
| 113 | | &mattr_value, |
| 114 | | ))) { |
| 115 | | .SUCCESS => {}, |
| 116 | | .FAILURE => return error.PermissionDenied, |
| 117 | | else => |err| { |
| 118 | | log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)}); |
| 119 | | return error.Unexpected; |
| 120 | | }, |
| 121 | | } |
| 122 | | }, |
| 123 | | .x86_64 => {}, |
| 124 | | else => unreachable, |
| 125 | | } |
| 126 | | |
| 127 | | out_buf = out_buf[curr_size..]; |
| 128 | | total_written += curr_size; |
| 129 | | curr_addr += curr_size; |
| 51 | pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { |
| 52 | return task.setProtectionImpl(address, len, true, prot); |
| 130 | 53 | } |
| 131 | 54 | |
| 132 | | return total_written; |
| 133 | | } |
| 55 | pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { |
| 56 | return task.setProtectionImpl(address, len, false, prot); |
| 57 | } |
| 134 | 58 | |
| 135 | | pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { |
| 136 | | const count = buf.len; |
| 137 | | var total_read: usize = 0; |
| 138 | | var curr_addr = address; |
| 139 | | const page_size = try getPageSize(task); // TODO we probably can assume value here |
| 140 | | var out_buf = buf[0..]; |
| 141 | | |
| 142 | | while (total_read < count) { |
| 143 | | const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); |
| 144 | | var curr_bytes_read: c.mach_msg_type_number_t = 0; |
| 145 | | var vm_memory: c.vm_offset_t = undefined; |
| 146 | | switch (c.getKernError(c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { |
| 147 | | .SUCCESS => {}, |
| 59 | fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void { |
| 60 | switch (std.c.getKernError(std.c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { |
| 61 | .SUCCESS => return, |
| 148 | 62 | .FAILURE => return error.PermissionDenied, |
| 149 | 63 | else => |err| { |
| 150 | | log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)}); |
| 64 | log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)}); |
| 151 | 65 | return error.Unexpected; |
| 152 | 66 | }, |
| 153 | 67 | } |
| 68 | } |
| 69 | |
| 70 | /// Will write to VM even if current protection attributes specifically prohibit |
| 71 | /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY |
| 72 | /// variant, and resetting after a successful or unsuccessful write. |
| 73 | pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { |
| 74 | const curr_prot = try task.getCurrProtection(address, buf.len); |
| 75 | try task.setCurrProtection( |
| 76 | address, |
| 77 | buf.len, |
| 78 | std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY, |
| 79 | ); |
| 80 | defer { |
| 81 | task.setCurrProtection(address, buf.len, curr_prot) catch {}; |
| 82 | } |
| 83 | return task.writeMem(address, buf, arch); |
| 84 | } |
| 154 | 85 | |
| 155 | | @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read); |
| 156 | | _ = c.vm_deallocate(c.mach_task_self(), vm_memory, curr_bytes_read); |
| 86 | pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { |
| 87 | const count = buf.len; |
| 88 | var total_written: usize = 0; |
| 89 | var curr_addr = address; |
| 90 | const page_size = try getPageSize(task); // TODO we probably can assume value here |
| 91 | var out_buf = buf[0..]; |
| 92 | |
| 93 | while (total_written < count) { |
| 94 | const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); |
| 95 | switch (std.c.getKernError(std.c.mach_vm_write( |
| 96 | task.port, |
| 97 | curr_addr, |
| 98 | @ptrToInt(out_buf.ptr), |
| 99 | @intCast(std.c.mach_msg_type_number_t, curr_size), |
| 100 | ))) { |
| 101 | .SUCCESS => {}, |
| 102 | .FAILURE => return error.PermissionDenied, |
| 103 | else => |err| { |
| 104 | log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)}); |
| 105 | return error.Unexpected; |
| 106 | }, |
| 107 | } |
| 108 | |
| 109 | switch (arch) { |
| 110 | .aarch64 => { |
| 111 | var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR_VAL_CACHE_FLUSH; |
| 112 | switch (std.c.getKernError(std.c.vm_machine_attribute( |
| 113 | task.port, |
| 114 | curr_addr, |
| 115 | curr_size, |
| 116 | std.c.MATTR_CACHE, |
| 117 | &mattr_value, |
| 118 | ))) { |
| 119 | .SUCCESS => {}, |
| 120 | .FAILURE => return error.PermissionDenied, |
| 121 | else => |err| { |
| 122 | log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)}); |
| 123 | return error.Unexpected; |
| 124 | }, |
| 125 | } |
| 126 | }, |
| 127 | .x86_64 => {}, |
| 128 | else => unreachable, |
| 129 | } |
| 130 | |
| 131 | out_buf = out_buf[curr_size..]; |
| 132 | total_written += curr_size; |
| 133 | curr_addr += curr_size; |
| 134 | } |
| 157 | 135 | |
| 158 | | out_buf = out_buf[curr_bytes_read..]; |
| 159 | | curr_addr += curr_bytes_read; |
| 160 | | total_read += curr_bytes_read; |
| 136 | return total_written; |
| 161 | 137 | } |
| 162 | 138 | |
| 163 | | return total_read; |
| 164 | | } |
| 139 | pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { |
| 140 | const count = buf.len; |
| 141 | var total_read: usize = 0; |
| 142 | var curr_addr = address; |
| 143 | const page_size = try getPageSize(task); // TODO we probably can assume value here |
| 144 | var out_buf = buf[0..]; |
| 145 | |
| 146 | while (total_read < count) { |
| 147 | const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); |
| 148 | var curr_bytes_read: std.c.mach_msg_type_number_t = 0; |
| 149 | var vm_memory: std.c.vm_offset_t = undefined; |
| 150 | switch (std.c.getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { |
| 151 | .SUCCESS => {}, |
| 152 | .FAILURE => return error.PermissionDenied, |
| 153 | else => |err| { |
| 154 | log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)}); |
| 155 | return error.Unexpected; |
| 156 | }, |
| 157 | } |
| 158 | |
| 159 | @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read); |
| 160 | _ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read); |
| 161 | |
| 162 | out_buf = out_buf[curr_bytes_read..]; |
| 163 | curr_addr += curr_bytes_read; |
| 164 | total_read += curr_bytes_read; |
| 165 | } |
| 165 | 166 | |
| 166 | | fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { |
| 167 | | var left = count; |
| 168 | | if (page_size > 0) { |
| 169 | | const page_offset = address % page_size; |
| 170 | | const bytes_left_in_page = page_size - page_offset; |
| 171 | | if (count > bytes_left_in_page) { |
| 172 | | left = bytes_left_in_page; |
| 167 | return total_read; |
| 168 | } |
| 169 | |
| 170 | fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { |
| 171 | var left = count; |
| 172 | if (page_size > 0) { |
| 173 | const page_offset = address % page_size; |
| 174 | const bytes_left_in_page = page_size - page_offset; |
| 175 | if (count > bytes_left_in_page) { |
| 176 | left = bytes_left_in_page; |
| 177 | } |
| 173 | 178 | } |
| 179 | return left; |
| 174 | 180 | } |
| 175 | | return left; |
| 176 | | } |
| 177 | 181 | |
| 178 | | fn getPageSize(task: MachTask) MachError!usize { |
| 179 | | if (task.isValid()) { |
| 180 | | var info_count = c.TASK_VM_INFO_COUNT; |
| 181 | | var vm_info: c.task_vm_info_data_t = undefined; |
| 182 | | switch (c.getKernError(c.task_info( |
| 183 | | task.port, |
| 184 | | c.TASK_VM_INFO, |
| 185 | | @ptrCast(c.task_info_t, &vm_info), |
| 186 | | &info_count, |
| 187 | | ))) { |
| 188 | | .SUCCESS => return @intCast(usize, vm_info.page_size), |
| 189 | | else => {}, |
| 182 | fn getPageSize(task: MachTask) MachError!usize { |
| 183 | if (task.isValid()) { |
| 184 | var info_count = std.c.TASK_VM_INFO_COUNT; |
| 185 | var vm_info: std.c.task_vm_info_data_t = undefined; |
| 186 | switch (std.c.getKernError(std.c.task_info( |
| 187 | task.port, |
| 188 | std.c.TASK_VM_INFO, |
| 189 | @ptrCast(std.c.task_info_t, &vm_info), |
| 190 | &info_count, |
| 191 | ))) { |
| 192 | .SUCCESS => return @intCast(usize, vm_info.page_size), |
| 193 | else => {}, |
| 194 | } |
| 195 | } |
| 196 | var page_size: std.c.vm_size_t = undefined; |
| 197 | switch (std.c.getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) { |
| 198 | .SUCCESS => return page_size, |
| 199 | else => |err| { |
| 200 | log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)}); |
| 201 | return error.Unexpected; |
| 202 | }, |
| 190 | 203 | } |
| 191 | 204 | } |
| 192 | | var page_size: c.vm_size_t = undefined; |
| 193 | | switch (c.getKernError(c._host_page_size(c.mach_host_self(), &page_size))) { |
| 194 | | .SUCCESS => return page_size, |
| 205 | }; |
| 206 | |
| 207 | pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask { |
| 208 | var port: std.c.mach_port_name_t = undefined; |
| 209 | switch (std.c.getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) { |
| 210 | .SUCCESS => {}, |
| 211 | .FAILURE => return error.PermissionDenied, |
| 195 | 212 | else => |err| { |
| 196 | | log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)}); |
| 213 | log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)}); |
| 197 | 214 | return error.Unexpected; |
| 198 | 215 | }, |
| 199 | 216 | } |
| 217 | return MachTask{ .port = port }; |
| 200 | 218 | } |
| 201 | | }; |
| 202 | | |
| 203 | | pub fn machTaskForPid(pid: c.pid_t) MachError!MachTask { |
| 204 | | var port: c.mach_port_name_t = undefined; |
| 205 | | switch (c.getKernError(c.task_for_pid(c.mach_task_self(), pid, &port))) { |
| 206 | | .SUCCESS => {}, |
| 207 | | .FAILURE => return error.PermissionDenied, |
| 208 | | else => |err| { |
| 209 | | log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)}); |
| 210 | | return error.Unexpected; |
| 211 | | }, |
| 212 | | } |
| 213 | | return MachTask{ .port = port }; |
| 214 | | } |
| 219 | } else struct {}; |