mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Merge pull request #9387 from ziglang/zld-incremental-2
macho+zld: bringing zld to stage2 MachO backend - simplicity is key!
This commit is contained in:
commit
f9798108f8
@ -581,14 +581,10 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/TextBlock.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Zld.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/reloc.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/tapi.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/tapi/parse.zig"
|
||||
|
||||
@ -894,6 +894,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
|
||||
// Make a decision on whether to use LLD or our own linker.
|
||||
const use_lld = options.use_lld orelse blk: {
|
||||
if (options.target.isDarwin()) {
|
||||
break :blk false;
|
||||
}
|
||||
|
||||
if (!build_options.have_llvm)
|
||||
break :blk false;
|
||||
|
||||
@ -931,11 +935,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
const darwin_can_use_system_sdk =
|
||||
// comptime conditions
|
||||
((build_options.have_llvm and comptime std.Target.current.isDarwin()) and
|
||||
// runtime conditions
|
||||
(use_lld and builtin.os.tag == .macos and options.target.isDarwin()));
|
||||
const darwin_can_use_system_sdk = blk: {
|
||||
if (comptime !std.Target.current.isDarwin()) break :blk false;
|
||||
break :blk std.builtin.os.tag == .macos and options.target.isDarwin();
|
||||
};
|
||||
|
||||
const sysroot = blk: {
|
||||
if (options.sysroot) |sysroot| {
|
||||
@ -952,10 +955,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
|
||||
const lto = blk: {
|
||||
if (options.want_lto) |explicit| {
|
||||
if (!use_lld)
|
||||
if (!use_lld and !options.target.isDarwin())
|
||||
return error.LtoUnavailableWithoutLld;
|
||||
break :blk explicit;
|
||||
} else if (!use_lld) {
|
||||
// TODO zig ld LTO support
|
||||
// See https://github.com/ziglang/zig/issues/8680
|
||||
break :blk false;
|
||||
} else if (options.c_source_files.len == 0) {
|
||||
break :blk false;
|
||||
|
||||
116
src/codegen.zig
116
src/codegen.zig
@ -2590,9 +2590,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
const got_addr = blk: {
|
||||
const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
|
||||
const got = seg.sections.items[macho_file.got_section_index.?];
|
||||
break :blk got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64);
|
||||
const got_index = macho_file.got_entries_map.get(.{
|
||||
.where = .local,
|
||||
.where_index = func.owner_decl.link.macho.local_sym_index,
|
||||
}) orelse unreachable;
|
||||
break :blk got.addr + got_index * @sizeOf(u64);
|
||||
};
|
||||
log.debug("got_addr = 0x{x}", .{got_addr});
|
||||
switch (arch) {
|
||||
.x86_64 => {
|
||||
try self.genSetReg(Type.initTag(.u64), .rax, .{ .memory = got_addr });
|
||||
@ -2609,37 +2612,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
}
|
||||
} else if (func_value.castTag(.extern_fn)) |func_payload| {
|
||||
const decl = func_payload.data;
|
||||
const decl_name = try std.fmt.allocPrint(self.bin_file.allocator, "_{s}", .{decl.name});
|
||||
defer self.bin_file.allocator.free(decl_name);
|
||||
const already_defined = macho_file.lazy_imports.contains(decl_name);
|
||||
const symbol: u32 = if (macho_file.lazy_imports.getIndex(decl_name)) |index|
|
||||
@intCast(u32, index)
|
||||
else
|
||||
try macho_file.addExternSymbol(decl_name);
|
||||
const start = self.code.items.len;
|
||||
const len: usize = blk: {
|
||||
const where_index = try macho_file.addExternFn(mem.spanZ(decl.name));
|
||||
const offset = blk: {
|
||||
switch (arch) {
|
||||
.x86_64 => {
|
||||
// callq
|
||||
try self.code.ensureCapacity(self.code.items.len + 5);
|
||||
self.code.appendSliceAssumeCapacity(&[5]u8{ 0xe8, 0x0, 0x0, 0x0, 0x0 });
|
||||
break :blk 5;
|
||||
break :blk @intCast(u32, self.code.items.len) - 4;
|
||||
},
|
||||
.aarch64 => {
|
||||
const offset = @intCast(u32, self.code.items.len);
|
||||
// bl
|
||||
writeInt(u32, try self.code.addManyAsArray(4), 0);
|
||||
break :blk 4;
|
||||
writeInt(u32, try self.code.addManyAsArray(4), Instruction.bl(0).toU32());
|
||||
break :blk offset;
|
||||
},
|
||||
else => unreachable, // unsupported architecture on MachO
|
||||
}
|
||||
};
|
||||
try macho_file.stub_fixups.append(self.bin_file.allocator, .{
|
||||
.symbol = symbol,
|
||||
.already_defined = already_defined,
|
||||
.start = start,
|
||||
.len = len,
|
||||
// Add relocation to the decl.
|
||||
try macho_file.active_decl.?.link.macho.relocs.append(self.bin_file.allocator, .{
|
||||
.offset = offset,
|
||||
.where = .import,
|
||||
.where_index = where_index,
|
||||
.payload = .{ .branch = .{
|
||||
.arch = arch,
|
||||
} },
|
||||
});
|
||||
// We mark the space and fix it up later.
|
||||
} else {
|
||||
return self.fail("TODO implement calling bitcasted functions", .{});
|
||||
}
|
||||
@ -4144,19 +4143,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.memory => |addr| {
|
||||
if (self.bin_file.options.pie) {
|
||||
// PC-relative displacement to the entry in the GOT table.
|
||||
// TODO we should come up with our own, backend independent relocation types
|
||||
// which each backend (Elf, MachO, etc.) would then translate into an actual
|
||||
// fixup when linking.
|
||||
// adrp reg, pages
|
||||
if (self.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
try macho_file.pie_fixups.append(self.bin_file.allocator, .{
|
||||
.target_addr = addr,
|
||||
.offset = self.code.items.len,
|
||||
.size = 4,
|
||||
});
|
||||
} else {
|
||||
return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
|
||||
}
|
||||
// adrp
|
||||
const offset = @intCast(u32, self.code.items.len);
|
||||
mem.writeIntLittle(
|
||||
u32,
|
||||
try self.code.addManyAsArray(4),
|
||||
@ -4169,6 +4157,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.offset = Instruction.LoadStoreOffset.imm(0),
|
||||
},
|
||||
}).toU32());
|
||||
|
||||
if (self.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
// TODO this is super awkward. We are reversing the address of the GOT entry here.
|
||||
// We should probably have it cached or move the reloc adding somewhere else.
|
||||
const got_addr = blk: {
|
||||
const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
|
||||
const got = seg.sections.items[macho_file.got_section_index.?];
|
||||
break :blk got.addr;
|
||||
};
|
||||
const where_index = blk: for (macho_file.got_entries.items) |key, id| {
|
||||
if (got_addr + id * @sizeOf(u64) == addr) break :blk key.where_index;
|
||||
} else unreachable;
|
||||
const decl = macho_file.active_decl.?;
|
||||
// Page reloc for adrp instruction.
|
||||
try decl.link.macho.relocs.append(self.bin_file.allocator, .{
|
||||
.offset = offset,
|
||||
.where = .local,
|
||||
.where_index = where_index,
|
||||
.payload = .{ .page = .{ .kind = .got } },
|
||||
});
|
||||
// Pageoff reloc for adrp instruction.
|
||||
try decl.link.macho.relocs.append(self.bin_file.allocator, .{
|
||||
.offset = offset + 4,
|
||||
.where = .local,
|
||||
.where_index = where_index,
|
||||
.payload = .{ .page_off = .{ .kind = .got } },
|
||||
});
|
||||
} else {
|
||||
return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
|
||||
}
|
||||
} else {
|
||||
// The value is in memory at a hard-coded address.
|
||||
// If the type is a pointer, it means the pointer address is at this memory location.
|
||||
@ -4421,14 +4439,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
encoder.modRm_RIPDisp32(reg.low_id());
|
||||
encoder.disp32(0);
|
||||
|
||||
// TODO we should come up with our own, backend independent relocation types
|
||||
// which each backend (Elf, MachO, etc.) would then translate into an actual
|
||||
// fixup when linking.
|
||||
const offset = @intCast(u32, self.code.items.len);
|
||||
|
||||
if (self.bin_file.cast(link.File.MachO)) |macho_file| {
|
||||
try macho_file.pie_fixups.append(self.bin_file.allocator, .{
|
||||
.target_addr = x,
|
||||
.offset = self.code.items.len - 4,
|
||||
.size = 4,
|
||||
// TODO this is super awkward. We are reversing the address of the GOT entry here.
|
||||
// We should probably have it cached or move the reloc adding somewhere else.
|
||||
const got_addr = blk: {
|
||||
const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
|
||||
const got = seg.sections.items[macho_file.got_section_index.?];
|
||||
break :blk got.addr;
|
||||
};
|
||||
const where_index = blk: for (macho_file.got_entries.items) |key, id| {
|
||||
if (got_addr + id * @sizeOf(u64) == x) break :blk key.where_index;
|
||||
} else unreachable;
|
||||
const decl = macho_file.active_decl.?;
|
||||
// Load reloc for LEA instruction.
|
||||
try decl.link.macho.relocs.append(self.bin_file.allocator, .{
|
||||
.offset = offset - 4,
|
||||
.where = .local,
|
||||
.where_index = where_index,
|
||||
.payload = .{ .load = .{ .kind = .got } },
|
||||
});
|
||||
} else {
|
||||
return self.fail("TODO implement genSetReg for PIE GOT indirection on this platform", .{});
|
||||
@ -4647,7 +4677,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
const got_addr = blk: {
|
||||
const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
|
||||
const got = seg.sections.items[macho_file.got_section_index.?];
|
||||
break :blk got.addr + decl.link.macho.offset_table_index * ptr_bytes;
|
||||
const got_index = macho_file.got_entries_map.get(.{
|
||||
.where = .local,
|
||||
.where_index = decl.link.macho.local_sym_index,
|
||||
}) orelse unreachable;
|
||||
break :blk got.addr + got_index * ptr_bytes;
|
||||
};
|
||||
return MCValue{ .memory = got_addr };
|
||||
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
|
||||
|
||||
@ -543,7 +543,7 @@ pub const File = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn linkAsArchive(base: *File, comp: *Compilation) !void {
|
||||
pub fn linkAsArchive(base: *File, comp: *Compilation) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
|
||||
3891
src/link/MachO.zig
3891
src/link/MachO.zig
File diff suppressed because it is too large
Load Diff
@ -81,6 +81,11 @@ const ar_hdr = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn date(self: ar_hdr) !u64 {
|
||||
const value = getValue(&self.ar_date);
|
||||
return std.fmt.parseInt(u64, value, 10);
|
||||
}
|
||||
|
||||
fn size(self: ar_hdr) !u32 {
|
||||
const value = getValue(&self.ar_size);
|
||||
return std.fmt.parseInt(u32, value, 10);
|
||||
@ -264,6 +269,7 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
|
||||
.file = try fs.cwd().openFile(self.name.?, .{}),
|
||||
.name = name,
|
||||
.file_offset = @intCast(u32, try reader.context.getPos()),
|
||||
.mtime = try self.header.?.date(),
|
||||
};
|
||||
try object.parse();
|
||||
try reader.context.seekTo(0);
|
||||
|
||||
@ -3,7 +3,7 @@ const DebugSymbols = @This();
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const fs = std.fs;
|
||||
const log = std.log.scoped(.link);
|
||||
const log = std.log.scoped(.dsym);
|
||||
const macho = std.macho;
|
||||
const mem = std.mem;
|
||||
const DW = std.dwarf;
|
||||
@ -27,9 +27,6 @@ const page_size: u16 = 0x1000;
|
||||
base: *MachO,
|
||||
file: fs.File,
|
||||
|
||||
/// Mach header
|
||||
header: ?macho.mach_header_64 = null,
|
||||
|
||||
/// Table of all load commands
|
||||
load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
|
||||
/// __PAGEZERO segment
|
||||
@ -78,9 +75,8 @@ dbg_info_decl_last: ?*TextBlock = null,
|
||||
/// Table of debug symbol names aka the debug string table.
|
||||
debug_string_table: std.ArrayListUnmanaged(u8) = .{},
|
||||
|
||||
header_dirty: bool = false,
|
||||
load_commands_dirty: bool = false,
|
||||
string_table_dirty: bool = false,
|
||||
strtab_dirty: bool = false,
|
||||
debug_string_table_dirty: bool = false,
|
||||
debug_abbrev_section_dirty: bool = false,
|
||||
debug_aranges_section_dirty: bool = false,
|
||||
@ -106,26 +102,10 @@ const min_nop_size = 2;
|
||||
/// You must call this function *after* `MachO.populateMissingMetadata()`
|
||||
/// has been called to get a viable debug symbols output.
|
||||
pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void {
|
||||
if (self.header == null) {
|
||||
const base_header = self.base.header.?;
|
||||
var header: macho.mach_header_64 = undefined;
|
||||
header.magic = macho.MH_MAGIC_64;
|
||||
header.cputype = base_header.cputype;
|
||||
header.cpusubtype = base_header.cpusubtype;
|
||||
header.filetype = macho.MH_DSYM;
|
||||
// These will get populated at the end of flushing the results to file.
|
||||
header.ncmds = 0;
|
||||
header.sizeofcmds = 0;
|
||||
header.flags = 0;
|
||||
header.reserved = 0;
|
||||
self.header = header;
|
||||
self.header_dirty = true;
|
||||
}
|
||||
if (self.uuid_cmd_index == null) {
|
||||
const base_cmd = self.base.load_commands.items[self.base.uuid_cmd_index.?];
|
||||
self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
try self.load_commands.append(allocator, base_cmd);
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.symtab_cmd_index == null) {
|
||||
@ -134,11 +114,11 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const symtab_size = base_cmd.nsyms * @sizeOf(macho.nlist_64);
|
||||
const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64));
|
||||
|
||||
log.debug("found dSym symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
|
||||
log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
|
||||
|
||||
const strtab_off = self.findFreeSpaceLinkedit(base_cmd.strsize, 1);
|
||||
|
||||
log.debug("found dSym string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize });
|
||||
log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize });
|
||||
|
||||
try self.load_commands.append(allocator, .{
|
||||
.Symtab = .{
|
||||
@ -150,16 +130,14 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.strsize = base_cmd.strsize,
|
||||
},
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.string_table_dirty = true;
|
||||
self.strtab_dirty = true;
|
||||
}
|
||||
if (self.pagezero_segment_cmd_index == null) {
|
||||
self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||
const base_cmd = self.base.load_commands.items[self.base.pagezero_segment_cmd_index.?].Segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .Segment = cmd });
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.text_segment_cmd_index == null) {
|
||||
@ -167,7 +145,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const base_cmd = self.base.load_commands.items[self.base.text_segment_cmd_index.?].Segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .Segment = cmd });
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.data_const_segment_cmd_index == null) outer: {
|
||||
@ -176,7 +153,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const base_cmd = self.base.load_commands.items[self.base.data_const_segment_cmd_index.?].Segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .Segment = cmd });
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.data_segment_cmd_index == null) outer: {
|
||||
@ -185,7 +161,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].Segment;
|
||||
const cmd = try self.copySegmentCommand(allocator, base_cmd);
|
||||
try self.load_commands.append(allocator, .{ .Segment = cmd });
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.linkedit_segment_cmd_index == null) {
|
||||
@ -196,7 +171,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
cmd.inner.fileoff = self.linkedit_off;
|
||||
cmd.inner.filesize = self.linkedit_size;
|
||||
try self.load_commands.append(allocator, .{ .Segment = cmd });
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.dwarf_segment_cmd_index == null) {
|
||||
@ -208,7 +182,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const off = linkedit.inner.fileoff + linkedit.inner.filesize;
|
||||
const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize;
|
||||
|
||||
log.debug("found dSym __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
|
||||
log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
|
||||
|
||||
try self.load_commands.append(allocator, .{
|
||||
.Segment = SegmentCommand.empty("__DWARF", .{
|
||||
@ -218,7 +192,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.filesize = needed_size,
|
||||
}),
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
}
|
||||
if (self.debug_str_section_index == null) {
|
||||
@ -232,7 +205,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.offset = @intCast(u32, dwarf_segment.inner.fileoff),
|
||||
.@"align" = 1,
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_string_table_dirty = true;
|
||||
}
|
||||
@ -244,7 +216,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const p_align = 1;
|
||||
const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
|
||||
|
||||
log.debug("found dSym __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
log.debug("found __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
|
||||
try dwarf_segment.addSection(allocator, "__debug_info", .{
|
||||
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
|
||||
@ -252,7 +224,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.offset = @intCast(u32, off),
|
||||
.@"align" = p_align,
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_info_header_dirty = true;
|
||||
}
|
||||
@ -264,7 +235,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const p_align = 1;
|
||||
const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
|
||||
|
||||
log.debug("found dSym __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
log.debug("found __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
|
||||
try dwarf_segment.addSection(allocator, "__debug_abbrev", .{
|
||||
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
|
||||
@ -272,7 +243,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.offset = @intCast(u32, off),
|
||||
.@"align" = p_align,
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_abbrev_section_dirty = true;
|
||||
}
|
||||
@ -284,7 +254,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const p_align = 16;
|
||||
const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
|
||||
|
||||
log.debug("found dSym __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
log.debug("found __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
|
||||
try dwarf_segment.addSection(allocator, "__debug_aranges", .{
|
||||
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
|
||||
@ -292,7 +262,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.offset = @intCast(u32, off),
|
||||
.@"align" = p_align,
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_aranges_section_dirty = true;
|
||||
}
|
||||
@ -304,7 +273,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
const p_align = 1;
|
||||
const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null);
|
||||
|
||||
log.debug("found dSym __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
log.debug("found __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint });
|
||||
|
||||
try dwarf_segment.addSection(allocator, "__debug_line", .{
|
||||
.addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff,
|
||||
@ -312,7 +281,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void
|
||||
.offset = @intCast(u32, off),
|
||||
.@"align" = p_align,
|
||||
});
|
||||
self.header_dirty = true;
|
||||
self.load_commands_dirty = true;
|
||||
self.debug_line_header_dirty = true;
|
||||
}
|
||||
@ -624,9 +592,8 @@ pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Opt
|
||||
try self.writeLoadCommands(allocator);
|
||||
try self.writeHeader();
|
||||
|
||||
assert(!self.header_dirty);
|
||||
assert(!self.load_commands_dirty);
|
||||
assert(!self.string_table_dirty);
|
||||
assert(!self.strtab_dirty);
|
||||
assert(!self.debug_abbrev_section_dirty);
|
||||
assert(!self.debug_aranges_section_dirty);
|
||||
assert(!self.debug_string_table_dirty);
|
||||
@ -716,23 +683,38 @@ fn writeLoadCommands(self: *DebugSymbols, allocator: *Allocator) !void {
|
||||
}
|
||||
|
||||
const off = @sizeOf(macho.mach_header_64);
|
||||
log.debug("writing {} dSym load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
|
||||
log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
|
||||
try self.file.pwriteAll(buffer, off);
|
||||
self.load_commands_dirty = false;
|
||||
}
|
||||
|
||||
fn writeHeader(self: *DebugSymbols) !void {
|
||||
if (!self.header_dirty) return;
|
||||
var header = emptyHeader(.{
|
||||
.filetype = macho.MH_DSYM,
|
||||
});
|
||||
|
||||
self.header.?.ncmds = @intCast(u32, self.load_commands.items.len);
|
||||
var sizeofcmds: u32 = 0;
|
||||
for (self.load_commands.items) |cmd| {
|
||||
sizeofcmds += cmd.cmdsize();
|
||||
switch (self.base.base.options.target.cpu.arch) {
|
||||
.aarch64 => {
|
||||
header.cputype = macho.CPU_TYPE_ARM64;
|
||||
header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
|
||||
},
|
||||
.x86_64 => {
|
||||
header.cputype = macho.CPU_TYPE_X86_64;
|
||||
header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
|
||||
},
|
||||
else => return error.UnsupportedCpuArchitecture,
|
||||
}
|
||||
self.header.?.sizeofcmds = sizeofcmds;
|
||||
log.debug("writing Mach-O dSym header {}", .{self.header.?});
|
||||
try self.file.pwriteAll(mem.asBytes(&self.header.?), 0);
|
||||
self.header_dirty = false;
|
||||
|
||||
header.ncmds = @intCast(u32, self.load_commands.items.len);
|
||||
header.sizeofcmds = 0;
|
||||
|
||||
for (self.load_commands.items) |cmd| {
|
||||
header.sizeofcmds += cmd.cmdsize();
|
||||
}
|
||||
|
||||
log.debug("writing Mach-O header {}", .{header});
|
||||
|
||||
try self.file.pwriteAll(mem.asBytes(&header), 0);
|
||||
}
|
||||
|
||||
fn allocatedSizeLinkedit(self: *DebugSymbols, start: u64) u64 {
|
||||
@ -798,7 +780,7 @@ fn relocateSymbolTable(self: *DebugSymbols) !void {
|
||||
const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64);
|
||||
|
||||
assert(new_symoff + existing_size <= self.linkedit_off + self.linkedit_size); // TODO expand LINKEDIT segment.
|
||||
log.debug("relocating dSym symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{
|
||||
log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{
|
||||
symtab.symoff,
|
||||
symtab.symoff + existing_size,
|
||||
new_symoff,
|
||||
@ -820,30 +802,30 @@ pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void {
|
||||
try self.relocateSymbolTable();
|
||||
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
|
||||
const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
|
||||
log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off });
|
||||
log.debug("writing local symbol {} at 0x{x}", .{ index, off });
|
||||
try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off);
|
||||
}
|
||||
|
||||
fn writeStringTable(self: *DebugSymbols) !void {
|
||||
if (!self.string_table_dirty) return;
|
||||
if (!self.strtab_dirty) return;
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
|
||||
const allocated_size = self.allocatedSizeLinkedit(symtab.stroff);
|
||||
const needed_size = mem.alignForwardGeneric(u64, self.base.string_table.items.len, @alignOf(u64));
|
||||
const needed_size = mem.alignForwardGeneric(u64, self.base.strtab.items.len, @alignOf(u64));
|
||||
|
||||
if (needed_size > allocated_size) {
|
||||
symtab.strsize = 0;
|
||||
symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
|
||||
}
|
||||
symtab.strsize = @intCast(u32, needed_size);
|
||||
log.debug("writing dSym string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
|
||||
log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
|
||||
|
||||
try self.file.pwriteAll(self.base.string_table.items, symtab.stroff);
|
||||
try self.file.pwriteAll(self.base.strtab.items, symtab.stroff);
|
||||
self.load_commands_dirty = true;
|
||||
self.string_table_dirty = false;
|
||||
self.strtab_dirty = false;
|
||||
}
|
||||
|
||||
pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
|
||||
|
||||
@ -12,13 +12,12 @@ const fat = @import("fat.zig");
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Arch = std.Target.Cpu.Arch;
|
||||
const Symbol = @import("Symbol.zig");
|
||||
const LibStub = @import("../tapi.zig").LibStub;
|
||||
const MachO = @import("../MachO.zig");
|
||||
|
||||
usingnamespace @import("commands.zig");
|
||||
|
||||
allocator: *Allocator,
|
||||
|
||||
arch: ?Arch = null,
|
||||
header: ?macho.mach_header_64 = null,
|
||||
file: ?fs.File = null,
|
||||
@ -146,7 +145,12 @@ pub const CreateOpts = struct {
|
||||
id: ?Id = null,
|
||||
};
|
||||
|
||||
pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, path: []const u8, opts: CreateOpts) Error!?[]*Dylib {
|
||||
pub fn createAndParseFromPath(
|
||||
allocator: *Allocator,
|
||||
arch: Arch,
|
||||
path: []const u8,
|
||||
opts: CreateOpts,
|
||||
) Error!?[]*Dylib {
|
||||
const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
else => |e| return e,
|
||||
@ -320,7 +324,7 @@ fn parseSymbols(self: *Dylib) !void {
|
||||
_ = try self.file.?.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
|
||||
|
||||
for (slice) |sym| {
|
||||
const add_to_symtab = Symbol.isExt(sym) and (Symbol.isSect(sym) or Symbol.isIndr(sym));
|
||||
const add_to_symtab = MachO.symbolIsExt(sym) and (MachO.symbolIsSect(sym) or MachO.symbolIsIndr(sym));
|
||||
|
||||
if (!add_to_symtab) continue;
|
||||
|
||||
@ -502,21 +506,3 @@ pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn createProxy(self: *Dylib, sym_name: []const u8) !?*Symbol {
|
||||
if (!self.symbols.contains(sym_name)) return null;
|
||||
|
||||
const name = try self.allocator.dupe(u8, sym_name);
|
||||
const proxy = try self.allocator.create(Symbol.Proxy);
|
||||
errdefer self.allocator.destroy(proxy);
|
||||
|
||||
proxy.* = .{
|
||||
.base = .{
|
||||
.@"type" = .proxy,
|
||||
.name = name,
|
||||
},
|
||||
.file = self,
|
||||
};
|
||||
|
||||
return &proxy.base;
|
||||
}
|
||||
|
||||
@ -7,14 +7,14 @@ const fs = std.fs;
|
||||
const io = std.io;
|
||||
const log = std.log.scoped(.object);
|
||||
const macho = std.macho;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const reloc = @import("reloc.zig");
|
||||
const sort = std.sort;
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Arch = std.Target.Cpu.Arch;
|
||||
const Relocation = reloc.Relocation;
|
||||
const Symbol = @import("Symbol.zig");
|
||||
const parseName = @import("Zld.zig").parseName;
|
||||
const MachO = @import("../MachO.zig");
|
||||
const TextBlock = @import("TextBlock.zig");
|
||||
|
||||
usingnamespace @import("commands.zig");
|
||||
|
||||
@ -26,7 +26,6 @@ file_offset: ?u32 = null,
|
||||
name: ?[]const u8 = null,
|
||||
|
||||
load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
|
||||
sections: std.ArrayListUnmanaged(Section) = .{},
|
||||
|
||||
segment_cmd_index: ?u16 = null,
|
||||
symtab_cmd_index: ?u16 = null,
|
||||
@ -44,71 +43,23 @@ dwarf_debug_str_index: ?u16 = null,
|
||||
dwarf_debug_line_index: ?u16 = null,
|
||||
dwarf_debug_ranges_index: ?u16 = null,
|
||||
|
||||
symbols: std.ArrayListUnmanaged(*Symbol) = .{},
|
||||
initializers: std.ArrayListUnmanaged(*Symbol) = .{},
|
||||
symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
|
||||
strtab: std.ArrayListUnmanaged(u8) = .{},
|
||||
data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{},
|
||||
|
||||
tu_path: ?[]const u8 = null,
|
||||
tu_mtime: ?u64 = null,
|
||||
// Debug info
|
||||
debug_info: ?DebugInfo = null,
|
||||
tu_name: ?[]const u8 = null,
|
||||
tu_comp_dir: ?[]const u8 = null,
|
||||
mtime: ?u64 = null,
|
||||
|
||||
pub const Section = struct {
|
||||
inner: macho.section_64,
|
||||
code: []u8,
|
||||
relocs: ?[]*Relocation,
|
||||
target_map: ?struct {
|
||||
segment_id: u16,
|
||||
section_id: u16,
|
||||
offset: u32,
|
||||
} = null,
|
||||
text_blocks: std.ArrayListUnmanaged(*TextBlock) = .{},
|
||||
sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{},
|
||||
|
||||
pub fn deinit(self: *Section, allocator: *Allocator) void {
|
||||
allocator.free(self.code);
|
||||
|
||||
if (self.relocs) |relocs| {
|
||||
for (relocs) |rel| {
|
||||
allocator.destroy(rel);
|
||||
}
|
||||
allocator.free(relocs);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn segname(self: Section) []const u8 {
|
||||
return parseName(&self.inner.segname);
|
||||
}
|
||||
|
||||
pub fn sectname(self: Section) []const u8 {
|
||||
return parseName(&self.inner.sectname);
|
||||
}
|
||||
|
||||
pub fn flags(self: Section) u32 {
|
||||
return self.inner.flags;
|
||||
}
|
||||
|
||||
pub fn sectionType(self: Section) u8 {
|
||||
return @truncate(u8, self.flags() & 0xff);
|
||||
}
|
||||
|
||||
pub fn sectionAttrs(self: Section) u32 {
|
||||
return self.flags() & 0xffffff00;
|
||||
}
|
||||
|
||||
pub fn isCode(self: Section) bool {
|
||||
const attr = self.sectionAttrs();
|
||||
return attr & macho.S_ATTR_PURE_INSTRUCTIONS != 0 or attr & macho.S_ATTR_SOME_INSTRUCTIONS != 0;
|
||||
}
|
||||
|
||||
pub fn isDebug(self: Section) bool {
|
||||
return self.sectionAttrs() & macho.S_ATTR_DEBUG != 0;
|
||||
}
|
||||
|
||||
pub fn dontDeadStrip(self: Section) bool {
|
||||
return self.sectionAttrs() & macho.S_ATTR_NO_DEAD_STRIP != 0;
|
||||
}
|
||||
|
||||
pub fn dontDeadStripIfReferencesLive(self: Section) bool {
|
||||
return self.sectionAttrs() & macho.S_ATTR_LIVE_SUPPORT != 0;
|
||||
}
|
||||
};
|
||||
// TODO symbol mapping and its inverse can probably be simple arrays
|
||||
// instead of hash maps.
|
||||
symbol_mapping: std.AutoHashMapUnmanaged(u32, u32) = .{},
|
||||
reverse_symbol_mapping: std.AutoHashMapUnmanaged(u32, u32) = .{},
|
||||
|
||||
const DebugInfo = struct {
|
||||
inner: dwarf.DwarfInfo,
|
||||
@ -211,27 +162,28 @@ pub fn deinit(self: *Object) void {
|
||||
lc.deinit(self.allocator);
|
||||
}
|
||||
self.load_commands.deinit(self.allocator);
|
||||
|
||||
for (self.sections.items) |*sect| {
|
||||
sect.deinit(self.allocator);
|
||||
}
|
||||
self.sections.deinit(self.allocator);
|
||||
|
||||
for (self.symbols.items) |sym| {
|
||||
sym.deinit(self.allocator);
|
||||
self.allocator.destroy(sym);
|
||||
}
|
||||
self.symbols.deinit(self.allocator);
|
||||
|
||||
self.data_in_code_entries.deinit(self.allocator);
|
||||
self.initializers.deinit(self.allocator);
|
||||
self.symtab.deinit(self.allocator);
|
||||
self.strtab.deinit(self.allocator);
|
||||
self.text_blocks.deinit(self.allocator);
|
||||
self.sections_as_symbols.deinit(self.allocator);
|
||||
self.symbol_mapping.deinit(self.allocator);
|
||||
self.reverse_symbol_mapping.deinit(self.allocator);
|
||||
|
||||
if (self.name) |n| {
|
||||
if (self.debug_info) |*db| {
|
||||
db.deinit(self.allocator);
|
||||
}
|
||||
|
||||
if (self.tu_name) |n| {
|
||||
self.allocator.free(n);
|
||||
}
|
||||
|
||||
if (self.tu_path) |tu_path| {
|
||||
self.allocator.free(tu_path);
|
||||
if (self.tu_comp_dir) |n| {
|
||||
self.allocator.free(n);
|
||||
}
|
||||
|
||||
if (self.name) |n| {
|
||||
self.allocator.free(n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,10 +222,8 @@ pub fn parse(self: *Object) !void {
|
||||
self.header = header;
|
||||
|
||||
try self.readLoadCommands(reader);
|
||||
try self.parseSymbols();
|
||||
try self.parseSections();
|
||||
try self.parseSymtab();
|
||||
try self.parseDataInCode();
|
||||
try self.parseInitializers();
|
||||
try self.parseDebugInfo();
|
||||
}
|
||||
|
||||
@ -290,8 +240,8 @@ pub fn readLoadCommands(self: *Object, reader: anytype) !void {
|
||||
var seg = cmd.Segment;
|
||||
for (seg.sections.items) |*sect, j| {
|
||||
const index = @intCast(u16, j);
|
||||
const segname = parseName(§.segname);
|
||||
const sectname = parseName(§.sectname);
|
||||
const segname = segmentName(sect.*);
|
||||
const sectname = sectionName(sect.*);
|
||||
if (mem.eql(u8, segname, "__DWARF")) {
|
||||
if (mem.eql(u8, sectname, "__debug_info")) {
|
||||
self.dwarf_debug_info_index = index;
|
||||
@ -345,62 +295,539 @@ pub fn readLoadCommands(self: *Object, reader: anytype) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseSections(self: *Object) !void {
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
|
||||
const NlistWithIndex = struct {
|
||||
nlist: macho.nlist_64,
|
||||
index: u32,
|
||||
|
||||
log.debug("parsing sections in {s}", .{self.name.?});
|
||||
fn lessThan(_: void, lhs: NlistWithIndex, rhs: NlistWithIndex) bool {
|
||||
// We sort by type: defined < undefined, and
|
||||
// afterwards by address in each group. Normally, dysymtab should
|
||||
// be enough to guarantee the sort, but turns out not every compiler
|
||||
// is kind enough to specify the symbols in the correct order.
|
||||
if (MachO.symbolIsSect(lhs.nlist)) {
|
||||
if (MachO.symbolIsSect(rhs.nlist)) {
|
||||
// Same group, sort by address.
|
||||
return lhs.nlist.n_value < rhs.nlist.n_value;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try self.sections.ensureCapacity(self.allocator, seg.sections.items.len);
|
||||
fn filterInSection(symbols: []NlistWithIndex, sect: macho.section_64) []NlistWithIndex {
|
||||
const Predicate = struct {
|
||||
addr: u64,
|
||||
|
||||
for (seg.sections.items) |sect| {
|
||||
log.debug("parsing section '{s},{s}'", .{ parseName(§.segname), parseName(§.sectname) });
|
||||
// Read sections' code
|
||||
var code = try self.allocator.alloc(u8, @intCast(usize, sect.size));
|
||||
_ = try self.file.?.preadAll(code, sect.offset);
|
||||
|
||||
var section = Section{
|
||||
.inner = sect,
|
||||
.code = code,
|
||||
.relocs = null,
|
||||
pub fn predicate(self: @This(), symbol: NlistWithIndex) bool {
|
||||
return symbol.nlist.n_value >= self.addr;
|
||||
}
|
||||
};
|
||||
|
||||
// Parse relocations
|
||||
if (sect.nreloc > 0) {
|
||||
var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc);
|
||||
defer self.allocator.free(raw_relocs);
|
||||
const start = MachO.findFirst(NlistWithIndex, symbols, 0, Predicate{ .addr = sect.addr });
|
||||
const end = MachO.findFirst(NlistWithIndex, symbols, start, Predicate{ .addr = sect.addr + sect.size });
|
||||
|
||||
_ = try self.file.?.preadAll(raw_relocs, sect.reloff);
|
||||
return symbols[start..end];
|
||||
}
|
||||
};
|
||||
|
||||
section.relocs = try reloc.parse(
|
||||
self.allocator,
|
||||
self.arch.?,
|
||||
section.code,
|
||||
mem.bytesAsSlice(macho.relocation_info, raw_relocs),
|
||||
self.symbols.items,
|
||||
fn filterDice(dices: []macho.data_in_code_entry, start_addr: u64, end_addr: u64) []macho.data_in_code_entry {
|
||||
const Predicate = struct {
|
||||
addr: u64,
|
||||
|
||||
pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool {
|
||||
return dice.offset >= self.addr;
|
||||
}
|
||||
};
|
||||
|
||||
const start = MachO.findFirst(macho.data_in_code_entry, dices, 0, Predicate{ .addr = start_addr });
|
||||
const end = MachO.findFirst(macho.data_in_code_entry, dices, start, Predicate{ .addr = end_addr });
|
||||
|
||||
return dices[start..end];
|
||||
}
|
||||
|
||||
const TextBlockParser = struct {
|
||||
allocator: *Allocator,
|
||||
section: macho.section_64,
|
||||
code: []u8,
|
||||
relocs: []macho.relocation_info,
|
||||
object: *Object,
|
||||
macho_file: *MachO,
|
||||
nlists: []NlistWithIndex,
|
||||
index: u32 = 0,
|
||||
match: MachO.MatchingSection,
|
||||
|
||||
fn peek(self: *TextBlockParser) ?NlistWithIndex {
|
||||
return if (self.index + 1 < self.nlists.len) self.nlists[self.index + 1] else null;
|
||||
}
|
||||
|
||||
const SeniorityContext = struct {
|
||||
object: *Object,
|
||||
};
|
||||
|
||||
fn lessThanBySeniority(context: SeniorityContext, lhs: NlistWithIndex, rhs: NlistWithIndex) bool {
|
||||
if (!MachO.symbolIsExt(rhs.nlist)) {
|
||||
return MachO.symbolIsTemp(lhs.nlist, context.object.getString(lhs.nlist.n_strx));
|
||||
} else if (MachO.symbolIsPext(rhs.nlist) or MachO.symbolIsWeakDef(rhs.nlist)) {
|
||||
return !MachO.symbolIsExt(lhs.nlist);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(self: *TextBlockParser) !?*TextBlock {
|
||||
if (self.index == self.nlists.len) return null;
|
||||
|
||||
var aliases = std.ArrayList(NlistWithIndex).init(self.allocator);
|
||||
defer aliases.deinit();
|
||||
|
||||
const next_nlist: ?NlistWithIndex = blk: while (true) {
|
||||
const curr_nlist = self.nlists[self.index];
|
||||
try aliases.append(curr_nlist);
|
||||
|
||||
if (self.peek()) |next_nlist| {
|
||||
if (curr_nlist.nlist.n_value == next_nlist.nlist.n_value) {
|
||||
self.index += 1;
|
||||
continue;
|
||||
}
|
||||
break :blk next_nlist;
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
for (aliases.items) |*nlist_with_index| {
|
||||
nlist_with_index.index = self.object.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
|
||||
}
|
||||
|
||||
if (aliases.items.len > 1) {
|
||||
// Bubble-up senior symbol as the main link to the text block.
|
||||
sort.sort(
|
||||
NlistWithIndex,
|
||||
aliases.items,
|
||||
SeniorityContext{ .object = self.object },
|
||||
TextBlockParser.lessThanBySeniority,
|
||||
);
|
||||
}
|
||||
|
||||
self.sections.appendAssumeCapacity(section);
|
||||
const senior_nlist = aliases.pop();
|
||||
const senior_sym = &self.macho_file.locals.items[senior_nlist.index];
|
||||
senior_sym.n_sect = self.macho_file.section_to_ordinal.get(self.match) orelse unreachable;
|
||||
|
||||
const start_addr = senior_nlist.nlist.n_value - self.section.addr;
|
||||
const end_addr = if (next_nlist) |n| n.nlist.n_value - self.section.addr else self.section.size;
|
||||
|
||||
const code = self.code[start_addr..end_addr];
|
||||
const size = code.len;
|
||||
|
||||
const max_align = self.section.@"align";
|
||||
const actual_align = if (senior_nlist.nlist.n_value > 0)
|
||||
math.min(@ctz(u64, senior_nlist.nlist.n_value), max_align)
|
||||
else
|
||||
max_align;
|
||||
|
||||
const stab: ?TextBlock.Stab = if (self.object.debug_info) |di| blk: {
|
||||
// TODO there has to be a better to handle this.
|
||||
for (di.inner.func_list.items) |func| {
|
||||
if (func.pc_range) |range| {
|
||||
if (senior_nlist.nlist.n_value >= range.start and senior_nlist.nlist.n_value < range.end) {
|
||||
break :blk TextBlock.Stab{
|
||||
.function = range.end - range.start,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global;
|
||||
break :blk .static;
|
||||
} else null;
|
||||
|
||||
const block = try self.macho_file.base.allocator.create(TextBlock);
|
||||
block.* = TextBlock.empty;
|
||||
block.local_sym_index = senior_nlist.index;
|
||||
block.stab = stab;
|
||||
block.size = size;
|
||||
block.alignment = actual_align;
|
||||
try self.macho_file.managed_blocks.append(self.macho_file.base.allocator, block);
|
||||
|
||||
try block.code.appendSlice(self.macho_file.base.allocator, code);
|
||||
|
||||
try block.aliases.ensureTotalCapacity(self.macho_file.base.allocator, aliases.items.len);
|
||||
for (aliases.items) |alias| {
|
||||
block.aliases.appendAssumeCapacity(alias.index);
|
||||
const sym = &self.macho_file.locals.items[alias.index];
|
||||
sym.n_sect = self.macho_file.section_to_ordinal.get(self.match) orelse unreachable;
|
||||
}
|
||||
|
||||
try block.parseRelocsFromObject(self.macho_file.base.allocator, self.relocs, self.object, .{
|
||||
.base_addr = start_addr,
|
||||
.macho_file = self.macho_file,
|
||||
});
|
||||
|
||||
if (self.macho_file.has_dices) {
|
||||
const dices = filterDice(
|
||||
self.object.data_in_code_entries.items,
|
||||
senior_nlist.nlist.n_value,
|
||||
senior_nlist.nlist.n_value + size,
|
||||
);
|
||||
try block.dices.ensureTotalCapacity(self.macho_file.base.allocator, dices.len);
|
||||
|
||||
for (dices) |dice| {
|
||||
block.dices.appendAssumeCapacity(.{
|
||||
.offset = dice.offset - try math.cast(u32, senior_nlist.nlist.n_value),
|
||||
.length = dice.length,
|
||||
.kind = dice.kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.index += 1;
|
||||
|
||||
return block;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parseTextBlocks(self: *Object, macho_file: *MachO) !void {
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
|
||||
|
||||
log.debug("analysing {s}", .{self.name.?});
|
||||
|
||||
// You would expect that the symbol table is at least pre-sorted based on symbol's type:
|
||||
// local < extern defined < undefined. Unfortunately, this is not guaranteed! For instance,
|
||||
// the GO compiler does not necessarily respect that therefore we sort immediately by type
|
||||
// and address within.
|
||||
var sorted_all_nlists = std.ArrayList(NlistWithIndex).init(self.allocator);
|
||||
defer sorted_all_nlists.deinit();
|
||||
try sorted_all_nlists.ensureTotalCapacity(self.symtab.items.len);
|
||||
|
||||
for (self.symtab.items) |nlist, index| {
|
||||
sorted_all_nlists.appendAssumeCapacity(.{
|
||||
.nlist = nlist,
|
||||
.index = @intCast(u32, index),
|
||||
});
|
||||
}
|
||||
|
||||
sort.sort(NlistWithIndex, sorted_all_nlists.items, {}, NlistWithIndex.lessThan);
|
||||
|
||||
// Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we
|
||||
// have to infer the start of undef section in the symtab ourselves.
|
||||
const iundefsym = if (self.dysymtab_cmd_index) |cmd_index| blk: {
|
||||
const dysymtab = self.load_commands.items[cmd_index].Dysymtab;
|
||||
break :blk dysymtab.iundefsym;
|
||||
} else blk: {
|
||||
var iundefsym: usize = sorted_all_nlists.items.len;
|
||||
while (iundefsym > 0) : (iundefsym -= 1) {
|
||||
const nlist = sorted_all_nlists.items[iundefsym];
|
||||
if (MachO.symbolIsSect(nlist.nlist)) break;
|
||||
}
|
||||
break :blk iundefsym;
|
||||
};
|
||||
|
||||
// We only care about defined symbols, so filter every other out.
|
||||
const sorted_nlists = sorted_all_nlists.items[0..iundefsym];
|
||||
|
||||
for (seg.sections.items) |sect, id| {
|
||||
const sect_id = @intCast(u8, id);
|
||||
log.debug("putting section '{s},{s}' as a TextBlock", .{
|
||||
segmentName(sect),
|
||||
sectionName(sect),
|
||||
});
|
||||
|
||||
// Get matching segment/section in the final artifact.
|
||||
const match = (try macho_file.getMatchingSection(sect)) orelse {
|
||||
log.debug("unhandled section", .{});
|
||||
continue;
|
||||
};
|
||||
|
||||
// Read section's code
|
||||
var code = try self.allocator.alloc(u8, @intCast(usize, sect.size));
|
||||
defer self.allocator.free(code);
|
||||
_ = try self.file.?.preadAll(code, sect.offset);
|
||||
|
||||
// Read section's list of relocations
|
||||
var raw_relocs = try self.allocator.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
|
||||
defer self.allocator.free(raw_relocs);
|
||||
_ = try self.file.?.preadAll(raw_relocs, sect.reloff);
|
||||
const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs);
|
||||
|
||||
// Symbols within this section only.
|
||||
const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect);
|
||||
|
||||
// In release mode, if the object file was generated with dead code stripping optimisations,
|
||||
// note it now and parse sections as atoms.
|
||||
const is_splittable = blk: {
|
||||
if (macho_file.base.options.optimize_mode == .Debug) break :blk false;
|
||||
break :blk self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
|
||||
};
|
||||
|
||||
macho_file.has_dices = blk: {
|
||||
if (self.text_section_index) |index| {
|
||||
if (index != id) break :blk false;
|
||||
if (self.data_in_code_entries.items.len == 0) break :blk false;
|
||||
break :blk true;
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null;
|
||||
|
||||
next: {
|
||||
if (is_splittable) blocks: {
|
||||
if (filtered_nlists.len == 0) break :blocks;
|
||||
|
||||
// If the first nlist does not match the start of the section,
|
||||
// then we need to encapsulate the memory range [section start, first symbol)
|
||||
// as a temporary symbol and insert the matching TextBlock.
|
||||
const first_nlist = filtered_nlists[0].nlist;
|
||||
if (first_nlist.n_value > sect.addr) {
|
||||
const sym_name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
|
||||
self.name.?,
|
||||
segmentName(sect),
|
||||
sectionName(sect),
|
||||
});
|
||||
defer self.allocator.free(sym_name);
|
||||
|
||||
const block_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
|
||||
const block_local_sym_index = @intCast(u32, macho_file.locals.items.len);
|
||||
try macho_file.locals.append(macho_file.base.allocator, .{
|
||||
.n_strx = try macho_file.makeString(sym_name),
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = macho_file.section_to_ordinal.get(match) orelse unreachable,
|
||||
.n_desc = 0,
|
||||
.n_value = sect.addr,
|
||||
});
|
||||
try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, block_local_sym_index);
|
||||
break :blk block_local_sym_index;
|
||||
};
|
||||
|
||||
const block_code = code[0 .. first_nlist.n_value - sect.addr];
|
||||
const block_size = block_code.len;
|
||||
|
||||
const block = try macho_file.base.allocator.create(TextBlock);
|
||||
block.* = TextBlock.empty;
|
||||
block.local_sym_index = block_local_sym_index;
|
||||
block.size = block_size;
|
||||
block.alignment = sect.@"align";
|
||||
try macho_file.managed_blocks.append(macho_file.base.allocator, block);
|
||||
|
||||
try block.code.appendSlice(macho_file.base.allocator, block_code);
|
||||
|
||||
try block.parseRelocsFromObject(self.allocator, relocs, self, .{
|
||||
.base_addr = 0,
|
||||
.macho_file = macho_file,
|
||||
});
|
||||
|
||||
if (macho_file.has_dices) {
|
||||
const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + block_size);
|
||||
try block.dices.ensureTotalCapacity(macho_file.base.allocator, dices.len);
|
||||
|
||||
for (dices) |dice| {
|
||||
block.dices.appendAssumeCapacity(.{
|
||||
.offset = dice.offset - try math.cast(u32, sect.addr),
|
||||
.length = dice.length,
|
||||
.kind = dice.kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update target section's metadata
|
||||
// TODO should we update segment's size here too?
|
||||
// How does it tie with incremental space allocs?
|
||||
const tseg = &macho_file.load_commands.items[match.seg].Segment;
|
||||
const tsect = &tseg.sections.items[match.sect];
|
||||
const new_alignment = math.max(tsect.@"align", block.alignment);
|
||||
const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
|
||||
const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
|
||||
tsect.size = new_size;
|
||||
tsect.@"align" = new_alignment;
|
||||
|
||||
if (macho_file.blocks.getPtr(match)) |last| {
|
||||
last.*.next = block;
|
||||
block.prev = last.*;
|
||||
last.* = block;
|
||||
} else {
|
||||
try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
|
||||
}
|
||||
|
||||
try self.text_blocks.append(self.allocator, block);
|
||||
}
|
||||
|
||||
var parser = TextBlockParser{
|
||||
.allocator = self.allocator,
|
||||
.section = sect,
|
||||
.code = code,
|
||||
.relocs = relocs,
|
||||
.object = self,
|
||||
.macho_file = macho_file,
|
||||
.nlists = filtered_nlists,
|
||||
.match = match,
|
||||
};
|
||||
|
||||
while (try parser.next()) |block| {
|
||||
const sym = macho_file.locals.items[block.local_sym_index];
|
||||
const is_ext = blk: {
|
||||
const orig_sym_id = self.reverse_symbol_mapping.get(block.local_sym_index) orelse unreachable;
|
||||
break :blk MachO.symbolIsExt(self.symtab.items[orig_sym_id]);
|
||||
};
|
||||
if (is_ext) {
|
||||
if (macho_file.symbol_resolver.get(sym.n_strx)) |resolv| {
|
||||
assert(resolv.where == .global);
|
||||
const global_object = macho_file.objects.items[resolv.file];
|
||||
if (global_object != self) {
|
||||
log.debug("deduping definition of {s} in {s}", .{
|
||||
macho_file.getString(sym.n_strx),
|
||||
self.name.?,
|
||||
});
|
||||
log.debug(" already defined in {s}", .{global_object.name.?});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sym.n_value == sect.addr) {
|
||||
if (self.sections_as_symbols.get(sect_id)) |alias| {
|
||||
// In x86_64 relocs, it can so happen that the compiler refers to the same
|
||||
// atom by both the actual assigned symbol and the start of the section. In this
|
||||
// case, we need to link the two together so add an alias.
|
||||
try block.aliases.append(macho_file.base.allocator, alias);
|
||||
}
|
||||
}
|
||||
|
||||
// Update target section's metadata
|
||||
// TODO should we update segment's size here too?
|
||||
// How does it tie with incremental space allocs?
|
||||
const tseg = &macho_file.load_commands.items[match.seg].Segment;
|
||||
const tsect = &tseg.sections.items[match.sect];
|
||||
const new_alignment = math.max(tsect.@"align", block.alignment);
|
||||
const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
|
||||
const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
|
||||
tsect.size = new_size;
|
||||
tsect.@"align" = new_alignment;
|
||||
|
||||
if (macho_file.blocks.getPtr(match)) |last| {
|
||||
last.*.next = block;
|
||||
block.prev = last.*;
|
||||
last.* = block;
|
||||
} else {
|
||||
try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
|
||||
}
|
||||
|
||||
try self.text_blocks.append(self.allocator, block);
|
||||
}
|
||||
|
||||
break :next;
|
||||
}
|
||||
|
||||
// Since there is no symbol to refer to this block, we create
|
||||
// a temp one, unless we already did that when working out the relocations
|
||||
// of other text blocks.
|
||||
const sym_name = try std.fmt.allocPrint(self.allocator, "l_{s}_{s}_{s}", .{
|
||||
self.name.?,
|
||||
segmentName(sect),
|
||||
sectionName(sect),
|
||||
});
|
||||
defer self.allocator.free(sym_name);
|
||||
|
||||
const block_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: {
|
||||
const block_local_sym_index = @intCast(u32, macho_file.locals.items.len);
|
||||
try macho_file.locals.append(macho_file.base.allocator, .{
|
||||
.n_strx = try macho_file.makeString(sym_name),
|
||||
.n_type = macho.N_SECT,
|
||||
.n_sect = macho_file.section_to_ordinal.get(match) orelse unreachable,
|
||||
.n_desc = 0,
|
||||
.n_value = sect.addr,
|
||||
});
|
||||
try self.sections_as_symbols.putNoClobber(self.allocator, sect_id, block_local_sym_index);
|
||||
break :blk block_local_sym_index;
|
||||
};
|
||||
|
||||
const block = try macho_file.base.allocator.create(TextBlock);
|
||||
block.* = TextBlock.empty;
|
||||
block.local_sym_index = block_local_sym_index;
|
||||
block.size = sect.size;
|
||||
block.alignment = sect.@"align";
|
||||
try macho_file.managed_blocks.append(macho_file.base.allocator, block);
|
||||
|
||||
try block.code.appendSlice(macho_file.base.allocator, code);
|
||||
|
||||
try block.parseRelocsFromObject(self.allocator, relocs, self, .{
|
||||
.base_addr = 0,
|
||||
.macho_file = macho_file,
|
||||
});
|
||||
|
||||
if (macho_file.has_dices) {
|
||||
const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size);
|
||||
try block.dices.ensureTotalCapacity(macho_file.base.allocator, dices.len);
|
||||
|
||||
for (dices) |dice| {
|
||||
block.dices.appendAssumeCapacity(.{
|
||||
.offset = dice.offset - try math.cast(u32, sect.addr),
|
||||
.length = dice.length,
|
||||
.kind = dice.kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Since this is block gets a helper local temporary symbol that didn't exist
|
||||
// in the object file which encompasses the entire section, we need traverse
|
||||
// the filtered symbols and note which symbol is contained within so that
|
||||
// we can properly allocate addresses down the line.
|
||||
// While we're at it, we need to update segment,section mapping of each symbol too.
|
||||
try block.contained.ensureTotalCapacity(self.allocator, filtered_nlists.len);
|
||||
|
||||
for (filtered_nlists) |nlist_with_index| {
|
||||
const nlist = nlist_with_index.nlist;
|
||||
const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable;
|
||||
const local = &macho_file.locals.items[local_sym_index];
|
||||
local.n_sect = macho_file.section_to_ordinal.get(match) orelse unreachable;
|
||||
|
||||
const stab: ?TextBlock.Stab = if (self.debug_info) |di| blk: {
|
||||
// TODO there has to be a better to handle this.
|
||||
for (di.inner.func_list.items) |func| {
|
||||
if (func.pc_range) |range| {
|
||||
if (nlist.n_value >= range.start and nlist.n_value < range.end) {
|
||||
break :blk TextBlock.Stab{
|
||||
.function = range.end - range.start,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global;
|
||||
break :blk .static;
|
||||
} else null;
|
||||
|
||||
block.contained.appendAssumeCapacity(.{
|
||||
.local_sym_index = local_sym_index,
|
||||
.offset = nlist.n_value - sect.addr,
|
||||
.stab = stab,
|
||||
});
|
||||
}
|
||||
|
||||
// Update target section's metadata
|
||||
// TODO should we update segment's size here too?
|
||||
// How does it tie with incremental space allocs?
|
||||
const tseg = &macho_file.load_commands.items[match.seg].Segment;
|
||||
const tsect = &tseg.sections.items[match.sect];
|
||||
const new_alignment = math.max(tsect.@"align", block.alignment);
|
||||
const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
|
||||
const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
|
||||
tsect.size = new_size;
|
||||
tsect.@"align" = new_alignment;
|
||||
|
||||
if (macho_file.blocks.getPtr(match)) |last| {
|
||||
last.*.next = block;
|
||||
block.prev = last.*;
|
||||
last.* = block;
|
||||
} else {
|
||||
try macho_file.blocks.putNoClobber(macho_file.base.allocator, match, block);
|
||||
}
|
||||
|
||||
try self.text_blocks.append(self.allocator, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseInitializers(self: *Object) !void {
|
||||
const index = self.mod_init_func_section_index orelse return;
|
||||
const section = self.sections.items[index];
|
||||
|
||||
log.debug("parsing initializers in {s}", .{self.name.?});
|
||||
|
||||
// Parse C++ initializers
|
||||
const relocs = section.relocs orelse unreachable;
|
||||
try self.initializers.ensureCapacity(self.allocator, relocs.len);
|
||||
for (relocs) |rel| {
|
||||
self.initializers.appendAssumeCapacity(rel.target.symbol);
|
||||
}
|
||||
|
||||
mem.reverse(*Symbol, self.initializers.items);
|
||||
}
|
||||
|
||||
pub fn parseSymbols(self: *Object) !void {
|
||||
fn parseSymtab(self: *Object) !void {
|
||||
const index = self.symtab_cmd_index orelse return;
|
||||
const symtab_cmd = self.load_commands.items[index].Symtab;
|
||||
|
||||
@ -408,90 +835,21 @@ pub fn parseSymbols(self: *Object) !void {
|
||||
defer self.allocator.free(symtab);
|
||||
_ = try self.file.?.preadAll(symtab, symtab_cmd.symoff);
|
||||
const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
|
||||
try self.symtab.appendSlice(self.allocator, slice);
|
||||
|
||||
var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize);
|
||||
defer self.allocator.free(strtab);
|
||||
_ = try self.file.?.preadAll(strtab, symtab_cmd.stroff);
|
||||
|
||||
for (slice) |sym| {
|
||||
const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
|
||||
|
||||
if (Symbol.isStab(sym)) {
|
||||
log.err("unhandled symbol type: stab {s} in {s}", .{ sym_name, self.name.? });
|
||||
return error.UnhandledSymbolType;
|
||||
}
|
||||
if (Symbol.isIndr(sym)) {
|
||||
log.err("unhandled symbol type: indirect {s} in {s}", .{ sym_name, self.name.? });
|
||||
return error.UnhandledSymbolType;
|
||||
}
|
||||
if (Symbol.isAbs(sym)) {
|
||||
log.err("unhandled symbol type: absolute {s} in {s}", .{ sym_name, self.name.? });
|
||||
return error.UnhandledSymbolType;
|
||||
}
|
||||
|
||||
const name = try self.allocator.dupe(u8, sym_name);
|
||||
const symbol: *Symbol = symbol: {
|
||||
if (Symbol.isSect(sym)) {
|
||||
const linkage: Symbol.Regular.Linkage = linkage: {
|
||||
if (!Symbol.isExt(sym)) break :linkage .translation_unit;
|
||||
if (Symbol.isWeakDef(sym) or Symbol.isPext(sym)) break :linkage .linkage_unit;
|
||||
break :linkage .global;
|
||||
};
|
||||
const regular = try self.allocator.create(Symbol.Regular);
|
||||
errdefer self.allocator.destroy(regular);
|
||||
regular.* = .{
|
||||
.base = .{
|
||||
.@"type" = .regular,
|
||||
.name = name,
|
||||
},
|
||||
.linkage = linkage,
|
||||
.address = sym.n_value,
|
||||
.section = sym.n_sect - 1,
|
||||
.weak_ref = Symbol.isWeakRef(sym),
|
||||
.file = self,
|
||||
};
|
||||
break :symbol ®ular.base;
|
||||
}
|
||||
|
||||
if (sym.n_value != 0) {
|
||||
const tentative = try self.allocator.create(Symbol.Tentative);
|
||||
errdefer self.allocator.destroy(tentative);
|
||||
tentative.* = .{
|
||||
.base = .{
|
||||
.@"type" = .tentative,
|
||||
.name = name,
|
||||
},
|
||||
.size = sym.n_value,
|
||||
.alignment = (sym.n_desc >> 8) & 0x0f,
|
||||
.file = self,
|
||||
};
|
||||
break :symbol &tentative.base;
|
||||
}
|
||||
|
||||
const undef = try self.allocator.create(Symbol.Unresolved);
|
||||
errdefer self.allocator.destroy(undef);
|
||||
undef.* = .{
|
||||
.base = .{
|
||||
.@"type" = .unresolved,
|
||||
.name = name,
|
||||
},
|
||||
.file = self,
|
||||
};
|
||||
break :symbol &undef.base;
|
||||
};
|
||||
|
||||
try self.symbols.append(self.allocator, symbol);
|
||||
}
|
||||
try self.strtab.appendSlice(self.allocator, strtab);
|
||||
}
|
||||
|
||||
pub fn parseDebugInfo(self: *Object) !void {
|
||||
log.debug("parsing debug info in '{s}'", .{self.name.?});
|
||||
|
||||
var debug_info = blk: {
|
||||
var di = try DebugInfo.parseFromObject(self.allocator, self);
|
||||
break :blk di orelse return;
|
||||
};
|
||||
defer debug_info.deinit(self.allocator);
|
||||
|
||||
log.debug("parsing debug info in '{s}'", .{self.name.?});
|
||||
|
||||
// We assume there is only one CU.
|
||||
const compile_unit = debug_info.inner.findCompileUnit(0x0) catch |err| switch (err) {
|
||||
@ -505,44 +863,19 @@ pub fn parseDebugInfo(self: *Object) !void {
|
||||
const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name);
|
||||
const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir);
|
||||
|
||||
self.tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name });
|
||||
self.tu_mtime = mtime: {
|
||||
const stat = try self.file.?.stat();
|
||||
break :mtime @intCast(u64, @divFloor(stat.mtime, 1_000_000_000));
|
||||
};
|
||||
self.debug_info = debug_info;
|
||||
self.tu_name = try self.allocator.dupe(u8, name);
|
||||
self.tu_comp_dir = try self.allocator.dupe(u8, comp_dir);
|
||||
|
||||
for (self.symbols.items) |sym| {
|
||||
if (sym.cast(Symbol.Regular)) |reg| {
|
||||
const size: u64 = blk: for (debug_info.inner.func_list.items) |func| {
|
||||
if (func.pc_range) |range| {
|
||||
if (reg.address >= range.start and reg.address < range.end) {
|
||||
break :blk range.end - range.start;
|
||||
}
|
||||
}
|
||||
} else 0;
|
||||
|
||||
reg.stab = .{
|
||||
.kind = kind: {
|
||||
if (size > 0) break :kind .function;
|
||||
switch (reg.linkage) {
|
||||
.translation_unit => break :kind .static,
|
||||
else => break :kind .global,
|
||||
}
|
||||
},
|
||||
.size = size,
|
||||
};
|
||||
}
|
||||
if (self.mtime == null) {
|
||||
self.mtime = mtime: {
|
||||
const file = self.file orelse break :mtime 0;
|
||||
const stat = file.stat() catch break :mtime 0;
|
||||
break :mtime @intCast(u64, @divFloor(stat.mtime, 1_000_000_000));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
|
||||
const sect = seg.sections.items[index];
|
||||
var buffer = try allocator.alloc(u8, @intCast(usize, sect.size));
|
||||
_ = try self.file.?.preadAll(buffer, sect.offset);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn parseDataInCode(self: *Object) !void {
|
||||
const index = self.data_in_code_cmd_index orelse return;
|
||||
const data_in_code = self.load_commands.items[index].LinkeditData;
|
||||
@ -562,3 +895,16 @@ pub fn parseDataInCode(self: *Object) !void {
|
||||
try self.data_in_code_entries.append(self.allocator, dice);
|
||||
}
|
||||
}
|
||||
|
||||
fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
|
||||
const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
|
||||
const sect = seg.sections.items[index];
|
||||
var buffer = try allocator.alloc(u8, @intCast(usize, sect.size));
|
||||
_ = try self.file.?.preadAll(buffer, sect.offset);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn getString(self: Object, off: u32) []const u8 {
|
||||
assert(off < self.strtab.items.len);
|
||||
return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
|
||||
}
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
const Symbol = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const macho = std.macho;
|
||||
const mem = std.mem;
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Dylib = @import("Dylib.zig");
|
||||
const Object = @import("Object.zig");
|
||||
|
||||
pub const Type = enum {
|
||||
regular,
|
||||
proxy,
|
||||
unresolved,
|
||||
tentative,
|
||||
};
|
||||
|
||||
/// Symbol type.
|
||||
@"type": Type,
|
||||
|
||||
/// Symbol name. Owned slice.
|
||||
name: []u8,
|
||||
|
||||
/// Alias of.
|
||||
alias: ?*Symbol = null,
|
||||
|
||||
/// Index in GOT table for indirection.
|
||||
got_index: ?u32 = null,
|
||||
|
||||
/// Index in stubs table for late binding.
|
||||
stubs_index: ?u32 = null,
|
||||
|
||||
pub const Regular = struct {
|
||||
base: Symbol,
|
||||
|
||||
/// Linkage type.
|
||||
linkage: Linkage,
|
||||
|
||||
/// Symbol address.
|
||||
address: u64,
|
||||
|
||||
/// Section ID where the symbol resides.
|
||||
section: u8,
|
||||
|
||||
/// Whether the symbol is a weak ref.
|
||||
weak_ref: bool,
|
||||
|
||||
/// Object file where to locate this symbol.
|
||||
file: *Object,
|
||||
|
||||
/// Debug stab if defined.
|
||||
stab: ?struct {
|
||||
/// Stab kind
|
||||
kind: enum {
|
||||
function,
|
||||
global,
|
||||
static,
|
||||
},
|
||||
|
||||
/// Size of the stab.
|
||||
size: u64,
|
||||
} = null,
|
||||
|
||||
/// True if symbol was already committed into the final
|
||||
/// symbol table.
|
||||
visited: bool = false,
|
||||
|
||||
pub const base_type: Symbol.Type = .regular;
|
||||
|
||||
pub const Linkage = enum {
|
||||
translation_unit,
|
||||
linkage_unit,
|
||||
global,
|
||||
};
|
||||
|
||||
pub fn isTemp(regular: *Regular) bool {
|
||||
if (regular.linkage == .translation_unit) {
|
||||
return mem.startsWith(u8, regular.base.name, "l") or mem.startsWith(u8, regular.base.name, "L");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Proxy = struct {
|
||||
base: Symbol,
|
||||
|
||||
/// Dynamic binding info - spots within the final
|
||||
/// executable where this proxy is referenced from.
|
||||
bind_info: std.ArrayListUnmanaged(struct {
|
||||
segment_id: u16,
|
||||
address: u64,
|
||||
}) = .{},
|
||||
|
||||
/// Dylib where to locate this symbol.
|
||||
/// null means self-reference.
|
||||
file: ?*Dylib = null,
|
||||
|
||||
pub const base_type: Symbol.Type = .proxy;
|
||||
|
||||
pub fn deinit(proxy: *Proxy, allocator: *Allocator) void {
|
||||
proxy.bind_info.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn dylibOrdinal(proxy: *Proxy) u16 {
|
||||
const dylib = proxy.file orelse return 0;
|
||||
return dylib.ordinal.?;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Unresolved = struct {
|
||||
base: Symbol,
|
||||
|
||||
/// File where this symbol was referenced.
|
||||
/// null means synthetic, e.g., dyld_stub_binder.
|
||||
file: ?*Object = null,
|
||||
|
||||
pub const base_type: Symbol.Type = .unresolved;
|
||||
};
|
||||
|
||||
pub const Tentative = struct {
|
||||
base: Symbol,
|
||||
|
||||
/// Symbol size.
|
||||
size: u64,
|
||||
|
||||
/// Symbol alignment as power of two.
|
||||
alignment: u16,
|
||||
|
||||
/// File where this symbol was referenced.
|
||||
file: *Object,
|
||||
|
||||
pub const base_type: Symbol.Type = .tentative;
|
||||
};
|
||||
|
||||
pub fn deinit(base: *Symbol, allocator: *Allocator) void {
|
||||
allocator.free(base.name);
|
||||
switch (base.@"type") {
|
||||
.proxy => @fieldParentPtr(Proxy, "base", base).deinit(allocator),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cast(base: *Symbol, comptime T: type) ?*T {
|
||||
if (base.@"type" != T.base_type) {
|
||||
return null;
|
||||
}
|
||||
return @fieldParentPtr(T, "base", base);
|
||||
}
|
||||
|
||||
pub fn getTopmostAlias(base: *Symbol) *Symbol {
|
||||
if (base.alias) |alias| {
|
||||
return alias.getTopmostAlias();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
pub fn isStab(sym: macho.nlist_64) bool {
|
||||
return (macho.N_STAB & sym.n_type) != 0;
|
||||
}
|
||||
|
||||
pub fn isPext(sym: macho.nlist_64) bool {
|
||||
return (macho.N_PEXT & sym.n_type) != 0;
|
||||
}
|
||||
|
||||
pub fn isExt(sym: macho.nlist_64) bool {
|
||||
return (macho.N_EXT & sym.n_type) != 0;
|
||||
}
|
||||
|
||||
pub fn isSect(sym: macho.nlist_64) bool {
|
||||
const type_ = macho.N_TYPE & sym.n_type;
|
||||
return type_ == macho.N_SECT;
|
||||
}
|
||||
|
||||
pub fn isUndf(sym: macho.nlist_64) bool {
|
||||
const type_ = macho.N_TYPE & sym.n_type;
|
||||
return type_ == macho.N_UNDF;
|
||||
}
|
||||
|
||||
pub fn isIndr(sym: macho.nlist_64) bool {
|
||||
const type_ = macho.N_TYPE & sym.n_type;
|
||||
return type_ == macho.N_INDR;
|
||||
}
|
||||
|
||||
pub fn isAbs(sym: macho.nlist_64) bool {
|
||||
const type_ = macho.N_TYPE & sym.n_type;
|
||||
return type_ == macho.N_ABS;
|
||||
}
|
||||
|
||||
pub fn isWeakDef(sym: macho.nlist_64) bool {
|
||||
return (sym.n_desc & macho.N_WEAK_DEF) != 0;
|
||||
}
|
||||
|
||||
pub fn isWeakRef(sym: macho.nlist_64) bool {
|
||||
return (sym.n_desc & macho.N_WEAK_REF) != 0;
|
||||
}
|
||||
1224
src/link/MachO/TextBlock.zig
Normal file
1224
src/link/MachO/TextBlock.zig
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,28 @@ const Allocator = std.mem.Allocator;
|
||||
const MachO = @import("../MachO.zig");
|
||||
const padToIdeal = MachO.padToIdeal;
|
||||
|
||||
pub const HeaderArgs = struct {
|
||||
magic: u32 = macho.MH_MAGIC_64,
|
||||
cputype: macho.cpu_type_t = 0,
|
||||
cpusubtype: macho.cpu_subtype_t = 0,
|
||||
filetype: u32 = 0,
|
||||
flags: u32 = 0,
|
||||
reserved: u32 = 0,
|
||||
};
|
||||
|
||||
pub fn emptyHeader(args: HeaderArgs) macho.mach_header_64 {
|
||||
return .{
|
||||
.magic = args.magic,
|
||||
.cputype = args.cputype,
|
||||
.cpusubtype = args.cpusubtype,
|
||||
.filetype = args.filetype,
|
||||
.ncmds = 0,
|
||||
.sizeofcmds = 0,
|
||||
.flags = args.flags,
|
||||
.reserved = args.reserved,
|
||||
};
|
||||
}
|
||||
|
||||
pub const LoadCommand = union(enum) {
|
||||
Segment: SegmentCommand,
|
||||
DyldInfoOnly: macho.dyld_info_command,
|
||||
@ -403,6 +425,44 @@ fn makeStaticString(bytes: []const u8) [16]u8 {
|
||||
return buf;
|
||||
}
|
||||
|
||||
fn parseName(name: *const [16]u8) []const u8 {
|
||||
const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
|
||||
return name[0..len];
|
||||
}
|
||||
|
||||
pub fn segmentName(sect: macho.section_64) []const u8 {
|
||||
return parseName(§.segname);
|
||||
}
|
||||
|
||||
pub fn sectionName(sect: macho.section_64) []const u8 {
|
||||
return parseName(§.sectname);
|
||||
}
|
||||
|
||||
pub fn sectionType(sect: macho.section_64) u8 {
|
||||
return @truncate(u8, sect.flags & 0xff);
|
||||
}
|
||||
|
||||
pub fn sectionAttrs(sect: macho.section_64) u32 {
|
||||
return sect.flags & 0xffffff00;
|
||||
}
|
||||
|
||||
pub fn sectionIsCode(sect: macho.section_64) bool {
|
||||
const attr = sectionAttrs(sect);
|
||||
return attr & macho.S_ATTR_PURE_INSTRUCTIONS != 0 or attr & macho.S_ATTR_SOME_INSTRUCTIONS != 0;
|
||||
}
|
||||
|
||||
pub fn sectionIsDebug(sect: macho.section_64) bool {
|
||||
return sectionAttrs(sect) & macho.S_ATTR_DEBUG != 0;
|
||||
}
|
||||
|
||||
pub fn sectionIsDontDeadStrip(sect: macho.section_64) bool {
|
||||
return sectionAttrs(sect) & macho.S_ATTR_NO_DEAD_STRIP != 0;
|
||||
}
|
||||
|
||||
pub fn sectionIsDontDeadStripIfReferencesLive(sect: macho.section_64) bool {
|
||||
return sectionAttrs(sect) & macho.S_ATTR_LIVE_SUPPORT != 0;
|
||||
}
|
||||
|
||||
fn testRead(allocator: *Allocator, buffer: []const u8, expected: anytype) !void {
|
||||
var stream = io.fixedBufferStream(buffer);
|
||||
var given = try LoadCommand.read(allocator, stream.reader());
|
||||
|
||||
@ -1,206 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.reloc);
|
||||
const macho = std.macho;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
|
||||
const aarch64 = @import("reloc/aarch64.zig");
|
||||
const x86_64 = @import("reloc/x86_64.zig");
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Symbol = @import("Symbol.zig");
|
||||
|
||||
pub const Relocation = struct {
|
||||
@"type": Type,
|
||||
code: []u8,
|
||||
offset: u32,
|
||||
target: Target,
|
||||
|
||||
pub fn cast(base: *Relocation, comptime T: type) ?*T {
|
||||
if (base.@"type" != T.base_type)
|
||||
return null;
|
||||
|
||||
return @fieldParentPtr(T, "base", base);
|
||||
}
|
||||
|
||||
pub const ResolveArgs = struct {
|
||||
source_addr: u64,
|
||||
target_addr: u64,
|
||||
subtractor: ?u64 = null,
|
||||
source_source_sect_addr: ?u64 = null,
|
||||
source_target_sect_addr: ?u64 = null,
|
||||
};
|
||||
|
||||
pub fn resolve(base: *Relocation, args: ResolveArgs) !void {
|
||||
log.debug("{s}", .{base.@"type"});
|
||||
log.debug(" | offset 0x{x}", .{base.offset});
|
||||
log.debug(" | source address 0x{x}", .{args.source_addr});
|
||||
log.debug(" | target address 0x{x}", .{args.target_addr});
|
||||
if (args.subtractor) |sub|
|
||||
log.debug(" | subtractor address 0x{x}", .{sub});
|
||||
if (args.source_source_sect_addr) |addr|
|
||||
log.debug(" | source source section address 0x{x}", .{addr});
|
||||
if (args.source_target_sect_addr) |addr|
|
||||
log.debug(" | source target section address 0x{x}", .{addr});
|
||||
|
||||
return switch (base.@"type") {
|
||||
.unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(args),
|
||||
.branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(args),
|
||||
.page => @fieldParentPtr(aarch64.Page, "base", base).resolve(args),
|
||||
.page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(args),
|
||||
.got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(args),
|
||||
.got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(args),
|
||||
.pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).resolve(args),
|
||||
.tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(args),
|
||||
.tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(args),
|
||||
.branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(args),
|
||||
.signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(args),
|
||||
.got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(args),
|
||||
.got => @fieldParentPtr(x86_64.Got, "base", base).resolve(args),
|
||||
.tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(args),
|
||||
};
|
||||
}
|
||||
|
||||
pub const Type = enum {
|
||||
branch_aarch64,
|
||||
unsigned,
|
||||
page,
|
||||
page_off,
|
||||
got_page,
|
||||
got_page_off,
|
||||
tlvp_page,
|
||||
pointer_to_got,
|
||||
tlvp_page_off,
|
||||
branch_x86_64,
|
||||
signed,
|
||||
got_load,
|
||||
got,
|
||||
tlv,
|
||||
};
|
||||
|
||||
pub const Target = union(enum) {
|
||||
symbol: *Symbol,
|
||||
section: u16,
|
||||
|
||||
pub fn from_reloc(reloc: macho.relocation_info, symbols: []*Symbol) Target {
|
||||
return if (reloc.r_extern == 1) .{
|
||||
.symbol = symbols[reloc.r_symbolnum],
|
||||
} else .{
|
||||
.section = @intCast(u16, reloc.r_symbolnum - 1),
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const Unsigned = struct {
|
||||
base: Relocation,
|
||||
subtractor: ?Relocation.Target = null,
|
||||
/// Addend embedded directly in the relocation slot
|
||||
addend: i64,
|
||||
/// Extracted from r_length:
|
||||
/// => 3 implies true
|
||||
/// => 2 implies false
|
||||
/// => * is unreachable
|
||||
is_64bit: bool,
|
||||
|
||||
pub const base_type: Relocation.Type = .unsigned;
|
||||
|
||||
pub fn resolve(unsigned: Unsigned, args: Relocation.ResolveArgs) !void {
|
||||
const addend = if (unsigned.base.target == .section)
|
||||
unsigned.addend - @intCast(i64, args.source_target_sect_addr.?)
|
||||
else
|
||||
unsigned.addend;
|
||||
|
||||
const result = if (args.subtractor) |subtractor|
|
||||
@intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
|
||||
else
|
||||
@intCast(i64, args.target_addr) + addend;
|
||||
|
||||
log.debug(" | calculated addend 0x{x}", .{addend});
|
||||
log.debug(" | calculated unsigned value 0x{x}", .{result});
|
||||
|
||||
if (unsigned.is_64bit) {
|
||||
mem.writeIntLittle(
|
||||
u64,
|
||||
unsigned.base.code[0..8],
|
||||
@bitCast(u64, result),
|
||||
);
|
||||
} else {
|
||||
mem.writeIntLittle(
|
||||
u32,
|
||||
unsigned.base.code[0..4],
|
||||
@truncate(u32, @bitCast(u64, result)),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parse(
|
||||
allocator: *Allocator,
|
||||
arch: std.Target.Cpu.Arch,
|
||||
code: []u8,
|
||||
relocs: []const macho.relocation_info,
|
||||
symbols: []*Symbol,
|
||||
) ![]*Relocation {
|
||||
var it = RelocIterator{
|
||||
.buffer = relocs,
|
||||
};
|
||||
|
||||
switch (arch) {
|
||||
.aarch64 => {
|
||||
var parser = aarch64.Parser{
|
||||
.allocator = allocator,
|
||||
.it = &it,
|
||||
.code = code,
|
||||
.parsed = std.ArrayList(*Relocation).init(allocator),
|
||||
.symbols = symbols,
|
||||
};
|
||||
defer parser.deinit();
|
||||
try parser.parse();
|
||||
|
||||
return parser.parsed.toOwnedSlice();
|
||||
},
|
||||
.x86_64 => {
|
||||
var parser = x86_64.Parser{
|
||||
.allocator = allocator,
|
||||
.it = &it,
|
||||
.code = code,
|
||||
.parsed = std.ArrayList(*Relocation).init(allocator),
|
||||
.symbols = symbols,
|
||||
};
|
||||
defer parser.deinit();
|
||||
try parser.parse();
|
||||
|
||||
return parser.parsed.toOwnedSlice();
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub const RelocIterator = struct {
|
||||
buffer: []const macho.relocation_info,
|
||||
index: i32 = -1,
|
||||
|
||||
pub fn next(self: *RelocIterator) ?macho.relocation_info {
|
||||
self.index += 1;
|
||||
if (self.index < self.buffer.len) {
|
||||
const reloc = self.buffer[@intCast(u32, self.index)];
|
||||
log.debug("relocation", .{});
|
||||
log.debug(" | type = {}", .{reloc.r_type});
|
||||
log.debug(" | offset = {}", .{reloc.r_address});
|
||||
log.debug(" | PC = {}", .{reloc.r_pcrel == 1});
|
||||
log.debug(" | length = {}", .{reloc.r_length});
|
||||
log.debug(" | symbolnum = {}", .{reloc.r_symbolnum});
|
||||
log.debug(" | extern = {}", .{reloc.r_extern == 1});
|
||||
return reloc;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn peek(self: RelocIterator) macho.relocation_info {
|
||||
assert(self.index + 1 < self.buffer.len);
|
||||
return self.buffer[@intCast(u32, self.index + 1)];
|
||||
}
|
||||
};
|
||||
@ -1,628 +0,0 @@
|
||||
const std = @import("std");
|
||||
const aarch64 = @import("../../../codegen/aarch64.zig");
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.reloc);
|
||||
const macho = std.macho;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
const reloc = @import("../reloc.zig");
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Relocation = reloc.Relocation;
|
||||
const Symbol = @import("../Symbol.zig");
|
||||
|
||||
pub const Branch = struct {
|
||||
base: Relocation,
|
||||
/// Always .UnconditionalBranchImmediate
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .branch_aarch64;
|
||||
|
||||
pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
|
||||
const displacement = try math.cast(i28, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
|
||||
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
|
||||
var inst = branch.inst;
|
||||
inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
|
||||
mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Page = struct {
|
||||
base: Relocation,
|
||||
addend: ?u32 = null,
|
||||
/// Always .PCRelativeAddress
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .page;
|
||||
|
||||
pub fn resolve(page: Page, args: Relocation.ResolveArgs) !void {
|
||||
const target_addr = if (page.addend) |addend| args.target_addr + addend else args.target_addr;
|
||||
const source_page = @intCast(i32, args.source_addr >> 12);
|
||||
const target_page = @intCast(i32, target_addr >> 12);
|
||||
const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
|
||||
|
||||
log.debug(" | calculated addend 0x{x}", .{page.addend});
|
||||
log.debug(" | moving by {} pages", .{pages});
|
||||
|
||||
var inst = page.inst;
|
||||
inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
|
||||
inst.pc_relative_address.immlo = @truncate(u2, pages);
|
||||
|
||||
mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const PageOff = struct {
|
||||
base: Relocation,
|
||||
addend: ?u32 = null,
|
||||
op_kind: OpKind,
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .page_off;
|
||||
|
||||
pub const OpKind = enum {
|
||||
arithmetic,
|
||||
load_store,
|
||||
};
|
||||
|
||||
pub fn resolve(page_off: PageOff, args: Relocation.ResolveArgs) !void {
|
||||
const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
|
||||
const narrowed = @truncate(u12, target_addr);
|
||||
|
||||
log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
|
||||
log.debug(" | {s} opcode", .{page_off.op_kind});
|
||||
|
||||
var inst = page_off.inst;
|
||||
if (page_off.op_kind == .arithmetic) {
|
||||
inst.add_subtract_immediate.imm12 = narrowed;
|
||||
} else {
|
||||
const offset: u12 = blk: {
|
||||
if (inst.load_store_register.size == 0) {
|
||||
if (inst.load_store_register.v == 1) {
|
||||
// 128-bit SIMD is scaled by 16.
|
||||
break :blk try math.divExact(u12, narrowed, 16);
|
||||
}
|
||||
// Otherwise, 8-bit SIMD or ldrb.
|
||||
break :blk narrowed;
|
||||
} else {
|
||||
const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
|
||||
break :blk try math.divExact(u12, narrowed, denom);
|
||||
}
|
||||
};
|
||||
inst.load_store_register.offset = offset;
|
||||
}
|
||||
|
||||
mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const GotPage = struct {
|
||||
base: Relocation,
|
||||
/// Always .PCRelativeAddress
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .got_page;
|
||||
|
||||
pub fn resolve(page: GotPage, args: Relocation.ResolveArgs) !void {
|
||||
const source_page = @intCast(i32, args.source_addr >> 12);
|
||||
const target_page = @intCast(i32, args.target_addr >> 12);
|
||||
const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
|
||||
|
||||
log.debug(" | moving by {} pages", .{pages});
|
||||
|
||||
var inst = page.inst;
|
||||
inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
|
||||
inst.pc_relative_address.immlo = @truncate(u2, pages);
|
||||
|
||||
mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const GotPageOff = struct {
|
||||
base: Relocation,
|
||||
/// Always .LoadStoreRegister with size = 3 for GOT indirection
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .got_page_off;
|
||||
|
||||
pub fn resolve(page_off: GotPageOff, args: Relocation.ResolveArgs) !void {
|
||||
const narrowed = @truncate(u12, args.target_addr);
|
||||
|
||||
log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
|
||||
|
||||
var inst = page_off.inst;
|
||||
const offset = try math.divExact(u12, narrowed, 8);
|
||||
inst.load_store_register.offset = offset;
|
||||
|
||||
mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const PointerToGot = struct {
|
||||
base: Relocation,
|
||||
|
||||
pub const base_type: Relocation.Type = .pointer_to_got;
|
||||
|
||||
pub fn resolve(ptr_to_got: PointerToGot, args: Relocation.ResolveArgs) !void {
|
||||
const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
|
||||
|
||||
log.debug(" | calculated value 0x{x}", .{result});
|
||||
|
||||
mem.writeIntLittle(u32, ptr_to_got.base.code[0..4], @bitCast(u32, result));
|
||||
}
|
||||
};
|
||||
|
||||
pub const TlvpPage = struct {
|
||||
base: Relocation,
|
||||
/// Always .PCRelativeAddress
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .tlvp_page;
|
||||
|
||||
pub fn resolve(page: TlvpPage, args: Relocation.ResolveArgs) !void {
|
||||
const source_page = @intCast(i32, args.source_addr >> 12);
|
||||
const target_page = @intCast(i32, args.target_addr >> 12);
|
||||
const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
|
||||
|
||||
log.debug(" | moving by {} pages", .{pages});
|
||||
|
||||
var inst = page.inst;
|
||||
inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
|
||||
inst.pc_relative_address.immlo = @truncate(u2, pages);
|
||||
|
||||
mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const TlvpPageOff = struct {
|
||||
base: Relocation,
|
||||
/// Always .AddSubtractImmediate regardless of the source instruction.
|
||||
/// This means, we always rewrite the instruction to add even if the
|
||||
/// source instruction was an ldr.
|
||||
inst: aarch64.Instruction,
|
||||
|
||||
pub const base_type: Relocation.Type = .tlvp_page_off;
|
||||
|
||||
pub fn resolve(page_off: TlvpPageOff, args: Relocation.ResolveArgs) !void {
|
||||
const narrowed = @truncate(u12, args.target_addr);
|
||||
|
||||
log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
|
||||
|
||||
var inst = page_off.inst;
|
||||
inst.add_subtract_immediate.imm12 = narrowed;
|
||||
|
||||
mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
allocator: *Allocator,
|
||||
it: *reloc.RelocIterator,
|
||||
code: []u8,
|
||||
parsed: std.ArrayList(*Relocation),
|
||||
symbols: []*Symbol,
|
||||
addend: ?u32 = null,
|
||||
subtractor: ?Relocation.Target = null,
|
||||
|
||||
pub fn deinit(parser: *Parser) void {
|
||||
parser.parsed.deinit();
|
||||
}
|
||||
|
||||
pub fn parse(parser: *Parser) !void {
|
||||
while (parser.it.next()) |rel| {
|
||||
switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
|
||||
.ARM64_RELOC_BRANCH26 => {
|
||||
try parser.parseBranch(rel);
|
||||
},
|
||||
.ARM64_RELOC_SUBTRACTOR => {
|
||||
try parser.parseSubtractor(rel);
|
||||
},
|
||||
.ARM64_RELOC_UNSIGNED => {
|
||||
try parser.parseUnsigned(rel);
|
||||
},
|
||||
.ARM64_RELOC_ADDEND => {
|
||||
try parser.parseAddend(rel);
|
||||
},
|
||||
.ARM64_RELOC_PAGE21,
|
||||
.ARM64_RELOC_GOT_LOAD_PAGE21,
|
||||
.ARM64_RELOC_TLVP_LOAD_PAGE21,
|
||||
=> {
|
||||
try parser.parsePage(rel);
|
||||
},
|
||||
.ARM64_RELOC_PAGEOFF12 => {
|
||||
try parser.parsePageOff(rel);
|
||||
},
|
||||
.ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
|
||||
try parser.parseGotLoadPageOff(rel);
|
||||
},
|
||||
.ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
|
||||
try parser.parseTlvpLoadPageOff(rel);
|
||||
},
|
||||
.ARM64_RELOC_POINTER_TO_GOT => {
|
||||
try parser.parsePointerToGot(rel);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parseAddend(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_ADDEND);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(rel.r_extern == 0);
|
||||
assert(parser.addend == null);
|
||||
|
||||
parser.addend = rel.r_symbolnum;
|
||||
|
||||
// Verify ADDEND is followed by a load.
|
||||
const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
|
||||
switch (next) {
|
||||
.ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
|
||||
else => {
|
||||
log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next});
|
||||
return error.UnexpectedRelocationType;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_BRANCH26);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const parsed_inst = aarch64.Instruction{ .unconditional_branch_immediate = mem.bytesToValue(
|
||||
meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.unconditional_branch_immediate,
|
||||
),
|
||||
inst,
|
||||
) };
|
||||
|
||||
var branch = try parser.allocator.create(Branch);
|
||||
errdefer parser.allocator.destroy(branch);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
branch.* = .{
|
||||
.base = .{
|
||||
.@"type" = .branch_aarch64,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.inst = parsed_inst,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{branch});
|
||||
try parser.parsed.append(&branch.base);
|
||||
}
|
||||
|
||||
fn parsePage(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const parsed_inst = aarch64.Instruction{ .pc_relative_address = mem.bytesToValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.pc_relative_address,
|
||||
), inst) };
|
||||
|
||||
const ptr: *Relocation = ptr: {
|
||||
switch (rel_type) {
|
||||
.ARM64_RELOC_PAGE21 => {
|
||||
defer {
|
||||
// Reset parser's addend state
|
||||
parser.addend = null;
|
||||
}
|
||||
var page = try parser.allocator.create(Page);
|
||||
errdefer parser.allocator.destroy(page);
|
||||
|
||||
page.* = .{
|
||||
.base = .{
|
||||
.@"type" = .page,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.addend = parser.addend,
|
||||
.inst = parsed_inst,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page});
|
||||
|
||||
break :ptr &page.base;
|
||||
},
|
||||
.ARM64_RELOC_GOT_LOAD_PAGE21 => {
|
||||
var page = try parser.allocator.create(GotPage);
|
||||
errdefer parser.allocator.destroy(page);
|
||||
|
||||
page.* = .{
|
||||
.base = .{
|
||||
.@"type" = .got_page,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.inst = parsed_inst,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page});
|
||||
|
||||
break :ptr &page.base;
|
||||
},
|
||||
.ARM64_RELOC_TLVP_LOAD_PAGE21 => {
|
||||
var page = try parser.allocator.create(TlvpPage);
|
||||
errdefer parser.allocator.destroy(page);
|
||||
|
||||
page.* = .{
|
||||
.base = .{
|
||||
.@"type" = .tlvp_page,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.inst = parsed_inst,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page});
|
||||
|
||||
break :ptr &page.base;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
};
|
||||
|
||||
try parser.parsed.append(ptr);
|
||||
}
|
||||
|
||||
fn parsePageOff(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
defer {
|
||||
// Reset parser's addend state
|
||||
parser.addend = null;
|
||||
}
|
||||
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_PAGEOFF12);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
|
||||
var op_kind: PageOff.OpKind = undefined;
|
||||
var parsed_inst: aarch64.Instruction = undefined;
|
||||
if (isArithmeticOp(inst)) {
|
||||
op_kind = .arithmetic;
|
||||
parsed_inst = .{ .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.add_subtract_immediate,
|
||||
), inst) };
|
||||
} else {
|
||||
op_kind = .load_store;
|
||||
parsed_inst = .{ .load_store_register = mem.bytesToValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.load_store_register,
|
||||
), inst) };
|
||||
}
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
var page_off = try parser.allocator.create(PageOff);
|
||||
errdefer parser.allocator.destroy(page_off);
|
||||
|
||||
page_off.* = .{
|
||||
.base = .{
|
||||
.@"type" = .page_off,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.op_kind = op_kind,
|
||||
.inst = parsed_inst,
|
||||
.addend = parser.addend,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page_off});
|
||||
try parser.parsed.append(&page_off.base);
|
||||
}
|
||||
|
||||
fn parseGotLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
assert(!isArithmeticOp(inst));
|
||||
|
||||
const parsed_inst = mem.bytesToValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.load_store_register,
|
||||
), inst);
|
||||
assert(parsed_inst.size == 3);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
var page_off = try parser.allocator.create(GotPageOff);
|
||||
errdefer parser.allocator.destroy(page_off);
|
||||
|
||||
page_off.* = .{
|
||||
.base = .{
|
||||
.@"type" = .got_page_off,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.inst = .{
|
||||
.load_store_register = parsed_inst,
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page_off});
|
||||
try parser.parsed.append(&page_off.base);
|
||||
}
|
||||
|
||||
fn parseTlvpLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const RegInfo = struct {
|
||||
rd: u5,
|
||||
rn: u5,
|
||||
size: u1,
|
||||
};
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const parsed: RegInfo = parsed: {
|
||||
if (isArithmeticOp(inst)) {
|
||||
const parsed_inst = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.add_subtract_immediate,
|
||||
), inst);
|
||||
break :parsed .{
|
||||
.rd = parsed_inst.rd,
|
||||
.rn = parsed_inst.rn,
|
||||
.size = parsed_inst.sf,
|
||||
};
|
||||
} else {
|
||||
const parsed_inst = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.load_store_register,
|
||||
), inst);
|
||||
break :parsed .{
|
||||
.rd = parsed_inst.rt,
|
||||
.rn = parsed_inst.rn,
|
||||
.size = @truncate(u1, parsed_inst.size),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
var page_off = try parser.allocator.create(TlvpPageOff);
|
||||
errdefer parser.allocator.destroy(page_off);
|
||||
|
||||
page_off.* = .{
|
||||
.base = .{
|
||||
.@"type" = .tlvp_page_off,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.inst = .{
|
||||
.add_subtract_immediate = .{
|
||||
.rd = parsed.rd,
|
||||
.rn = parsed.rn,
|
||||
.imm12 = 0, // This will be filled when target addresses are known.
|
||||
.sh = 0,
|
||||
.s = 0,
|
||||
.op = 0,
|
||||
.sf = parsed.size,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{page_off});
|
||||
try parser.parsed.append(&page_off.base);
|
||||
}
|
||||
|
||||
fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_SUBTRACTOR);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(parser.subtractor == null);
|
||||
|
||||
parser.subtractor = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
// Verify SUBTRACTOR is followed by UNSIGNED.
|
||||
const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
|
||||
if (next != .ARM64_RELOC_UNSIGNED) {
|
||||
log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
|
||||
return error.UnexpectedRelocationType;
|
||||
}
|
||||
}
|
||||
|
||||
fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
defer {
|
||||
// Reset parser's subtractor state
|
||||
parser.subtractor = null;
|
||||
}
|
||||
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_UNSIGNED);
|
||||
assert(rel.r_pcrel == 0);
|
||||
|
||||
var unsigned = try parser.allocator.create(reloc.Unsigned);
|
||||
errdefer parser.allocator.destroy(unsigned);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
const is_64bit: bool = switch (rel.r_length) {
|
||||
3 => true,
|
||||
2 => false,
|
||||
else => unreachable,
|
||||
};
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const addend: i64 = if (is_64bit)
|
||||
mem.readIntLittle(i64, parser.code[offset..][0..8])
|
||||
else
|
||||
mem.readIntLittle(i32, parser.code[offset..][0..4]);
|
||||
|
||||
unsigned.* = .{
|
||||
.base = .{
|
||||
.@"type" = .unsigned,
|
||||
.code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.subtractor = parser.subtractor,
|
||||
.is_64bit = is_64bit,
|
||||
.addend = addend,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{unsigned});
|
||||
try parser.parsed.append(&unsigned.base);
|
||||
}
|
||||
|
||||
fn parsePointerToGot(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
|
||||
assert(rel_type == .ARM64_RELOC_POINTER_TO_GOT);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
var ptr_to_got = try parser.allocator.create(PointerToGot);
|
||||
errdefer parser.allocator.destroy(ptr_to_got);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
|
||||
ptr_to_got.* = .{
|
||||
.base = .{
|
||||
.@"type" = .pointer_to_got,
|
||||
.code = parser.code[offset..][0..4],
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{ptr_to_got});
|
||||
try parser.parsed.append(&ptr_to_got.base);
|
||||
}
|
||||
};
|
||||
|
||||
inline fn isArithmeticOp(inst: *const [4]u8) bool {
|
||||
const group_decode = @truncate(u5, inst[3]);
|
||||
return ((group_decode >> 2) == 4);
|
||||
}
|
||||
@ -1,345 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.reloc);
|
||||
const macho = std.macho;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
const reloc = @import("../reloc.zig");
|
||||
|
||||
const Allocator = mem.Allocator;
|
||||
const Relocation = reloc.Relocation;
|
||||
const Symbol = @import("../Symbol.zig");
|
||||
|
||||
pub const Branch = struct {
|
||||
base: Relocation,
|
||||
|
||||
pub const base_type: Relocation.Type = .branch_x86_64;
|
||||
|
||||
pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
|
||||
const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
mem.writeIntLittle(u32, branch.base.code[0..4], @bitCast(u32, displacement));
|
||||
}
|
||||
};
|
||||
|
||||
pub const Signed = struct {
|
||||
base: Relocation,
|
||||
addend: i32,
|
||||
correction: i4,
|
||||
|
||||
pub const base_type: Relocation.Type = .signed;
|
||||
|
||||
pub fn resolve(signed: Signed, args: Relocation.ResolveArgs) !void {
|
||||
const target_addr = target_addr: {
|
||||
if (signed.base.target == .section) {
|
||||
const source_target = @intCast(i64, args.source_source_sect_addr.?) + @intCast(i64, signed.base.offset) + signed.addend + 4;
|
||||
const source_disp = source_target - @intCast(i64, args.source_target_sect_addr.?);
|
||||
break :target_addr @intCast(i64, args.target_addr) + source_disp;
|
||||
}
|
||||
break :target_addr @intCast(i64, args.target_addr) + signed.addend;
|
||||
};
|
||||
const displacement = try math.cast(
|
||||
i32,
|
||||
target_addr - @intCast(i64, args.source_addr) - signed.correction - 4,
|
||||
);
|
||||
|
||||
log.debug(" | addend 0x{x}", .{signed.addend});
|
||||
log.debug(" | correction 0x{x}", .{signed.correction});
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
|
||||
mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
|
||||
}
|
||||
};
|
||||
|
||||
pub const GotLoad = struct {
|
||||
base: Relocation,
|
||||
|
||||
pub const base_type: Relocation.Type = .got_load;
|
||||
|
||||
pub fn resolve(got_load: GotLoad, args: Relocation.ResolveArgs) !void {
|
||||
const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
mem.writeIntLittle(u32, got_load.base.code[0..4], @bitCast(u32, displacement));
|
||||
}
|
||||
};
|
||||
|
||||
pub const Got = struct {
|
||||
base: Relocation,
|
||||
addend: i32,
|
||||
|
||||
pub const base_type: Relocation.Type = .got;
|
||||
|
||||
pub fn resolve(got: Got, args: Relocation.ResolveArgs) !void {
|
||||
const displacement = try math.cast(
|
||||
i32,
|
||||
@intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + got.addend,
|
||||
);
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
mem.writeIntLittle(u32, got.base.code[0..4], @bitCast(u32, displacement));
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tlv = struct {
|
||||
base: Relocation,
|
||||
op: *u8,
|
||||
|
||||
pub const base_type: Relocation.Type = .tlv;
|
||||
|
||||
pub fn resolve(tlv: Tlv, args: Relocation.ResolveArgs) !void {
|
||||
// We need to rewrite the opcode from movq to leaq.
|
||||
tlv.op.* = 0x8d;
|
||||
log.debug(" | rewriting op to leaq", .{});
|
||||
|
||||
const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
|
||||
log.debug(" | displacement 0x{x}", .{displacement});
|
||||
|
||||
mem.writeIntLittle(u32, tlv.base.code[0..4], @bitCast(u32, displacement));
|
||||
}
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
allocator: *Allocator,
|
||||
it: *reloc.RelocIterator,
|
||||
code: []u8,
|
||||
parsed: std.ArrayList(*Relocation),
|
||||
symbols: []*Symbol,
|
||||
subtractor: ?Relocation.Target = null,
|
||||
|
||||
pub fn deinit(parser: *Parser) void {
|
||||
parser.parsed.deinit();
|
||||
}
|
||||
|
||||
pub fn parse(parser: *Parser) !void {
|
||||
while (parser.it.next()) |rel| {
|
||||
switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
|
||||
.X86_64_RELOC_BRANCH => {
|
||||
try parser.parseBranch(rel);
|
||||
},
|
||||
.X86_64_RELOC_SUBTRACTOR => {
|
||||
try parser.parseSubtractor(rel);
|
||||
},
|
||||
.X86_64_RELOC_UNSIGNED => {
|
||||
try parser.parseUnsigned(rel);
|
||||
},
|
||||
.X86_64_RELOC_SIGNED,
|
||||
.X86_64_RELOC_SIGNED_1,
|
||||
.X86_64_RELOC_SIGNED_2,
|
||||
.X86_64_RELOC_SIGNED_4,
|
||||
=> {
|
||||
try parser.parseSigned(rel);
|
||||
},
|
||||
.X86_64_RELOC_GOT_LOAD => {
|
||||
try parser.parseGotLoad(rel);
|
||||
},
|
||||
.X86_64_RELOC_GOT => {
|
||||
try parser.parseGot(rel);
|
||||
},
|
||||
.X86_64_RELOC_TLV => {
|
||||
try parser.parseTlv(rel);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_BRANCH);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
|
||||
var branch = try parser.allocator.create(Branch);
|
||||
errdefer parser.allocator.destroy(branch);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
branch.* = .{
|
||||
.base = .{
|
||||
.@"type" = .branch_x86_64,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{branch});
|
||||
try parser.parsed.append(&branch.base);
|
||||
}
|
||||
|
||||
fn parseSigned(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const correction: i4 = switch (rel_type) {
|
||||
.X86_64_RELOC_SIGNED => 0,
|
||||
.X86_64_RELOC_SIGNED_1 => 1,
|
||||
.X86_64_RELOC_SIGNED_2 => 2,
|
||||
.X86_64_RELOC_SIGNED_4 => 4,
|
||||
else => unreachable,
|
||||
};
|
||||
const addend = mem.readIntLittle(i32, inst) + correction;
|
||||
|
||||
var signed = try parser.allocator.create(Signed);
|
||||
errdefer parser.allocator.destroy(signed);
|
||||
|
||||
signed.* = .{
|
||||
.base = .{
|
||||
.@"type" = .signed,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.addend = addend,
|
||||
.correction = correction,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{signed});
|
||||
try parser.parsed.append(&signed.base);
|
||||
}
|
||||
|
||||
fn parseGotLoad(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_GOT_LOAD);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
var got_load = try parser.allocator.create(GotLoad);
|
||||
errdefer parser.allocator.destroy(got_load);
|
||||
|
||||
got_load.* = .{
|
||||
.base = .{
|
||||
.@"type" = .got_load,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{got_load});
|
||||
try parser.parsed.append(&got_load.base);
|
||||
}
|
||||
|
||||
fn parseGot(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_GOT);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
const addend = mem.readIntLittle(i32, inst);
|
||||
|
||||
var got = try parser.allocator.create(Got);
|
||||
errdefer parser.allocator.destroy(got);
|
||||
|
||||
got.* = .{
|
||||
.base = .{
|
||||
.@"type" = .got,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.addend = addend,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{got});
|
||||
try parser.parsed.append(&got.base);
|
||||
}
|
||||
|
||||
fn parseTlv(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_TLV);
|
||||
assert(rel.r_pcrel == 1);
|
||||
assert(rel.r_length == 2);
|
||||
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const inst = parser.code[offset..][0..4];
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
var tlv = try parser.allocator.create(Tlv);
|
||||
errdefer parser.allocator.destroy(tlv);
|
||||
|
||||
tlv.* = .{
|
||||
.base = .{
|
||||
.@"type" = .tlv,
|
||||
.code = inst,
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.op = &parser.code[offset - 2],
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{tlv});
|
||||
try parser.parsed.append(&tlv.base);
|
||||
}
|
||||
|
||||
fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_SUBTRACTOR);
|
||||
assert(rel.r_pcrel == 0);
|
||||
assert(parser.subtractor == null);
|
||||
|
||||
parser.subtractor = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
|
||||
// Verify SUBTRACTOR is followed by UNSIGNED.
|
||||
const next = @intToEnum(macho.reloc_type_x86_64, parser.it.peek().r_type);
|
||||
if (next != .X86_64_RELOC_UNSIGNED) {
|
||||
log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
|
||||
return error.UnexpectedRelocationType;
|
||||
}
|
||||
}
|
||||
|
||||
fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
|
||||
defer {
|
||||
// Reset parser's subtractor state
|
||||
parser.subtractor = null;
|
||||
}
|
||||
|
||||
const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
|
||||
assert(rel_type == .X86_64_RELOC_UNSIGNED);
|
||||
assert(rel.r_pcrel == 0);
|
||||
|
||||
var unsigned = try parser.allocator.create(reloc.Unsigned);
|
||||
errdefer parser.allocator.destroy(unsigned);
|
||||
|
||||
const target = Relocation.Target.from_reloc(rel, parser.symbols);
|
||||
const is_64bit: bool = switch (rel.r_length) {
|
||||
3 => true,
|
||||
2 => false,
|
||||
else => unreachable,
|
||||
};
|
||||
const offset = @intCast(u32, rel.r_address);
|
||||
const addend: i64 = if (is_64bit)
|
||||
mem.readIntLittle(i64, parser.code[offset..][0..8])
|
||||
else
|
||||
mem.readIntLittle(i32, parser.code[offset..][0..4]);
|
||||
|
||||
unsigned.* = .{
|
||||
.base = .{
|
||||
.@"type" = .unsigned,
|
||||
.code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
|
||||
.offset = offset,
|
||||
.target = target,
|
||||
},
|
||||
.subtractor = parser.subtractor,
|
||||
.is_64bit = is_64bit,
|
||||
.addend = addend,
|
||||
};
|
||||
|
||||
log.debug(" | emitting {}", .{unsigned});
|
||||
try parser.parsed.append(&unsigned.base);
|
||||
}
|
||||
};
|
||||
12
src/main.zig
12
src/main.zig
@ -1655,6 +1655,18 @@ fn buildOutputType(
|
||||
}
|
||||
}
|
||||
|
||||
if (use_lld) |opt| {
|
||||
if (opt and cross_target.isDarwin()) {
|
||||
fatal("LLD requested with Mach-O object format. Only the self-hosted linker is supported for this target.", .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (want_lto) |opt| {
|
||||
if (opt and cross_target.isDarwin()) {
|
||||
fatal("LTO is not yet supported with the Mach-O object format. More details: https://github.com/ziglang/zig/issues/8680", .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime std.Target.current.isDarwin()) {
|
||||
// If we want to link against frameworks, we need system headers.
|
||||
if (framework_dirs.items.len > 0 or frameworks.items.len > 0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user