diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-07-06 19:09:49 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-07-15 18:49:47 +0200 |
| commit | dbd2eb7c7f9267e8ae508d0995c1d4c5a3b46309 (patch) | |
| tree | 9f1749c1ed5cebc47e4813a6f3fceb3e0101ccb9 /src | |
| parent | 15b85df3dd8a754bc26159ea2202781b748a613e (diff) | |
| download | zig-dbd2eb7c7f9267e8ae508d0995c1d4c5a3b46309.tar.gz zig-dbd2eb7c7f9267e8ae508d0995c1d4c5a3b46309.zip | |
zld: simplify relocation parsing
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO/Object.zig | 37 | ||||
| -rw-r--r-- | src/link/MachO/Zld.zig | 62 | ||||
| -rw-r--r-- | src/link/MachO/reloc.zig | 868 | ||||
| -rw-r--r-- | src/link/MachO/reloc/aarch64.zig | 618 | ||||
| -rw-r--r-- | src/link/MachO/reloc/x86_64.zig | 385 |
5 files changed, 761 insertions, 1209 deletions
diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 06d5a260cd..6e8925b648 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -408,7 +408,6 @@ const TextBlockParser = struct { 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; - log.warn("{} - {}", .{ start_addr, end_addr }); const code = self.code[start_addr..end_addr]; const size = code.len; @@ -430,7 +429,7 @@ const TextBlockParser = struct { .aliases = alias_only_indices, .references = std.AutoArrayHashMap(u32, void).init(self.allocator), .code = try self.allocator.dupe(u8, code), - .relocs = std.ArrayList(*Relocation).init(self.allocator), + .relocs = std.ArrayList(Relocation).init(self.allocator), .size = size, .alignment = self.section.@"align", }; @@ -579,7 +578,7 @@ pub fn parseTextBlocks(self: *Object, zld: *Zld) !void { .local_sym_index = local_sym_index, .references = std.AutoArrayHashMap(u32, void).init(self.allocator), .code = try self.allocator.dupe(u8, code), - .relocs = std.ArrayList(*Relocation).init(self.allocator), + .relocs = std.ArrayList(Relocation).init(self.allocator), .size = sect.size, .alignment = sect.@"align", }; @@ -607,30 +606,14 @@ fn parseRelocs( var it = reloc.RelocIterator{ .buffer = relocs, }; - - switch (self.arch.?) { - .aarch64 => { - var parser = reloc.aarch64.Parser{ - .object = self, - .zld = zld, - .it = &it, - .block = block, - .base_addr = base_addr, - }; - try parser.parse(); - }, - .x86_64 => { - var parser = reloc.x86_64.Parser{ - .object = self, - .zld = zld, - .it = &it, - .block = block, - .base_addr = base_addr, - }; - try parser.parse(); - }, - else => unreachable, - } + var parser = reloc.Parser{ + .object = self, + .zld = zld, + .it = &it, + .block = block, + .base_addr = base_addr, + }; + try parser.parse(); } pub fn symbolFromReloc(self: *Object, rel: macho.relocation_info) !*Symbol { diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 7f7997bc17..69f8821cb7 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -137,7 +137,7 @@ pub const TextBlock = struct { aliases: ?[]u32 = null, references: std.AutoArrayHashMap(u32, void), code: []u8, - relocs: std.ArrayList(*Relocation), + relocs: std.ArrayList(Relocation), size: u64, alignment: u32, next: ?*TextBlock = null, @@ -1604,7 +1604,7 @@ fn resolveSymbols(self: *Zld) !void { .local_sym_index = local_sym_index, .references = std.AutoArrayHashMap(u32, void).init(self.allocator), .code = code, - .relocs = std.ArrayList(*Relocation).init(self.allocator), + .relocs = std.ArrayList(Relocation).init(self.allocator), .size = size, .alignment = alignment, }; @@ -1871,64 +1871,6 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void { } } -fn relocTargetAddr(self: *Zld, object: *const Object, target: reloc.Relocation.Target) !u64 { - const target_addr = blk: { - switch (target) { - .symbol => |sym_id| { - const sym = object.symbols.items[sym_id]; - switch (sym.payload) { - .regular => |reg| { - log.debug(" | regular '{s}'", .{sym.name}); - break :blk reg.address; - }, - .proxy => |proxy| { - if (mem.eql(u8, sym.name, "__tlv_bootstrap")) { - log.debug(" | symbol '__tlv_bootstrap'", .{}); - const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const tlv = segment.sections.items[self.tlv_section_index.?]; - break :blk tlv.addr; - } - - log.debug(" | symbol stub '{s}'", .{sym.name}); - const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const stubs = segment.sections.items[self.stubs_section_index.?]; - const stubs_index = sym.stubs_index orelse { - if (proxy.bind_info.items.len > 0) { - break :blk 0; // Dynamically bound by dyld. - } - log.err( - "expected stubs index or dynamic bind address when relocating symbol '{s}'", - .{sym.name}, - ); - log.err("this is an internal linker error", .{}); - return error.FailedToResolveRelocationTarget; - }; - break :blk stubs.addr + stubs_index * stubs.reserved2; - }, - else => { - log.err("failed to resolve symbol '{s}' as a relocation target", .{sym.name}); - log.err("this is an internal linker error", .{}); - return error.FailedToResolveRelocationTarget; - }, - } - }, - .section => |sect_id| { - log.debug(" | section offset", .{}); - const source_sect = object.sections.items[sect_id]; - log.debug(" | section '{s},{s}'", .{ - segmentName(source_sect.inner), - sectionName(source_sect.inner), - }); - const target_map = source_sect.target_map orelse unreachable; - const target_seg = self.load_commands.items[target_map.segment_id].Segment; - const target_sect = target_seg.sections.items[target_map.section_id]; - break :blk target_sect.addr + target_map.offset; - }, - } - }; - return target_addr; -} - fn populateMetadata(self: *Zld) !void { if (self.pagezero_segment_cmd_index == null) { self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig index e11e850aa6..4693e89787 100644 --- a/src/link/MachO/reloc.zig +++ b/src/link/MachO/reloc.zig @@ -1,4 +1,5 @@ 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; @@ -6,141 +7,431 @@ const math = std.math; const mem = std.mem; const meta = std.meta; -pub const aarch64 = @import("reloc/aarch64.zig"); -pub const x86_64 = @import("reloc/x86_64.zig"); - const Allocator = mem.Allocator; +const Arch = std.Target.Cpu.Arch; +const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); -const TextBlock = @import("Zld.zig").TextBlock; +const TextBlock = Zld.TextBlock; +const Zld = @import("Zld.zig"); pub const Relocation = struct { - @"type": Type, + /// Offset within the `block`s code buffer. + /// Note relocation size can be inferred by relocation's kind. offset: u32, + + /// Parent block containing this relocation. block: *TextBlock, + + /// Target symbol: either a regular or a proxy. target: *Symbol, - pub fn cast(base: *Relocation, comptime T: type) ?*T { - if (base.@"type" != T.base_type) - return null; + payload: union(enum) { + unsigned: Unsigned, + branch: Branch, + page: Page, + page_off: PageOff, + pointer_to_got: PointerToGot, + signed: Signed, + load: Load, + }, - return @fieldParentPtr(T, "base", base); - } + pub const Unsigned = struct { + subtractor: ?*Symbol = 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 fn resolve(self: Unsigned, base: Relocation, source_addr: u64, target_addr: u64) !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 resolve(base: *Relocation) !void { - // return switch (base.@"type") { - // .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(), - // .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(), - // .page => @fieldParentPtr(aarch64.Page, "base", base).resolve(), - // .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(), - // .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(), - // .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(), - // .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).resolve(), - // .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(), - // .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(), - // .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(), - // .signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(), - // .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(), - // .got => @fieldParentPtr(x86_64.Got, "base", base).resolve(), - // .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(), - // }; - // } - - 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 fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "Unsigned {{ ", .{}); + if (self.subtractor) |sub| { + try std.fmt.format(writer, ".subtractor = {}, ", .{sub}); + } + try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); + const length: usize = if (self.is_64bit) 8 else 4; + try std.fmt.format(writer, ".length = {}, ", .{length}); + try std.fmt.format(writer, "}}", .{}); + } }; - pub fn format(base: *const Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - try std.fmt.format(writer, "Relocation {{ ", .{}); - try std.fmt.format(writer, ".type = {s}, ", .{base.@"type"}); - try std.fmt.format(writer, ".offset = {}, ", .{base.offset}); - try std.fmt.format(writer, ".block = {}", .{base.block.local_sym_index}); - try std.fmt.format(writer, ".target = {}, ", .{base.target}); - - try switch (base.@"type") { - .unsigned => @fieldParentPtr(Unsigned, "base", base).format(fmt, options, writer), - .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).format(fmt, options, writer), - .page => @fieldParentPtr(aarch64.Page, "base", base).format(fmt, options, writer), - .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).format(fmt, options, writer), - .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).format(fmt, options, writer), - .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).format(fmt, options, writer), - .pointer_to_got => @fieldParentPtr(aarch64.PointerToGot, "base", base).format(fmt, options, writer), - .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).format(fmt, options, writer), - .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).format(fmt, options, writer), - .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).format(fmt, options, writer), - .signed => @fieldParentPtr(x86_64.Signed, "base", base).format(fmt, options, writer), - .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).format(fmt, options, writer), - .got => @fieldParentPtr(x86_64.Got, "base", base).format(fmt, options, writer), - .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).format(fmt, options, writer), + pub const Branch = struct { + arch: Arch, + + pub fn resolve(self: Branch, base: Relocation, source_addr: u64, target_addr: u64) !void { + switch (arch) { + .aarch64 => { + const displacement = try math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr)); + var inst = aarch64.Instruction{ + .unconditional_branch_immediate = mem.bytesToValue( + meta.TagPayload( + aarch.Instruction, + aarch64.Instruction.unconditional_branch_immediate, + ), + base.block.code[base.offset..][0..4], + ), + }; + inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2)); + mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32()); + }, + .x86_64 => { + const displacement = try math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4); + mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement)); + }, + else => return error.UnsupportedCpuArchitecture, + } + } + + pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "Branch {{}}", .{}); + } + }; + + pub const Page = struct { + kind: enum { + page, + got, + tlvp, + }, + addend: ?u32 = null, + + pub fn resolve(self: Page, base: Relocation, source_addr: u64, target_addr: u64) !void { + const actual_target_addr = if (self.addend) |addend| target_addr + addend else target_addr; + const source_page = @intCast(i32, source_addr >> 12); + const target_page = @intCast(i32, actual_target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); + + var inst = aarch64.Instruction{ + .pc_relative_address = mem.bytesToValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.pc_relative_address, + ), + base.block.code[base.offset..][0..4], + ), + }; + inst.pc_relative_address.immhi = @truncate(u19, pages >> 2); + inst.pc_relative_address.immlo = @truncate(u2, pages); + + mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32()); + } + + pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "Page {{ ", .{}); + switch (self.kind) { + .page => {}, + .got => { + try std.fmt.format(writer, ".got, ", .{}); + }, + .tlvp => { + try std.fmt.format(writer, ".tlvp", .{}); + }, + } + if (self.addend) |add| { + try std.fmt.format(writer, ".addend = {}, ", .{add}); + } + try std.fmt.format(writer, "}}", .{}); + } + }; + + pub const PageOff = struct { + kind: enum { + page, + got, + tlvp, + }, + addend: ?u32 = null, + op_kind: ?OpKind = null, + + pub const OpKind = enum { + arithmetic, + load, }; - try std.fmt.format(writer, "}}", .{}); + pub fn resolve(self: PageOff, base: Relocation, source_addr: u64, target_addr: u64) !void { + switch (self.kind) { + .page => { + // 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()); + + }, + .got => { + // 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()); + }, + .tlvp => { + + // 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 fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "PageOff {{ ", .{}); + switch (self.kind) { + .page => {}, + .got => { + try std.fmt.format(writer, ".got, ", .{}); + }, + .tlvp => { + try std.fmt.format(writer, ".tlvp, ", .{}); + }, + } + if (self.addend) |add| { + try std.fmt.format(writer, ".addend = {}, ", .{add}); + } + if (self.op_kind) |op| { + try std.fmt.format(writer, ".op_kind = {s}, ", .{op}); + } + try std.fmt.format(writer, "}}", .{}); + } + }; + + pub const PointerToGot = struct { + pub fn resolve(self: PointerToGot, base: Relocation, source_addr: u64, target_addr: u64) !void { + const result = try math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr)); + mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, result)); + } + + pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "PointerToGot {{}}", .{}); + } + }; + + pub const Signed = struct { + addend: i32, + correction: i4, + + pub fn resolve(self: Signed, base: Relocation, source_addr: u64, target_addr: u64) !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 fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "Signed {{ ", .{}); + try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); + try std.fmt.format(writer, ".correction = {}, ", .{self.correction}); + try std.fmt.format(writer, "}}", .{}); + } + }; + + pub const Load = struct { + kind: enum { + got, + tlvp, + }, + addend: ?i32 = null, + + pub fn resolve(self: Load, base: Relocation, source_addr: u64, target_addr: u64) !void { + if (self.kind == .tlvp) { + // We need to rewrite the opcode from movq to leaq. + base.block.code[base.offset - 2] = 0x8d; + } + const addend = if (self.addend) |addend| addend else 0; + const displacement = try math.cast( + i32, + @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4 + addend, + ); + mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement)); + } + + pub fn format(self: Load, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try std.fmt.format(writer, "Load {{ ", .{}); + try std.fmt.format(writer, "{s}, ", .{self.kind}); + if (self.addend) |addend| { + try std.fmt.format(writer, ".addend = {}, ", .{addend}); + } + try std.fmt.format(writer, "}}", .{}); + } + }; + + pub fn resolve(self: Relocation, zld: *Zld) !void { + const source_addr = blk: { + const sym = zld.locals.items[self.block.local_sym_index]; + break :blk sym.payload.regular.address; + }; + const target_addr = blk: { + const is_via_got = inner: { + switch (self.payload) { + .pointer_to_got => break :inner true, + .page => |page| page.kind == .got, + .page_off => |page_off| page_off == .got, + .load => {}, + else => break :inner false, + } + }; + + if (is_via_got) { + const dc_seg = zld.load_commands.items[zld.data_const_segment_cmd_index.?].Segment; + const got = dc_seg.sections.items[zld.got_section_index.?]; + const got_index = self.target.got_index orelse { + log.err("expected GOT entry for symbol '{s}'", .{self.target.name}); + log.err(" this is an internal linker error", .{}); + return error.FailedToResolveRelocationTarget; + }; + break :blk got.addr + got_index * @sizeOf(u64); + } + + switch (self.target.payload) { + .regular => |reg| break :blk reg.address, + .proxy => |proxy| { + if (mem.eql(u8, self.target.name, "__tlv_bootstrap")) { + const segment = zld.load_commands.items[zld.data_segment_cmd_index.?].Segment; + const tlv = segment.sections.items[zld.tlv_section_index.?]; + break :blk tlv.addr; + } + + const segment = zld.load_commands.items[zld.text_segment_cmd_index.?].Segment; + const stubs = segment.sections.items[zld.stubs_section_index.?]; + const stubs_index = self.target.stubs_index orelse { + if (proxy.bind_info.items.len > 0) { + break :blk 0; // Dynamically bound by dyld. + } + log.err("expected stubs index or dynamic bind address for symbol '{s}'", .{ + self.target.name, + }); + log.err(" this is an internal linker error", .{}); + return error.FailedToResolveRelocationTarget; + }; + break :blk stubs.addr + stubs_index * stubs.reserved2; + }, + else => { + log.err("failed to resolve symbol '{s}' as a relocation target", .{self.target.name}); + log.err(" this is an internal linker error", .{}); + return error.FailedToResolveRelocationTarget; + }, + } + }; + switch (self.payload) { + .unsigned => |unsigned| try unsigned.resolve(self, source_addr, target_addr), + .branch => |branch| try branch.resolve(self, source_addr, target_addr), + .page => |page| try page.resolve(self, source_addr, target_addr), + .page_off => |page_off| try page_off.resolve(self, source_addr, target_addr), + .pointer_to_got => |pointer_to_got| try pointer_to_got.resolve(self, source_addr, target_addr), + .signed => |signed| try signed.resolve(self, source_addr, target_addr), + .load => |load| try load.resolve(self, source_addr, target_addr), + } } -}; -pub const Unsigned = struct { - base: Relocation, - subtractor: ?*Symbol = 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) !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 format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - if (self.subtractor) |sub| { - try std.fmt.format(writer, ".subtractor = {}, ", .{sub}); - } - try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); - const length: usize = if (self.is_64bit) 8 else 4; - try std.fmt.format(writer, ".length = {}, ", .{length}); + pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + try std.fmt.format(writer, "Relocation {{ ", .{}); + try std.fmt.format(writer, ".offset = {}, ", .{self.offset}); + try std.fmt.format(writer, ".block = {}", .{self.block.local_sym_index}); + try std.fmt.format(writer, ".target = {}, ", .{self.target}); + + switch (self.payload) { + .unsigned => |unsigned| try unsigned.format(fmt, options, writer), + .branch => |branch| try branch.format(fmt, options, writer), + .page => |page| try page.format(fmt, options, writer), + .page_off => |page_off| try page_off.format(fmt, options, writer), + .pointer_to_got => |pointer_to_got| try pointer_to_got.format(fmt, options, writer), + .signed => |signed| try signed.format(fmt, options, writer), + .load => |load| try load.format(fmt, options, writer), + } + + try std.fmt.format(writer, "}}", .{}); } }; @@ -161,3 +452,342 @@ pub const RelocIterator = struct { return self.buffer[@intCast(u32, self.index + 1)]; } }; + +pub const Parser = struct { + object: *Object, + zld: *Zld, + it: *RelocIterator, + block: *TextBlock, + + /// Base address of the parsed text block in the source section. + base_addr: u64, + + /// Used only when targeting aarch64 + addend: ?u32 = null, + + /// Parsed subtractor symbol from _RELOC_SUBTRACTOR reloc type. + subtractor: ?*Symbol = null, + + pub fn parse(self: *Parser) !void { + while (self.it.next()) |rel| { + const out_rel = blk: { + switch (self.object.arch.?) { + .aarch64 => { + const out_rel = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) { + .ARM64_RELOC_BRANCH26 => try self.parseBranch(rel), + .ARM64_RELOC_SUBTRACTOR => { + // Subtractor is not a relocation with effect on the TextBlock, so + // parse it and carry on. + try self.parseSubtractor(rel); + + // Verify SUBTRACTOR is followed by UNSIGNED. + const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type); + if (next != .ARM64_RELOC_UNSIGNED) { + log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); + return error.UnexpectedRelocationType; + } + continue; + }, + .ARM64_RELOC_UNSIGNED => try self.parseUnsigned(rel), + .ARM64_RELOC_ADDEND => { + // Addend is not a relocation with effect on the TextBlock, so + // parse it and carry on. + try self.parseAddend(rel); + + // Verify ADDEND is followed by a load. + const next = @intToEnum(macho.reloc_type_arm64, self.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; + }, + } + continue; + }, + .ARM64_RELOC_PAGE21, + .ARM64_RELOC_GOT_LOAD_PAGE21, + .ARM64_RELOC_TLVP_LOAD_PAGE21, + => try self.parsePage(rel), + .ARM64_RELOC_PAGEOFF12, + .ARM64_RELOC_GOT_LOAD_PAGEOFF12, + .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, + => try self.parsePageOff(rel), + .ARM64_RELOC_POINTER_TO_GOT => try self.parsePointerToGot(rel), + }; + break :blk out_rel; + }, + .x86_64 => { + const out_rel = switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) { + .X86_64_RELOC_BRANCH => try self.parseBranch(rel), + .X86_64_RELOC_SUBTRACTOR => { + // Subtractor is not a relocation with effect on the TextBlock, so + // parse it and carry on. + try self.parseSubtractor(rel); + + // Verify SUBTRACTOR is followed by UNSIGNED. + const next = @intToEnum(macho.reloc_type_x86_64, self.it.peek().r_type); + if (next != .X86_64_RELOC_UNSIGNED) { + log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); + return error.UnexpectedRelocationType; + } + continue; + }, + .X86_64_RELOC_UNSIGNED => try self.parseUnsigned(rel), + .X86_64_RELOC_SIGNED, + .X86_64_RELOC_SIGNED_1, + .X86_64_RELOC_SIGNED_2, + .X86_64_RELOC_SIGNED_4, + => try self.parseSigned(rel), + .X86_64_RELOC_GOT_LOAD, + .X86_64_RELOC_GOT, + .X86_64_RELOC_TLV, + => try self.parseLoad(rel), + }; + break :blk out_rel; + }, + else => unreachable, + } + }; + try self.block.relocs.append(out_rel); + + if (out_rel.target.payload == .regular) { + try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {}); + } + + const is_via_got = switch (out_rel.payload) { + .pointer_to_got => true, + .load => |load| load.kind == .got, + .page => |page| page.kind == .got, + .page_off => |page_off| page_off.kind == .got, + else => false, + }; + + if (is_via_got and out_rel.target.got_index == null) { + const index = @intCast(u32, self.zld.got_entries.items.len); + out_rel.target.got_index = index; + try self.zld.got_entries.append(self.zld.allocator, out_rel.target); + log.debug("adding GOT entry for symbol {s} at index {}", .{ out_rel.target.name, index }); + } + + if (out_rel.payload == .branch) { + const sym = out_rel.target; + + if (sym.stubs_index != null) continue; + if (sym.payload != .proxy) continue; + + const index = @intCast(u32, self.zld.stubs.items.len); + sym.stubs_index = index; + try self.zld.stubs.append(self.zld.allocator, sym); + + log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index }); + } + } + } + + fn parseBaseRelInfo(self: *Parser, rel: macho.relocation_info) !Relocation { + const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); + const target = try self.object.symbolFromReloc(rel); + return Relocation{ + .offset = offset, + .target = target, + .block = self.block, + .payload = undefined, + }; + } + + fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !Relocation { + defer { + // Reset parser's subtractor state + self.subtractor = null; + } + + assert(rel.r_pcrel == 0); + + var parsed = try self.parseBaseRelInfo(rel); + const is_64bit: bool = switch (rel.r_length) { + 3 => true, + 2 => false, + else => unreachable, + }; + const addend: i64 = if (is_64bit) + mem.readIntLittle(i64, self.block.code[parsed.offset..][0..8]) + else + mem.readIntLittle(i32, self.block.code[parsed.offset..][0..4]); + + parsed.payload = .{ + .unsigned = .{ + .subtractor = self.subtractor, + .is_64bit = is_64bit, + .addend = addend, + }, + }; + + return parsed; + } + + fn parseBranch(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 1); + assert(rel.r_length == 2); + + var parsed = try self.parseBaseRelInfo(rel); + parsed.payload = .{ + .branch = .{ + .arch = self.object.arch.?, + }, + }; + return parsed; + } + + fn parsePage(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 1); + assert(rel.r_length == 2); + + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + + defer if (rel_type == .ARM64_RELOC_PAGE21) { + // Reset parser's addend state + self.addend = null; + }; + + const addend = if (rel_type == .ARM64_RELOC_PAGE21) + self.addend + else + null; + + var parsed = try self.parseBaseRelInfo(rel); + parsed.payload = .{ + .page = .{ + .kind = switch (rel_type) { + .ARM64_RELOC_PAGE21 => .page, + .ARM64_RELOC_GOT_LOAD_PAGE21 => .got, + .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp, + else => unreachable, + }, + .addend = addend, + }, + }; + return parsed; + } + + fn parsePageOff(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 0); + assert(rel.r_length == 2); + + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + + defer if (rel_type == .ARM64_RELOC_PAGEOFF12) { + // Reset parser's addend state + self.addend = null; + }; + + const addend = if (rel_type == .ARM64_RELOC_PAGEOFF12) + self.addend + else + null; + + var parsed = try self.parseBaseRelInfo(rel); + const op_kind: ?Relocation.PageOff.OpKind = blk: { + if (rel_type != .ARM64_RELOC_PAGEOFF12) break :blk null; + const op_kind: Relocation.PageOff.OpKind = if (isArithmeticOp(self.block.code[parsed.offset..][0..4])) + .arithmetic + else + .load; + break :blk op_kind; + }; + + parsed.payload = .{ + .page_off = .{ + .kind = switch (rel_type) { + .ARM64_RELOC_PAGEOFF12 => .page, + .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got, + .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp, + else => unreachable, + }, + .addend = addend, + .op_kind = op_kind, + }, + }; + return parsed; + } + + fn parsePointerToGot(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 1); + assert(rel.r_length == 2); + + var parsed = try self.parseBaseRelInfo(rel); + parsed.payload = .{ + .pointer_to_got = .{}, + }; + return parsed; + } + + fn parseAddend(self: *Parser, rel: macho.relocation_info) !void { + assert(rel.r_pcrel == 0); + assert(rel.r_extern == 0); + assert(self.addend == null); + + self.addend = rel.r_symbolnum; + } + + fn parseSigned(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 1); + assert(rel.r_length == 2); + + var parsed = try self.parseBaseRelInfo(rel); + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + 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, self.block.code[parsed.offset..][0..4]) + correction; + + parsed.payload = .{ + .signed = .{ + .correction = correction, + .addend = addend, + }, + }; + + return parsed; + } + + fn parseSubtractor(self: *Parser, rel: macho.relocation_info) !void { + assert(rel.r_pcrel == 0); + assert(self.subtractor == null); + + self.subtractor = try self.object.symbolFromReloc(rel); + } + + fn parseLoad(self: *Parser, rel: macho.relocation_info) !Relocation { + assert(rel.r_pcrel == 1); + assert(rel.r_length == 2); + + var parsed = try self.parseBaseRelInfo(rel); + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + const addend = if (rel_type == .X86_64_RELOC_GOT) + mem.readIntLittle(i32, self.block.code[parsed.offset..][0..4]) + else + null; + + parsed.payload = .{ + .load = .{ + .kind = switch (rel_type) { + .X86_64_RELOC_GOT_LOAD, .X86_64_RELOC_GOT => .got, + .X86_64_RELOC_TLV => .tlvp, + else => unreachable, + }, + .addend = addend, + }, + }; + return parsed; + } +}; + +inline fn isArithmeticOp(inst: *const [4]u8) bool { + const group_decode = @truncate(u5, inst[3]); + return ((group_decode >> 2) == 4); +} diff --git a/src/link/MachO/reloc/aarch64.zig b/src/link/MachO/reloc/aarch64.zig deleted file mode 100644 index 5105282e43..0000000000 --- a/src/link/MachO/reloc/aarch64.zig +++ /dev/null @@ -1,618 +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 Object = @import("../Object.zig"); -const Relocation = reloc.Relocation; -const Symbol = @import("../Symbol.zig"); -const TextBlock = Zld.TextBlock; -const Zld = @import("../Zld.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 fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - if (self.addend) |addend| { - try std.fmt.format(writer, ".addend = {}, ", .{addend}); - } - } -}; - -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 fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - if (self.addend) |addend| { - try std.fmt.format(writer, ".addend = {}, ", .{addend}); - } - try std.fmt.format(writer, ".op_kind = {s}, ", .{self.op_kind}); - } -}; - -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 fn format(self: GotPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: GotPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: TlvpPage, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: TlvpPageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -pub const Parser = struct { - object: *Object, - zld: *Zld, - it: *reloc.RelocIterator, - block: *TextBlock, - base_addr: u64, - addend: ?u32 = null, - subtractor: ?*Symbol = null, - - pub fn parse(self: *Parser) !void { - while (self.it.next()) |rel| { - const out_rel = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) { - .ARM64_RELOC_BRANCH26 => try self.parseBranch(rel), - .ARM64_RELOC_SUBTRACTOR => { - // Subtractor is not a relocation with effect on the TextBlock, so - // parse it and carry on. - try self.parseSubtractor(rel); - continue; - }, - .ARM64_RELOC_UNSIGNED => try self.parseUnsigned(rel), - .ARM64_RELOC_ADDEND => { - // Addend is not a relocation with effect on the TextBlock, so - // parse it and carry on. - try self.parseAddend(rel); - continue; - }, - .ARM64_RELOC_PAGE21, - .ARM64_RELOC_GOT_LOAD_PAGE21, - .ARM64_RELOC_TLVP_LOAD_PAGE21, - => try self.parsePage(rel), - .ARM64_RELOC_PAGEOFF12 => try self.parsePageOff(rel), - .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => try self.parseGotLoadPageOff(rel), - .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => try self.parseTlvpLoadPageOff(rel), - .ARM64_RELOC_POINTER_TO_GOT => try self.parsePointerToGot(rel), - }; - try self.block.relocs.append(out_rel); - - if (out_rel.target.payload == .regular) { - try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {}); - } - - switch (out_rel.@"type") { - .got_page, .got_page_off, .pointer_to_got => { - const sym = out_rel.target; - - if (sym.got_index != null) continue; - - const index = @intCast(u32, self.zld.got_entries.items.len); - sym.got_index = index; - try self.zld.got_entries.append(self.zld.allocator, sym); - - log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index }); - }, - .branch_aarch64 => { - const sym = out_rel.target; - - if (sym.stubs_index != null) continue; - if (sym.payload != .proxy) continue; - - const index = @intCast(u32, self.zld.stubs.items.len); - sym.stubs_index = index; - try self.zld.stubs.append(self.zld.allocator, sym); - - log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index }); - }, - else => {}, - } - } - } - - fn parseAddend(self: *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(self.addend == null); - - self.addend = rel.r_symbolnum; - - // Verify ADDEND is followed by a load. - const next = @intToEnum(macho.reloc_type_arm64, self.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(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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, @intCast(u64, rel.r_address) - self.base_addr); - const target = try self.object.symbolFromReloc(rel); - - var branch = try self.object.allocator.create(Branch); - errdefer self.object.allocator.destroy(branch); - - branch.* = .{ - .base = .{ - .@"type" = .branch_aarch64, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &branch.base; - } - - fn parsePage(self: *Parser, rel: macho.relocation_info) !*Relocation { - assert(rel.r_pcrel == 1); - assert(rel.r_length == 2); - - const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - const target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - - const ptr: *Relocation = ptr: { - switch (rel_type) { - .ARM64_RELOC_PAGE21 => { - defer { - // Reset parser's addend state - self.addend = null; - } - var page = try self.object.allocator.create(Page); - errdefer self.object.allocator.destroy(page); - - page.* = .{ - .base = .{ - .@"type" = .page, - .offset = offset, - .target = target, - .block = self.block, - }, - .addend = self.addend, - }; - - break :ptr &page.base; - }, - .ARM64_RELOC_GOT_LOAD_PAGE21 => { - var page = try self.object.allocator.create(GotPage); - errdefer self.object.allocator.destroy(page); - - page.* = .{ - .base = .{ - .@"type" = .got_page, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - break :ptr &page.base; - }, - .ARM64_RELOC_TLVP_LOAD_PAGE21 => { - var page = try self.object.allocator.create(TlvpPage); - errdefer self.object.allocator.destroy(page); - - page.* = .{ - .base = .{ - .@"type" = .tlvp_page, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - break :ptr &page.base; - }, - else => unreachable, - } - }; - - return ptr; - } - - fn parsePageOff(self: *Parser, rel: macho.relocation_info) !*Relocation { - defer { - // Reset parser's addend state - self.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 target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - const op_kind: PageOff.OpKind = if (isArithmeticOp(self.block.code[offset..][0..4])) - .arithmetic - else - .load_store; - - var page_off = try self.object.allocator.create(PageOff); - errdefer self.object.allocator.destroy(page_off); - - page_off.* = .{ - .base = .{ - .@"type" = .page_off, - .offset = offset, - .target = target, - .block = self.block, - }, - .op_kind = op_kind, - .addend = self.addend, - }; - - return &page_off.base; - } - - fn parseGotLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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 target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - assert(!isArithmeticOp(self.block.code[offset..][0..4])); - - var page_off = try self.object.allocator.create(GotPageOff); - errdefer self.object.allocator.destroy(page_off); - - page_off.* = .{ - .base = .{ - .@"type" = .got_page_off, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &page_off.base; - } - - fn parseTlvpLoadPageOff(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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 target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - - var page_off = try self.object.allocator.create(TlvpPageOff); - errdefer self.object.allocator.destroy(page_off); - - page_off.* = .{ - .base = .{ - .@"type" = .tlvp_page_off, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &page_off.base; - } - - fn parseSubtractor(self: *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(self.subtractor == null); - - self.subtractor = try self.object.symbolFromReloc(rel); - - // Verify SUBTRACTOR is followed by UNSIGNED. - const next = @intToEnum(macho.reloc_type_arm64, self.it.peek().r_type); - if (next != .ARM64_RELOC_UNSIGNED) { - log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); - return error.UnexpectedRelocationType; - } - } - - fn parseUnsigned(self: *Parser, rel: macho.relocation_info) !*Relocation { - defer { - // Reset parser's subtractor state - self.subtractor = null; - } - - const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); - assert(rel_type == .ARM64_RELOC_UNSIGNED); - assert(rel.r_pcrel == 0); - - const target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - const is_64bit: bool = switch (rel.r_length) { - 3 => true, - 2 => false, - else => unreachable, - }; - const addend: i64 = if (is_64bit) - mem.readIntLittle(i64, self.block.code[offset..][0..8]) - else - mem.readIntLittle(i32, self.block.code[offset..][0..4]); - - var unsigned = try self.object.allocator.create(reloc.Unsigned); - errdefer self.object.allocator.destroy(unsigned); - - unsigned.* = .{ - .base = .{ - .@"type" = .unsigned, - .offset = offset, - .target = target, - .block = self.block, - }, - .subtractor = self.subtractor, - .is_64bit = is_64bit, - .addend = addend, - }; - - return &unsigned.base; - } - - fn parsePointerToGot(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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 self.object.allocator.create(PointerToGot); - errdefer self.object.allocator.destroy(ptr_to_got); - - const target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - - ptr_to_got.* = .{ - .base = .{ - .@"type" = .pointer_to_got, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &ptr_to_got.base; - } -}; - -inline fn isArithmeticOp(inst: *const [4]u8) bool { - const group_decode = @truncate(u5, inst[3]); - return ((group_decode >> 2) == 4); -} diff --git a/src/link/MachO/reloc/x86_64.zig b/src/link/MachO/reloc/x86_64.zig deleted file mode 100644 index 85c797dcd0..0000000000 --- a/src/link/MachO/reloc/x86_64.zig +++ /dev/null @@ -1,385 +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 Object = @import("../Object.zig"); -const Relocation = reloc.Relocation; -const Symbol = @import("../Symbol.zig"); -const TextBlock = Zld.TextBlock; -const Zld = @import("../Zld.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 fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); - try std.fmt.format(writer, ".correction = {}, ", .{self.correction}); - } -}; - -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 fn format(self: GotLoad, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -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 fn format(self: Got, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); - } -}; - -pub const Tlv = struct { - base: Relocation, - - 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 fn format(self: Tlv, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = self; - _ = fmt; - _ = options; - _ = writer; - } -}; - -pub const Parser = struct { - object: *Object, - zld: *Zld, - it: *reloc.RelocIterator, - block: *TextBlock, - base_addr: u64, - subtractor: ?*Symbol = null, - - pub fn parse(self: *Parser) !void { - while (self.it.next()) |rel| { - const out_rel = switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) { - .X86_64_RELOC_BRANCH => try self.parseBranch(rel), - .X86_64_RELOC_SUBTRACTOR => { - // Subtractor is not a relocation with effect on the TextBlock, so - // parse it and carry on. - try self.parseSubtractor(rel); - continue; - }, - .X86_64_RELOC_UNSIGNED => try self.parseUnsigned(rel), - .X86_64_RELOC_SIGNED, - .X86_64_RELOC_SIGNED_1, - .X86_64_RELOC_SIGNED_2, - .X86_64_RELOC_SIGNED_4, - => try self.parseSigned(rel), - .X86_64_RELOC_GOT_LOAD => try self.parseGotLoad(rel), - .X86_64_RELOC_GOT => try self.parseGot(rel), - .X86_64_RELOC_TLV => try self.parseTlv(rel), - }; - try self.block.relocs.append(out_rel); - - if (out_rel.target.payload == .regular) { - try self.block.references.put(out_rel.target.payload.regular.local_sym_index, {}); - } - - switch (out_rel.@"type") { - .got_load, .got => { - const sym = out_rel.target; - - if (sym.got_index != null) continue; - - const index = @intCast(u32, self.zld.got_entries.items.len); - sym.got_index = index; - try self.zld.got_entries.append(self.zld.allocator, sym); - - log.debug("adding GOT entry for symbol {s} at index {}", .{ sym.name, index }); - }, - .branch_x86_64 => { - const sym = out_rel.target; - - if (sym.stubs_index != null) continue; - if (sym.payload != .proxy) continue; - - const index = @intCast(u32, self.zld.stubs.items.len); - sym.stubs_index = index; - try self.zld.stubs.append(self.zld.allocator, sym); - - log.debug("adding stub entry for symbol {s} at index {}", .{ sym.name, index }); - }, - else => {}, - } - } - } - - fn parseBranch(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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, @intCast(u64, rel.r_address) - self.base_addr); - const target = try self.object.symbolFromReloc(rel); - - var branch = try self.object.allocator.create(Branch); - errdefer self.object.allocator.destroy(branch); - - branch.* = .{ - .base = .{ - .@"type" = .branch_x86_64, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &branch.base; - } - - fn parseSigned(self: *Parser, rel: macho.relocation_info) !*Relocation { - assert(rel.r_pcrel == 1); - assert(rel.r_length == 2); - - const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); - const target = try self.object.symbolFromReloc(rel); - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - 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, self.block.code[offset..][0..4]) + correction; - - var signed = try self.object.allocator.create(Signed); - errdefer self.object.allocator.destroy(signed); - - signed.* = .{ - .base = .{ - .@"type" = .signed, - .offset = offset, - .target = target, - .block = self.block, - }, - .addend = addend, - .correction = correction, - }; - - return &signed.base; - } - - fn parseGotLoad(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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, @intCast(u64, rel.r_address) - self.base_addr); - const target = try self.object.symbolFromReloc(rel); - - var got_load = try self.object.allocator.create(GotLoad); - errdefer self.object.allocator.destroy(got_load); - - got_load.* = .{ - .base = .{ - .@"type" = .got_load, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &got_load.base; - } - - fn parseGot(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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, @intCast(u64, rel.r_address) - self.base_addr); - const target = try self.object.symbolFromReloc(rel); - const addend = mem.readIntLittle(i32, self.block.code[offset..][0..4]); - - var got = try self.object.allocator.create(Got); - errdefer self.object.allocator.destroy(got); - - got.* = .{ - .base = .{ - .@"type" = .got, - .offset = offset, - .target = target, - .block = self.block, - }, - .addend = addend, - }; - - return &got.base; - } - - fn parseTlv(self: *Parser, rel: macho.relocation_info) !*Relocation { - 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, @intCast(u64, rel.r_address) - self.base_addr); - const target = try self.object.symbolFromReloc(rel); - - var tlv = try self.object.allocator.create(Tlv); - errdefer self.object.allocator.destroy(tlv); - - tlv.* = .{ - .base = .{ - .@"type" = .tlv, - .offset = offset, - .target = target, - .block = self.block, - }, - }; - - return &tlv.base; - } - - fn parseSubtractor(self: *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(self.subtractor == null); - - self.subtractor = try self.object.symbolFromReloc(rel); - - // Verify SUBTRACTOR is followed by UNSIGNED. - const next = @intToEnum(macho.reloc_type_x86_64, self.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(self: *Parser, rel: macho.relocation_info) !*Relocation { - defer { - // Reset parser's subtractor state - self.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); - - const target = try self.object.symbolFromReloc(rel); - const is_64bit: bool = switch (rel.r_length) { - 3 => true, - 2 => false, - else => unreachable, - }; - const offset = @intCast(u32, @intCast(u64, rel.r_address) - self.base_addr); - const addend: i64 = if (is_64bit) - mem.readIntLittle(i64, self.block.code[offset..][0..8]) - else - mem.readIntLittle(i32, self.block.code[offset..][0..4]); - - var unsigned = try self.object.allocator.create(reloc.Unsigned); - errdefer self.object.allocator.destroy(unsigned); - - unsigned.* = .{ - .base = .{ - .@"type" = .unsigned, - .offset = offset, - .target = target, - .block = self.block, - }, - .subtractor = self.subtractor, - .is_64bit = is_64bit, - .addend = addend, - }; - - return &unsigned.base; - } -}; |
