| ... | @@ -1,214 +1,219 @@ | ... | @@ -1,214 +1,219 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const c = std.c.darwin; | 2 | const builtin = @import("builtin"); |
| 3 | const log = std.log; | 3 | const log = std.log; |
| 4 | const mem = std.mem; | 4 | const mem = std.mem; |
| 5 | | 5 | |
| 6 | pub const MachError = error{ | 6 | pub usingnamespace std.c; |
| 7 | /// Not enough permissions held to perform the requested kernel | 7 | pub usingnamespace mach_task; |
| 8 | /// call. | 8 | |
| 9 | PermissionDenied, | 9 | const mach_task = if (builtin.target.isDarwin()) struct { |
| 10 | /// Kernel returned an unhandled and unexpected error code. | 10 | pub const MachError = error{ |
| 11 | /// This is a catch-all for any yet unobserved kernel response | 11 | /// Not enough permissions held to perform the requested kernel |
| 12 | /// to some Mach message. | 12 | /// call. |
| 13 | Unexpected, | 13 | PermissionDenied, |
| 14 | }; | 14 | /// Kernel returned an unhandled and unexpected error code. |
| 15 | | 15 | /// This is a catch-all for any yet unobserved kernel response |
| 16 | pub const MachTask = struct { | 16 | /// to some Mach message. |
| 17 | port: c.mach_port_name_t, | 17 | Unexpected, |
| 18 | | 18 | }; |
| 19 | pub fn isValid(self: MachTask) bool { | 19 | |
| 20 | return self.port != 0; | 20 | pub const MachTask = struct { |
| 21 | } | 21 | port: std.c.mach_port_name_t, |
| 22 | | 22 | |
| 23 | pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!c.vm_prot_t { | 23 | pub fn isValid(self: MachTask) bool { |
| 24 | var base_addr = address; | 24 | return self.port != 0; |
| 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 {}; | | |
| 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 { | 27 | pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t { |
| 83 | const count = buf.len; | 28 | var base_addr = address; |
| 84 | var total_written: usize = 0; | 29 | var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; |
| 85 | var curr_addr = address; | 30 | var objname: std.c.mach_port_t = undefined; |
| 86 | const page_size = try getPageSize(task); // TODO we probably can assume value here | 31 | var info: std.c.vm_region_submap_info_64 = undefined; |
| 87 | var out_buf = buf[0..]; | 32 | var count: std.c.mach_msg_type_number_t = std.c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; |
| 88 | | 33 | switch (std.c.getKernError(std.c.mach_vm_region( |
| 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( | | |
| 92 | task.port, | 34 | task.port, |
| 93 | curr_addr, | 35 | &base_addr, |
| 94 | @ptrToInt(out_buf.ptr), | 36 | &base_len, |
| 95 | @intCast(c.mach_msg_type_number_t, curr_size), | 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 | .FAILURE => return error.PermissionDenied, | 43 | .FAILURE => return error.PermissionDenied, |
| 99 | else => |err| { | 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 | return error.Unexpected; | 46 | return error.Unexpected; |
| 102 | }, | 47 | }, |
| 103 | } | 48 | } |
| | 49 | } |
| 104 | | 50 | |
| 105 | switch (arch) { | 51 | pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { |
| 106 | .aarch64 => { | 52 | return task.setProtectionImpl(address, len, true, prot); |
| 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; | | |
| 130 | } | 53 | } |
| 131 | | 54 | |
| 132 | return total_written; | 55 | pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { |
| 133 | } | 56 | return task.setProtectionImpl(address, len, false, prot); |
| | 57 | } |
| 134 | | 58 | |
| 135 | pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { | 59 | fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void { |
| 136 | const count = buf.len; | 60 | switch (std.c.getKernError(std.c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) { |
| 137 | var total_read: usize = 0; | 61 | .SUCCESS => return, |
| 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 => {}, | | |
| 148 | .FAILURE => return error.PermissionDenied, | 62 | .FAILURE => return error.PermissionDenied, |
| 149 | else => |err| { | 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 | return error.Unexpected; | 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); | 86 | pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { |
| 156 | _ = c.vm_deallocate(c.mach_task_self(), vm_memory, curr_bytes_read); | 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..]; | 136 | return total_written; |
| 159 | curr_addr += curr_bytes_read; | | |
| 160 | total_read += curr_bytes_read; | | |
| 161 | } | 137 | } |
| 162 | | 138 | |
| 163 | return total_read; | 139 | pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { |
| 164 | } | 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 | return total_read; |
| 167 | var left = count; | 168 | } |
| 168 | if (page_size > 0) { | 169 | |
| 169 | const page_offset = address % page_size; | 170 | fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { |
| 170 | const bytes_left_in_page = page_size - page_offset; | 171 | var left = count; |
| 171 | if (count > bytes_left_in_page) { | 172 | if (page_size > 0) { |
| 172 | left = bytes_left_in_page; | 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 { | 182 | fn getPageSize(task: MachTask) MachError!usize { |
| 179 | if (task.isValid()) { | 183 | if (task.isValid()) { |
| 180 | var info_count = c.TASK_VM_INFO_COUNT; | 184 | var info_count = std.c.TASK_VM_INFO_COUNT; |
| 181 | var vm_info: c.task_vm_info_data_t = undefined; | 185 | var vm_info: std.c.task_vm_info_data_t = undefined; |
| 182 | switch (c.getKernError(c.task_info( | 186 | switch (std.c.getKernError(std.c.task_info( |
| 183 | task.port, | 187 | task.port, |
| 184 | c.TASK_VM_INFO, | 188 | std.c.TASK_VM_INFO, |
| 185 | @ptrCast(c.task_info_t, &vm_info), | 189 | @ptrCast(std.c.task_info_t, &vm_info), |
| 186 | &info_count, | 190 | &info_count, |
| 187 | ))) { | 191 | ))) { |
| 188 | .SUCCESS => return @intCast(usize, vm_info.page_size), | 192 | .SUCCESS => return @intCast(usize, vm_info.page_size), |
| 189 | else => {}, | 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; | 205 | }; |
| 193 | switch (c.getKernError(c._host_page_size(c.mach_host_self(), &page_size))) { | 206 | |
| 194 | .SUCCESS => return page_size, | 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 | else => |err| { | 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 | return error.Unexpected; | 214 | return error.Unexpected; |
| 198 | }, | 215 | }, |
| 199 | } | 216 | } |
| | 217 | return MachTask{ .port = port }; |
| 200 | } | 218 | } |
| 201 | }; | 219 | } else struct {}; |
| 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 | } | | |