std+macho: revert and fix exposing Mach wrappers in std.os and std.c

This commit is contained in:
Jakub Konka 2022-03-13 17:03:04 +01:00
parent 4a2100e820
commit 76bceb240d
5 changed files with 220 additions and 204 deletions

View File

@ -39,12 +39,10 @@ pub fn versionCheck(glibc_version: std.builtin.Version) type {
};
}
pub const darwin = @import("c/darwin.zig");
pub usingnamespace switch (builtin.os.tag) {
.linux => @import("c/linux.zig"),
.windows => @import("c/windows.zig"),
.macos, .ios, .tvos, .watchos => darwin,
.macos, .ios, .tvos, .watchos => @import("c/darwin.zig"),
.freebsd, .kfreebsd => @import("c/freebsd.zig"),
.netbsd => @import("c/netbsd.zig"),
.dragonfly => @import("c/dragonfly.zig"),

View File

@ -2,15 +2,15 @@ const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const io = std.io;
const c = std.c.darwin;
const mem = std.mem;
const meta = std.meta;
const testing = std.testing;
const Allocator = mem.Allocator;
pub const cpu_type_t = c.integer_t;
pub const cpu_subtype_t = c.integer_t;
pub const cpu_type_t = c_int;
pub const cpu_subtype_t = c_int;
pub const vm_prot_t = c_int;
pub const mach_header = extern struct {
magic: u32,
@ -605,10 +605,10 @@ pub const segment_command = extern struct {
filesize: u32,
/// maximum VM protection
maxprot: c.vm_prot_t,
maxprot: vm_prot_t,
/// initial VM protection
initprot: c.vm_prot_t,
initprot: vm_prot_t,
/// number of sections in segment
nsects: u32,
@ -642,10 +642,10 @@ pub const segment_command_64 = extern struct {
filesize: u64 = 0,
/// maximum VM protection
maxprot: c.vm_prot_t = c.PROT.NONE,
maxprot: vm_prot_t = PROT.NONE,
/// initial VM protection
initprot: c.vm_prot_t = c.PROT.NONE,
initprot: vm_prot_t = PROT.NONE,
/// number of sections in segment
nsects: u32 = 0,
@ -656,6 +656,23 @@ pub const segment_command_64 = extern struct {
}
};
pub const PROT = struct {
/// [MC2] no permissions
pub const NONE: vm_prot_t = 0x00;
/// [MC2] pages can be read
pub const READ: vm_prot_t = 0x01;
/// [MC2] pages can be written
pub const WRITE: vm_prot_t = 0x02;
/// [MC2] pages can be executed
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;
};
/// A segment is made up of zero or more sections. Non-MH_OBJECT files have
/// all of their segments with the proper sections in each, and padded to the
/// specified segment alignment when produced by the link editor. The first
@ -2148,8 +2165,8 @@ test "read-write segment command" {
.vmaddr = 4294967296,
.vmsize = 294912,
.filesize = 294912,
.maxprot = c.PROT.READ | c.PROT.WRITE | c.PROT.EXEC,
.initprot = c.PROT.EXEC | c.PROT.READ,
.maxprot = PROT.READ | PROT.WRITE | PROT.EXEC,
.initprot = PROT.EXEC | PROT.READ,
.nsects = 1,
},
};

View File

@ -29,11 +29,7 @@ const Allocator = std.mem.Allocator;
const Preopen = std.fs.wasi.Preopen;
const PreopenList = std.fs.wasi.PreopenList;
pub const darwin = struct {
pub usingnamespace std.c;
pub usingnamespace @import("os/darwin.zig");
};
pub const darwin = @import("os/darwin.zig");
pub const dragonfly = std.c;
pub const freebsd = std.c;
pub const haiku = std.c;
@ -51,6 +47,7 @@ comptime {
}
test {
_ = darwin;
_ = linux;
_ = uefi;
_ = wasi;

View File

@ -1,214 +1,219 @@
const std = @import("std");
const c = std.c.darwin;
const builtin = @import("builtin");
const log = std.log;
const mem = std.mem;
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 usingnamespace std.c;
pub usingnamespace mach_task;
pub const MachTask = struct {
port: c.mach_port_name_t,
const mach_task = if (builtin.target.isDarwin()) struct {
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 fn isValid(self: MachTask) bool {
return self.port != 0;
}
pub const MachTask = struct {
port: std.c.mach_port_name_t,
pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!c.vm_prot_t {
var base_addr = address;
var base_len: c.mach_vm_size_t = if (len == 1) 2 else len;
var objname: c.mach_port_t = undefined;
var info: c.vm_region_submap_info_64 = undefined;
var count: c.mach_msg_type_number_t = c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
switch (c.getKernError(c.mach_vm_region(
task.port,
&base_addr,
&base_len,
c.VM_REGION_BASIC_INFO_64,
@ptrCast(c.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 isValid(self: MachTask) bool {
return self.port != 0;
}
}
pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, true, prot);
}
pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, false, prot);
}
fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: c.vm_prot_t) MachError!void {
switch (c.getKernError(c.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,
c.PROT.READ | c.PROT.WRITE | c.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 (c.getKernError(c.mach_vm_write(
pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t {
var base_addr = address;
var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len;
var objname: std.c.mach_port_t = undefined;
var info: std.c.vm_region_submap_info_64 = undefined;
var count: std.c.mach_msg_type_number_t = std.c.VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
switch (std.c.getKernError(std.c.mach_vm_region(
task.port,
curr_addr,
@ptrToInt(out_buf.ptr),
@intCast(c.mach_msg_type_number_t, curr_size),
&base_addr,
&base_len,
std.c.VM_REGION_BASIC_INFO_64,
@ptrCast(std.c.vm_region_info_t, &info),
&count,
&objname,
))) {
.SUCCESS => {},
.SUCCESS => return info.protection,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_write kernel call failed with error code: {s}", .{@tagName(err)});
log.err("mach_vm_region kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
switch (arch) {
.aarch64 => {
var mattr_value: c.vm_machine_attribute_val_t = c.MATTR_VAL_CACHE_FLUSH;
switch (c.getKernError(c.vm_machine_attribute(
task.port,
curr_addr,
curr_size,
c.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 setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, true, prot);
}
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..];
pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
return task.setProtectionImpl(address, len, false, prot);
}
while (total_read < count) {
const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read);
var curr_bytes_read: c.mach_msg_type_number_t = 0;
var vm_memory: c.vm_offset_t = undefined;
switch (c.getKernError(c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) {
.SUCCESS => {},
fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void {
switch (std.c.getKernError(std.c.mach_vm_protect(task.port, address, len, @boolToInt(set_max), prot))) {
.SUCCESS => return,
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("mach_vm_read kernel call failed with error code: {s}", .{@tagName(err)});
log.err("mach_vm_protect 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);
_ = c.vm_deallocate(c.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;
}
/// 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,
std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY,
);
defer {
task.setCurrProtection(address, buf.len, curr_prot) catch {};
}
return task.writeMem(address, buf, arch);
}
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;
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 (std.c.getKernError(std.c.mach_vm_write(
task.port,
curr_addr,
@ptrToInt(out_buf.ptr),
@intCast(std.c.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: std.c.vm_machine_attribute_val_t = std.c.MATTR_VAL_CACHE_FLUSH;
switch (std.c.getKernError(std.c.vm_machine_attribute(
task.port,
curr_addr,
curr_size,
std.c.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: std.c.mach_msg_type_number_t = 0;
var vm_memory: std.c.vm_offset_t = undefined;
switch (std.c.getKernError(std.c.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);
_ = std.c.vm_deallocate(std.c.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 = std.c.TASK_VM_INFO_COUNT;
var vm_info: std.c.task_vm_info_data_t = undefined;
switch (std.c.getKernError(std.c.task_info(
task.port,
std.c.TASK_VM_INFO,
@ptrCast(std.c.task_info_t, &vm_info),
&info_count,
))) {
.SUCCESS => return @intCast(usize, vm_info.page_size),
else => {},
}
}
var page_size: std.c.vm_size_t = undefined;
switch (std.c.getKernError(std.c._host_page_size(std.c.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;
},
}
}
return left;
}
};
fn getPageSize(task: MachTask) MachError!usize {
if (task.isValid()) {
var info_count = c.TASK_VM_INFO_COUNT;
var vm_info: c.task_vm_info_data_t = undefined;
switch (c.getKernError(c.task_info(
task.port,
c.TASK_VM_INFO,
@ptrCast(c.task_info_t, &vm_info),
&info_count,
))) {
.SUCCESS => return @intCast(usize, vm_info.page_size),
else => {},
}
}
var page_size: c.vm_size_t = undefined;
switch (c.getKernError(c._host_page_size(c.mach_host_self(), &page_size))) {
.SUCCESS => return page_size,
pub fn machTaskForPid(pid: std.os.pid_t) MachError!MachTask {
var port: std.c.mach_port_name_t = undefined;
switch (std.c.getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) {
.SUCCESS => {},
.FAILURE => return error.PermissionDenied,
else => |err| {
log.err("_host_page_size kernel call failed with error code: {s}", .{@tagName(err)});
log.err("task_for_pid kernel call failed with error code: {s}", .{@tagName(err)});
return error.Unexpected;
},
}
return MachTask{ .port = port };
}
};
pub fn machTaskForPid(pid: c.pid_t) MachError!MachTask {
var port: c.mach_port_name_t = undefined;
switch (c.getKernError(c.task_for_pid(c.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 };
}
} else struct {};

View File

@ -4,7 +4,6 @@ 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);
@ -4344,8 +4343,8 @@ fn populateMissingMetadata(self: *MachO) !void {
.vmaddr = pagezero_vmsize,
.vmsize = needed_size,
.filesize = needed_size,
.maxprot = darwin.PROT.READ | darwin.PROT.EXEC,
.initprot = darwin.PROT.READ | darwin.PROT.EXEC,
.maxprot = macho.PROT.READ | macho.PROT.EXEC,
.initprot = macho.PROT.READ | macho.PROT.EXEC,
},
},
});
@ -4449,8 +4448,8 @@ fn populateMissingMetadata(self: *MachO) !void {
.vmsize = needed_size,
.fileoff = fileoff,
.filesize = needed_size,
.maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
.initprot = darwin.PROT.READ | darwin.PROT.WRITE,
.maxprot = macho.PROT.READ | macho.PROT.WRITE,
.initprot = macho.PROT.READ | macho.PROT.WRITE,
},
},
});
@ -4498,8 +4497,8 @@ fn populateMissingMetadata(self: *MachO) !void {
.vmsize = needed_size,
.fileoff = fileoff,
.filesize = needed_size,
.maxprot = darwin.PROT.READ | darwin.PROT.WRITE,
.initprot = darwin.PROT.READ | darwin.PROT.WRITE,
.maxprot = macho.PROT.READ | macho.PROT.WRITE,
.initprot = macho.PROT.READ | macho.PROT.WRITE,
},
},
});
@ -4607,8 +4606,8 @@ fn populateMissingMetadata(self: *MachO) !void {
.segname = makeStaticString("__LINKEDIT"),
.vmaddr = vmaddr,
.fileoff = fileoff,
.maxprot = darwin.PROT.READ,
.initprot = darwin.PROT.READ,
.maxprot = macho.PROT.READ,
.initprot = macho.PROT.READ,
},
},
});