authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-13 13:35:37+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-13 13:35:39+01:00
log633c4a2a6094cd301648c2e2c6b5002917e0ef2c
treefdad1685dde93d39af844d257440ddd42ba3046b
parent2036af94e97f0ac16293556423c144ec9e1b8226

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.

6 files changed, 255 insertions(+), 47 deletions(-)

CMakeLists.txt+1
...@@ -460,6 +460,7 @@ set(ZIG_STAGE2_SOURCES...@@ -460,6 +460,7 @@ set(ZIG_STAGE2_SOURCES
460 "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"460 "${CMAKE_SOURCE_DIR}/lib/std/meta/trait.zig"
461 "${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"461 "${CMAKE_SOURCE_DIR}/lib/std/multi_array_list.zig"
462 "${CMAKE_SOURCE_DIR}/lib/std/os.zig"462 "${CMAKE_SOURCE_DIR}/lib/std/os.zig"
463 "${CMAKE_SOURCE_DIR}/lib/std/os/darwin.zig"
463 "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"464 "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"
464 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig"465 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/errno/generic.zig"
465 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"466 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"
lib/std/c/darwin.zig+18-7
...@@ -201,9 +201,9 @@ pub const vm_object_id_t = u64;...@@ -201,9 +201,9 @@ pub const vm_object_id_t = u64;
201201
202pub const vm_region_submap_info_64 = extern struct {202pub const vm_region_submap_info_64 = extern struct {
203 // present across protection203 // present across protection
204 protection: std.macho.vm_prot_t,204 protection: vm_prot_t,
205 // max avail through vm_prot205 // max avail through vm_prot
206 max_protection: std.macho.vm_prot_t,206 max_protection: vm_prot_t,
207 // behavior of map/obj on fork207 // behavior of map/obj on fork
208 inheritance: vm_inherit_t,208 inheritance: vm_inherit_t,
209 // offset into object/map209 // offset into object/map
...@@ -391,6 +391,7 @@ pub const task_vm_info = extern struct {...@@ -391,6 +391,7 @@ pub const task_vm_info = extern struct {
391};391};
392pub const task_vm_info_data_t = task_vm_info;392pub const task_vm_info_data_t = task_vm_info;
393393
394pub const vm_prot_t = c_int;
394pub const boolean_t = c_int;395pub const boolean_t = c_int;
395396
396pub extern "c" fn mach_vm_protect(397pub extern "c" fn mach_vm_protect(
...@@ -398,7 +399,7 @@ pub extern "c" fn mach_vm_protect(...@@ -398,7 +399,7 @@ pub extern "c" fn mach_vm_protect(
398 address: mach_vm_address_t,399 address: mach_vm_address_t,
399 size: mach_vm_size_t,400 size: mach_vm_size_t,
400 set_maximum: boolean_t,401 set_maximum: boolean_t,
401 new_protection: std.macho.vm_prot_t,402 new_protection: vm_prot_t,
402) kern_return_t;403) kern_return_t;
403404
404pub extern "c" fn mach_port_deallocate(target_tport: mach_port_name_t, task: mach_port_name_t) kern_return_t;405pub 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;...@@ -920,13 +921,19 @@ pub const STDERR_FILENO = 2;
920921
921pub const PROT = struct {922pub const PROT = struct {
922 /// [MC2] no permissions923 /// [MC2] no permissions
923 pub const NONE = 0x00;924 pub const NONE: vm_prot_t = 0x00;
924 /// [MC2] pages can be read925 /// [MC2] pages can be read
925 pub const READ = 0x01;926 pub const READ: vm_prot_t = 0x01;
926 /// [MC2] pages can be written927 /// [MC2] pages can be written
927 pub const WRITE = 0x02;928 pub const WRITE: vm_prot_t = 0x02;
928 /// [MC2] pages can be executed929 /// [MC2] pages can be executed
929 pub const EXEC = 0x04;930 pub const EXEC: vm_prot_t = 0x04;
931 /// When a caller finds that they cannot obtain write permission on a
932 /// mapped entry, the following flag can be used. The entry will be
933 /// made "needs copy" effectively copying the object (using COW),
934 /// and write permission will be added to the maximum protections for
935 /// the associated entry.
936 pub const COPY: vm_prot_t = 0x10;
930};937};
931938
932pub const MAP = struct {939pub const MAP = struct {
...@@ -1771,6 +1778,10 @@ pub const E = enum(u16) {...@@ -1771,6 +1778,10 @@ pub const E = enum(u16) {
1771 _,1778 _,
1772};1779};
17731780
1781pub fn getKernError(err: kern_return_t) KernE {
1782 return @intToEnum(KernE, err);
1783}
1784
1774/// Kernel return values1785/// Kernel return values
1775pub const KernE = enum(u8) {1786pub const KernE = enum(u8) {
1776 SUCCESS = 0,1787 SUCCESS = 0,
lib/std/macho.zig+10-31
...@@ -2,12 +2,16 @@ const std = @import("std");...@@ -2,12 +2,16 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const assert = std.debug.assert;3const assert = std.debug.assert;
4const io = std.io;4const io = std.io;
5const os = std.os.darwin;
5const mem = std.mem;6const mem = std.mem;
6const meta = std.meta;7const meta = std.meta;
7const testing = std.testing;8const testing = std.testing;
89
9const Allocator = mem.Allocator;10const Allocator = mem.Allocator;
1011
12pub const cpu_type_t = os.integer_t;
13pub const cpu_subtype_t = os.integer_t;
14
11pub const mach_header = extern struct {15pub const mach_header = extern struct {
12 magic: u32,16 magic: u32,
13 cputype: cpu_type_t,17 cputype: cpu_type_t,
...@@ -601,10 +605,10 @@ pub const segment_command = extern struct {...@@ -601,10 +605,10 @@ pub const segment_command = extern struct {
601 filesize: u32,605 filesize: u32,
602606
603 /// maximum VM protection607 /// maximum VM protection
604 maxprot: vm_prot_t,608 maxprot: os.vm_prot_t,
605609
606 /// initial VM protection610 /// initial VM protection
607 initprot: vm_prot_t,611 initprot: os.vm_prot_t,
608612
609 /// number of sections in segment613 /// number of sections in segment
610 nsects: u32,614 nsects: u32,
...@@ -638,10 +642,10 @@ pub const segment_command_64 = extern struct {...@@ -638,10 +642,10 @@ pub const segment_command_64 = extern struct {
638 filesize: u64 = 0,642 filesize: u64 = 0,
639643
640 /// maximum VM protection644 /// maximum VM protection
641 maxprot: vm_prot_t = VM_PROT_NONE,645 maxprot: os.vm_prot_t = os.PROT.NONE,
642646
643 /// initial VM protection647 /// initial VM protection
644 initprot: vm_prot_t = VM_PROT_NONE,648 initprot: os.vm_prot_t = os.PROT.NONE,
645649
646 /// number of sections in segment650 /// number of sections in segment
647 nsects: u32 = 0,651 nsects: u32 = 0,
...@@ -1438,11 +1442,6 @@ pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;...@@ -1438,11 +1442,6 @@ pub const S_THREAD_LOCAL_INIT_FUNCTION_POINTERS = 0x15;
1438/// 32-bit offsets to initializers1442/// 32-bit offsets to initializers
1439pub const S_INIT_FUNC_OFFSETS = 0x16;1443pub const S_INIT_FUNC_OFFSETS = 0x16;
14401444
1441pub const cpu_type_t = integer_t;
1442pub const cpu_subtype_t = integer_t;
1443pub const integer_t = c_int;
1444pub const vm_prot_t = c_int;
1445
1446/// CPU type targeting 64-bit Intel-based Macs1445/// CPU type targeting 64-bit Intel-based Macs
1447pub const CPU_TYPE_X86_64: cpu_type_t = 0x01000007;1446pub const CPU_TYPE_X86_64: cpu_type_t = 0x01000007;
14481447
...@@ -1455,26 +1454,6 @@ pub const CPU_SUBTYPE_X86_64_ALL: cpu_subtype_t = 0x3;...@@ -1455,26 +1454,6 @@ pub const CPU_SUBTYPE_X86_64_ALL: cpu_subtype_t = 0x3;
1455/// All ARM-based Macs1454/// All ARM-based Macs
1456pub const CPU_SUBTYPE_ARM_ALL: cpu_subtype_t = 0x0;1455pub const CPU_SUBTYPE_ARM_ALL: cpu_subtype_t = 0x0;
14571456
1458// Protection values defined as bits within the vm_prot_t type
1459/// No VM protection
1460pub const VM_PROT_NONE: vm_prot_t = 0x0;
1461
1462/// VM read permission
1463pub const VM_PROT_READ: vm_prot_t = 0x1;
1464
1465/// VM write permission
1466pub const VM_PROT_WRITE: vm_prot_t = 0x2;
1467
1468/// VM execute permission
1469pub const VM_PROT_EXECUTE: vm_prot_t = 0x4;
1470
1471/// When a caller finds that they cannot obtain write permission on a
1472/// mapped entry, the following flag can be used. The entry will be
1473/// made "needs copy" effectively copying the object (using COW),
1474/// and write permission will be added to the maximum protections for
1475/// the associated entry.
1476pub const VM_PROT_COPY: vm_prot_t = 0x10;
1477
1478// The following are used to encode rebasing information1457// The following are used to encode rebasing information
1479pub const REBASE_TYPE_POINTER: u8 = 1;1458pub const REBASE_TYPE_POINTER: u8 = 1;
1480pub const REBASE_TYPE_TEXT_ABSOLUTE32: u8 = 2;1459pub const REBASE_TYPE_TEXT_ABSOLUTE32: u8 = 2;
...@@ -2169,8 +2148,8 @@ test "read-write segment command" {...@@ -2169,8 +2148,8 @@ test "read-write segment command" {
2169 .vmaddr = 4294967296,2148 .vmaddr = 4294967296,
2170 .vmsize = 294912,2149 .vmsize = 294912,
2171 .filesize = 294912,2150 .filesize = 294912,
2172 .maxprot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,2151 .maxprot = os.PROT.READ | os.PROT.WRITE | os.PROT.EXEC,
2173 .initprot = VM_PROT_EXECUTE | VM_PROT_READ,2152 .initprot = os.PROT.EXEC | os.PROT.READ,
2174 .nsects = 1,2153 .nsects = 1,
2175 },2154 },
2176 };2155 };
lib/std/os.zig+1-1
...@@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator;...@@ -29,7 +29,7 @@ const Allocator = std.mem.Allocator;
29const Preopen = std.fs.wasi.Preopen;29const Preopen = std.fs.wasi.Preopen;
30const PreopenList = std.fs.wasi.PreopenList;30const PreopenList = std.fs.wasi.PreopenList;
3131
32pub const darwin = std.c;32pub const darwin = @import("os/darwin.zig");
33pub const dragonfly = std.c;33pub const dragonfly = std.c;
34pub const freebsd = std.c;34pub const freebsd = std.c;
35pub const haiku = std.c;35pub const haiku = std.c;
lib/std/os/darwin.zig created+216
...@@ -0,0 +1,216 @@
1const std = @import("std");
2const log = std.log;
3const mem = std.mem;
4const os = @This();
5
6pub usingnamespace @import("../c.zig");
7
8pub const MachError = error{
9 /// Not enough permissions held to perform the requested kernel
10 /// call.
11 PermissionDenied,
12 /// Kernel returned an unhandled and unexpected error code.
13 /// This is a catch-all for any yet unobserved kernel response
14 /// to some Mach message.
15 Unexpected,
16};
17
18pub const MachTask = struct {
19 port: os.mach_port_name_t,
20
21 pub fn isValid(self: MachTask) bool {
22 return self.port != 0;
23 }
24
25 pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!os.vm_prot_t {
26 var base_addr = address;
27 var base_len: os.mach_vm_size_t = if (len == 1) 2 else len;
28 var objname: os.mach_port_t = undefined;
29 var info: os.vm_region_submap_info_64 = undefined;
30 var count: os.mach_msg_type_number_t = os.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
31 switch (os.getKernError(os.mach_vm_region(
32 task.port,
33 &base_addr,
34 &base_len,
35 os.VM_REGION_BASIC_INFO_64,
36 @ptrCast(os.vm_region_info_t, &info),
37 &count,
38 &objname,
39 ))) {
40 .SUCCESS => return info.protection,
41 .FAILURE => return error.PermissionDenied,
42 else => |err| {
43 log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)});
44 return error.Unexpected;
45 },
46 }
47 }
48
49 pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: os.vm_prot_t) MachError!void {
50 return task.setProtectionImpl(address, len, true, prot);
51 }
52
53 pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: os.vm_prot_t) MachError!void {
54 return task.setProtectionImpl(address, len, false, prot);
55 }
56
57 fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: os.vm_prot_t) MachError!void {
58 switch (os.getKernError(os.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) {
59 .SUCCESS => return,
60 .FAILURE => return error.PermissionDenied,
61 else => |err| {
62 log.err("mach_vm_protect kernel call failed with error code: {s}", .{@tagName(err)});
63 return error.Unexpected;
64 },
65 }
66 }
67
68 /// Will write to VM even if current protection attributes specifically prohibit
69 /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY
70 /// variant, and resetting after a successful or unsuccessful write.
71 pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
72 const curr_prot = try task.getCurrProtection(address, buf.len);
73 try task.setCurrProtection(
74 address,
75 buf.len,
76 os.PROT.READ | os.PROT.WRITE | os.PROT.COPY,
77 );
78 defer {
79 task.setCurrProtection(address, buf.len, curr_prot) catch {};
80 }
81 return task.writeMem(address, buf, arch);
82 }
83
84 pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
85 const count = buf.len;
86 var total_written: usize = 0;
87 var curr_addr = address;
88 const page_size = try getPageSize(task); // TODO we probably can assume value here
89 var out_buf = buf[0..];
90
91 while (total_written < count) {
92 const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written);
93 switch (os.getKernError(os.mach_vm_write(
94 task.port,
95 curr_addr,
96 @ptrToInt(out_buf.ptr),
97 @intCast(os.mach_msg_type_number_t, curr_size),
98 ))) {
99 .SUCCESS => {},
100 .FAILURE => return error.PermissionDenied,
101 else => |err| {
102 log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)});
103 return error.Unexpected;
104 },
105 }
106
107 switch (arch) {
108 .aarch64 => {
109 var mattr_value: os.vm_machine_attribute_val_t = os.MATTR_VAL_CACHE_FLUSH;
110 switch (os.getKernError(os.vm_machine_attribute(
111 task.port,
112 curr_addr,
113 curr_size,
114 os.MATTR_CACHE,
115 &mattr_value,
116 ))) {
117 .SUCCESS => {},
118 .FAILURE => return error.PermissionDenied,
119 else => |err| {
120 log.err("vm_machine_attribute kernel call failed with error code: {s}", .{@tagName(err)});
121 return error.Unexpected;
122 },
123 }
124 },
125 .x86_64 => {},
126 else => unreachable,
127 }
128
129 out_buf = out_buf[curr_size..];
130 total_written += curr_size;
131 curr_addr += curr_size;
132 }
133
134 return total_written;
135 }
136
137 pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize {
138 const count = buf.len;
139 var total_read: usize = 0;
140 var curr_addr = address;
141 const page_size = try getPageSize(task); // TODO we probably can assume value here
142 var out_buf = buf[0..];
143
144 while (total_read < count) {
145 const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read);
146 var curr_bytes_read: os.mach_msg_type_number_t = 0;
147 var vm_memory: os.vm_offset_t = undefined;
148 switch (os.getKernError(os.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) {
149 .SUCCESS => {},
150 .FAILURE => return error.PermissionDenied,
151 else => |err| {
152 log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)});
153 return error.Unexpected;
154 },
155 }
156
157 @memcpy(out_buf[0..].ptr, @intToPtr([*]const u8, vm_memory), curr_bytes_read);
158 _ = os.vm_deallocate(os.mach_task_self(), vm_memory, curr_bytes_read);
159
160 out_buf = out_buf[curr_bytes_read..];
161 curr_addr += curr_bytes_read;
162 total_read += curr_bytes_read;
163 }
164
165 return total_read;
166 }
167
168 fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize {
169 var left = count;
170 if (page_size > 0) {
171 const page_offset = address % page_size;
172 const bytes_left_in_page = page_size - page_offset;
173 if (count > bytes_left_in_page) {
174 left = bytes_left_in_page;
175 }
176 }
177 return left;
178 }
179
180 fn getPageSize(task: MachTask) MachError!usize {
181 if (task.isValid()) {
182 var info_count = os.TASK_VM_INFO_COUNT;
183 var vm_info: os.task_vm_info_data_t = undefined;
184 switch (os.getKernError(os.task_info(
185 task.port,
186 os.TASK_VM_INFO,
187 @ptrCast(os.task_info_t, &vm_info),
188 &info_count,
189 ))) {
190 .SUCCESS => return @intCast(usize, vm_info.page_size),
191 else => {},
192 }
193 }
194 var page_size: os.vm_size_t = undefined;
195 switch (os.getKernError(os._host_page_size(os.mach_host_self(), &page_size))) {
196 .SUCCESS => return page_size,
197 else => |err| {
198 log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)});
199 return error.Unexpected;
200 },
201 }
202 }
203};
204
205pub fn machTaskForPid(pid: os.pid_t) MachError!MachTask {
206 var port: os.mach_port_name_t = undefined;
207 switch (os.getKernError(os.task_for_pid(os.mach_task_self(), pid, &port))) {
208 .SUCCESS => {},
209 .FAILURE => return error.PermissionDenied,
210 else => |err| {
211 log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)});
212 return error.Unexpected;
213 },
214 }
215 return MachTask{ .port = port };
216}
src/link/MachO.zig+9-8
...@@ -4,6 +4,7 @@ const std = @import("std");...@@ -4,6 +4,7 @@ const std = @import("std");
4const build_options = @import("build_options");4const build_options = @import("build_options");
5const builtin = @import("builtin");5const builtin = @import("builtin");
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const darwin = std.os.darwin;
7const fmt = std.fmt;8const fmt = std.fmt;
8const fs = std.fs;9const fs = std.fs;
9const log = std.log.scoped(.link);10const log = std.log.scoped(.link);
...@@ -4382,8 +4383,8 @@ fn populateMissingMetadata(self: *MachO) !void {...@@ -4382,8 +4383,8 @@ fn populateMissingMetadata(self: *MachO) !void {
4382 .vmaddr = pagezero_vmsize,4383 .vmaddr = pagezero_vmsize,
4383 .vmsize = needed_size,4384 .vmsize = needed_size,
4384 .filesize = needed_size,4385 .filesize = needed_size,
4385 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,4386 .maxprot = darwin.PROT.READ | darwin.PROT.EXEC,
4386 .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,4387 .initprot = darwin.PROT.READ | darwin.PROT.EXEC,
4387 },4388 },
4388 },4389 },
4389 });4390 });
...@@ -4487,8 +4488,8 @@ fn populateMissingMetadata(self: *MachO) !void {...@@ -4487,8 +4488,8 @@ fn populateMissingMetadata(self: *MachO) !void {
4487 .vmsize = needed_size,4488 .vmsize = needed_size,
4488 .fileoff = fileoff,4489 .fileoff = fileoff,
4489 .filesize = needed_size,4490 .filesize = needed_size,
4490 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,4491 .maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
4491 .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,4492 .initprot = darwin.PROT.READ | darwin.PROT.WRITE,
4492 },4493 },
4493 },4494 },
4494 });4495 });
...@@ -4536,8 +4537,8 @@ fn populateMissingMetadata(self: *MachO) !void {...@@ -4536,8 +4537,8 @@ fn populateMissingMetadata(self: *MachO) !void {
4536 .vmsize = needed_size,4537 .vmsize = needed_size,
4537 .fileoff = fileoff,4538 .fileoff = fileoff,
4538 .filesize = needed_size,4539 .filesize = needed_size,
4539 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,4540 .maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
4540 .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,4541 .initprot = darwin.PROT.READ | darwin.PROT.WRITE,
4541 },4542 },
4542 },4543 },
4543 });4544 });
...@@ -4645,8 +4646,8 @@ fn populateMissingMetadata(self: *MachO) !void {...@@ -4645,8 +4646,8 @@ fn populateMissingMetadata(self: *MachO) !void {
4645 .segname = makeStaticString("__LINKEDIT"),4646 .segname = makeStaticString("__LINKEDIT"),
4646 .vmaddr = vmaddr,4647 .vmaddr = vmaddr,
4647 .fileoff = fileoff,4648 .fileoff = fileoff,
4648 .maxprot = macho.VM_PROT_READ,4649 .maxprot = darwin.PROT.READ,
4649 .initprot = macho.VM_PROT_READ,4650 .initprot = darwin.PROT.READ,
4650 },4651 },
4651 },4652 },
4652 });4653 });