diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-04-02 19:53:16 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-04-13 10:56:03 +0200 |
| commit | 6a866f1a96d232a681a71b899657df1ae70b8f2e (patch) | |
| tree | 0e2ffbb81ef73db48a840a52ae59ebbe935e3a87 /src | |
| parent | 4e676ecbb5c107ca5c9dbc497920d0150e4e1d8e (diff) | |
| download | zig-6a866f1a96d232a681a71b899657df1ae70b8f2e.tar.gz zig-6a866f1a96d232a681a71b899657df1ae70b8f2e.zip | |
zld: preprocess relocs on arm64
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO/Object.zig | 105 | ||||
| -rw-r--r-- | src/link/MachO/reloc.zig | 708 |
2 files changed, 763 insertions, 50 deletions
diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 2275032b9c..90b67b5136 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -7,8 +7,10 @@ const io = std.io; const log = std.log.scoped(.object); const macho = std.macho; const mem = std.mem; +const reloc = @import("reloc.zig"); const Allocator = mem.Allocator; +const Relocation = reloc.Relocation; const Symbol = @import("Symbol.zig"); const parseName = @import("Zld.zig").parseName; @@ -22,6 +24,7 @@ file_offset: ?u32 = null, name: ?[]u8 = null, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, +sections: std.ArrayListUnmanaged(Section) = .{}, segment_cmd_index: ?u16 = null, symtab_cmd_index: ?u16 = null, @@ -42,6 +45,24 @@ strtab: std.ArrayListUnmanaged(u8) = .{}, data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, +const Section = struct { + inner: macho.section_64, + code: []u8, + relocs: ?[]*Relocation, + // TODO store object-to-exe-section mapping here + + 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 init(allocator: *Allocator) Object { return .{ .allocator = allocator, @@ -53,6 +74,12 @@ 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); + self.symtab.deinit(self.allocator); self.strtab.deinit(self.allocator); self.data_in_code_entries.deinit(self.allocator); @@ -95,15 +122,9 @@ pub fn parse(self: *Object) !void { } try self.readLoadCommands(reader); + try self.parseSections(); if (self.symtab_cmd_index != null) try self.parseSymtab(); if (self.data_in_code_cmd_index != null) try self.readDataInCode(); - - { - const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; - for (seg.sections.items) |_, sect_id| { - try self.parseRelocs(@intCast(u16, sect_id)); - } - } } pub fn readLoadCommands(self: *Object, reader: anytype) !void { @@ -170,53 +191,37 @@ pub fn readLoadCommands(self: *Object, reader: anytype) !void { } } -pub fn parseRelocs(self: *Object, sect_id: u16) !void { +pub fn parseSections(self: *Object) !void { const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; - const sect = seg.sections.items[sect_id]; - - if (sect.nreloc == 0) return; - - var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); - defer self.allocator.free(raw_relocs); - _ = try self.file.?.preadAll(raw_relocs, sect.reloff); - const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs); - - for (relocs) |reloc| { - const is_addend = is_addend: { - switch (self.arch.?) { - .x86_64 => { - const rel_type = @intToEnum(macho.reloc_type_x86_64, reloc.r_type); - log.warn("{s}", .{rel_type}); - - break :is_addend false; - }, - .aarch64 => { - const rel_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); - log.warn("{s}", .{rel_type}); - - break :is_addend rel_type == .ARM64_RELOC_ADDEND; - }, - else => unreachable, - } + + try self.sections.ensureCapacity(self.allocator, seg.sections.items.len); + + for (seg.sections.items) |sect| { + // Read sections' code + var code = try self.allocator.alloc(u8, sect.size); + _ = try self.file.?.preadAll(code, sect.offset); + + var section = Section{ + .inner = sect, + .code = code, + .relocs = undefined, }; - if (!is_addend) { - if (reloc.r_extern == 1) { - const sym = self.symtab.items[reloc.r_symbolnum]; - const sym_name = self.getString(sym.inner.n_strx); - log.warn(" | symbol = {s}", .{sym_name}); - } else { - const target_sect = seg.sections.items[reloc.r_symbolnum - 1]; - log.warn(" | section = {s},{s}", .{ - parseName(&target_sect.segname), - parseName(&target_sect.sectname), - }); - } - } + // Parse relocations + var relocs: ?[]*Relocation = if (sect.nreloc > 0) relocs: { + var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); + defer self.allocator.free(raw_relocs); + + _ = try self.file.?.preadAll(raw_relocs, sect.reloff); + + break :relocs try reloc.parse( + self.allocator, + §ion.code, + mem.bytesAsSlice(macho.relocation_info, raw_relocs), + ); + } else null; - log.warn(" | offset = 0x{x}", .{reloc.r_address}); - log.warn(" | PC = {}", .{reloc.r_pcrel == 1}); - log.warn(" | length = {}", .{reloc.r_length}); + self.sections.appendAssumeCapacity(section); } } diff --git a/src/link/MachO/reloc.zig b/src/link/MachO/reloc.zig new file mode 100644 index 0000000000..a0e922d6ac --- /dev/null +++ b/src/link/MachO/reloc.zig @@ -0,0 +1,708 @@ +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 mem = std.mem; +const meta = std.meta; + +const Allocator = mem.Allocator; + +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 Type = enum { + branch, + unsigned, + page, + page_off, + got_page, + got_page_off, + tlvp_page, + tlvp_page_off, + }; + + pub const Target = union(enum) { + symbol: u32, + section: u16, + + pub fn from_reloc(reloc: macho.relocation_info) Target { + return if (reloc.r_extern == 1) .{ + .symbol = reloc.r_symbolnum, + } else .{ + .section = @intCast(u16, reloc.r_symbolnum - 1), + }; + } + }; + + pub const Branch = struct { + base: Relocation, + /// Always .UnconditionalBranchImmediate + inst: aarch64.Instruction, + + pub const base_type: Relocation.Type = .branch; + + pub fn resolve(branch: Branch, source_addr: u64, target_addr: u64) !void { + const displacement = try math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr)); + var inst = branch.inst; + inst.AddSubtractImmediate.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2); + mem.writeIntLittle(u32, branch.base.code.*[branch.base.offset..], inst.toU32()); + } + }; + + pub const Unsigned = struct { + base: Relocation, + subtractor: ?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, target_addr: u64, subtractor: i64) !void { + const result = @intCast(i64, target_addr) - subtractor + unsigned.addend; + + log.debug(" | calculated addend 0x{x}", .{unsigned.addend}); + log.debug(" | calculated unsigned value 0x{x}", .{result}); + + if (unsigned.is_64bit) { + mem.writeIntLittle( + u64, + unsigned.base.code.*[unsigned.base.offset..], + @bitCast(u64, result), + ); + } else { + mem.writeIntLittle( + u32, + unsigned.base.code.*[unsigned.base.offset..], + @truncate(u32, @bitCast(u64, result)), + ); + } + } + }; + + 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, source_addr: u64, target_addr: u64) !void { + const ta = if (page.addend) |a| target_addr + a else target_addr; + const source_page = @intCast(i32, source_addr >> 12); + const target_page = @intCast(i32, ta >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); + + log.debug(" | moving by {} pages", .{pages}); + + var inst = page.inst; + inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2); + inst.PCRelativeAddress.immlo = @truncate(u2, pages); + + mem.writeIntLittle(u32, page.base.code.*[page.base.offset..], 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, target_addr: u64) !void { + const ta = if (page_off.addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + + 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.AddSubtractImmediate.imm12 = narrowed; + } else { + const offset: u12 = blk: { + if (inst.LoadStoreRegister.size == 0) { + if (inst.LoadStoreRegister.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.LoadStoreRegister.size); + break :blk try math.divExact(u12, narrowed, denom); + } + }; + inst.LoadStoreRegister.offset = offset; + } + + mem.writeIntLittle(u32, page_off.base.code.*[page_off.base.offset..], 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, source_addr: u64, target_addr: u64) !void { + const source_page = @intCast(i32, source_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); + + log.debug(" | moving by {} pages", .{pages}); + + var inst = page.inst; + inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2); + inst.PCRelativeAddress.immlo = @truncate(u2, pages); + + mem.writeIntLittle(u32, page.base.code.*[page.base.offset..], 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, target_addr: u64) !void { + const narrowed = @truncate(u12, 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.LoadStoreRegister.offset = offset; + + mem.writeIntLittle(u32, page_off.base.code.*[page_off.base.offset..], inst.toU32()); + } + }; + + pub const TlvpPage = struct { + base: Relocation, + /// Always .PCRelativeAddress + inst: aarch64.Instruction, + + pub const base_type: Relocation.Type = .tlvp_page; + + pub fn resolve(page: TlvpPage, source_addr: u64, target_addr: u64) !void { + const source_page = @intCast(i32, source_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); + + log.debug(" | moving by {} pages", .{pages}); + + var inst = page.inst; + inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2); + inst.PCRelativeAddress.immlo = @truncate(u2, pages); + + mem.writeIntLittle(u32, page.base.code.*[page.base.offset..], 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, target_addr: u64) !void { + const narrowed = @truncate(u12, target_addr); + + log.debug(" | narrowed address within the page 0x{x}", .{narrowed}); + + var inst = page_off.inst; + inst.AddSubtractImmediate.imm12 = narrowed; + + mem.writeIntLittle(u32, page_off.base.code.*[page_off.base.offset..], inst.toU32()); + } + }; +}; + +pub fn parse(allocator: *Allocator, code: *[]u8, relocs: []const macho.relocation_info) ![]*Relocation { + var it = RelocIterator{ + .buffer = relocs, + }; + + var parser = Parser{ + .allocator = allocator, + .it = &it, + .code = code, + .parsed = std.ArrayList(*Relocation).init(allocator), + }; + defer parser.deinit(); + + return parser.parse(); +} + +const RelocIterator = struct { + buffer: []const macho.relocation_info, + index: i64 = -1, + + pub fn next(self: *RelocIterator) ?macho.relocation_info { + self.index += 1; + if (self.index < self.buffer.len) { + const reloc = self.buffer[@intCast(u64, self.index)]; + log.warn("{s}", .{@intToEnum(macho.reloc_type_arm64, reloc.r_type)}); + log.warn(" | offset = {}", .{reloc.r_address}); + log.warn(" | PC = {}", .{reloc.r_pcrel == 1}); + log.warn(" | length = {}", .{reloc.r_length}); + log.warn(" | symbolnum = {}", .{reloc.r_symbolnum}); + log.warn(" | extern = {}", .{reloc.r_extern == 1}); + return reloc; + } + return null; + } + + pub fn peek(self: *RelocIterator) ?macho.reloc_type_arm64 { + if (self.index + 1 < self.buffer.len) { + const reloc = self.buffer[@intCast(u64, self.index + 1)]; + const tt = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + return tt; + } + return null; + } +}; + +const Parser = struct { + allocator: *Allocator, + it: *RelocIterator, + code: *[]u8, + parsed: std.ArrayList(*Relocation), + addend: ?u32 = null, + subtractor: ?Relocation.Target = null, + + fn deinit(parser: *Parser) void { + parser.parsed.deinit(); + } + + fn parse(parser: *Parser) ![]*Relocation { + while (parser.it.next()) |reloc| { + switch (@intToEnum(macho.reloc_type_arm64, reloc.r_type)) { + .ARM64_RELOC_BRANCH26 => { + try parser.parseBranch(reloc); + }, + .ARM64_RELOC_SUBTRACTOR => { + try parser.parseSubtractor(reloc); + }, + .ARM64_RELOC_UNSIGNED => { + try parser.parseUnsigned(reloc); + }, + .ARM64_RELOC_ADDEND => { + try parser.parseAddend(reloc); + }, + .ARM64_RELOC_PAGE21, + .ARM64_RELOC_GOT_LOAD_PAGE21, + .ARM64_RELOC_TLVP_LOAD_PAGE21, + => { + try parser.parsePage(reloc); + }, + .ARM64_RELOC_PAGEOFF12 => { + try parser.parsePageOff(reloc); + }, + .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => { + try parser.parseGotLoadPageOff(reloc); + }, + .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { + try parser.parseTlvpLoadPageOff(reloc); + }, + .ARM64_RELOC_POINTER_TO_GOT => { + return error.ToDoRelocPointerToGot; + }, + } + } + + return parser.parsed.toOwnedSlice(); + } + + fn parseAddend(parser: *Parser, reloc: macho.relocation_info) !void { + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_ADDEND); + assert(reloc.r_pcrel == 0); + assert(reloc.r_extern == 0); + assert(parser.addend == null); + + parser.addend = reloc.r_symbolnum; + + // Verify ADDEND is followed by a load. + if (parser.it.peek()) |tt| { + switch (tt) { + .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {}, + else => |other| { + log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{other}); + return error.UnexpectedRelocationType; + }, + } + } else { + log.err("unexpected end of stream", .{}); + return error.UnexpectedEndOfStream; + } + } + + fn parseBranch(parser: *Parser, reloc: macho.relocation_info) !void { + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_BRANCH26); + assert(reloc.r_pcrel == 1); + assert(reloc.r_length == 2); + + const offset = @intCast(u32, reloc.r_address); + const inst = parser.code.*[offset..][0..4]; + const parsed_inst = aarch64.Instruction{ .UnconditionalBranchImmediate = mem.bytesToValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.UnconditionalBranchImmediate, + ), + inst, + ) }; + + var branch = try parser.allocator.create(Relocation.Branch); + errdefer parser.allocator.destroy(branch); + + const target = Relocation.Target.from_reloc(reloc); + + branch.* = .{ + .base = .{ + .@"type" = .branch, + .code = parser.code, + .offset = @intCast(u32, reloc.r_address), + .target = target, + }, + .inst = parsed_inst, + }; + + log.warn(" | emitting {}", .{branch}); + try parser.parsed.append(&branch.base); + } + + fn parsePage(parser: *Parser, reloc: macho.relocation_info) !void { + assert(reloc.r_pcrel == 1); + assert(reloc.r_length == 2); + + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + const target = Relocation.Target.from_reloc(reloc); + + const offset = @intCast(u32, reloc.r_address); + const inst = parser.code.*[offset..][0..4]; + const parsed_inst = aarch64.Instruction{ .PCRelativeAddress = mem.bytesToValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.PCRelativeAddress, + ), inst) }; + + const ptr: *Relocation = ptr: { + switch (reloc_type) { + .ARM64_RELOC_PAGE21 => { + defer { + // Reset parser's addend state + parser.addend = null; + } + var page = try parser.allocator.create(Relocation.Page); + errdefer parser.allocator.destroy(page); + + page.* = .{ + .base = .{ + .@"type" = .page, + .code = parser.code, + .offset = offset, + .target = target, + }, + .addend = parser.addend, + .inst = parsed_inst, + }; + + log.warn(" | emitting {}", .{page}); + + break :ptr &page.base; + }, + .ARM64_RELOC_GOT_LOAD_PAGE21 => { + var page = try parser.allocator.create(Relocation.GotPage); + errdefer parser.allocator.destroy(page); + + page.* = .{ + .base = .{ + .@"type" = .got_page, + .code = parser.code, + .offset = offset, + .target = target, + }, + .inst = parsed_inst, + }; + + log.warn(" | emitting {}", .{page}); + + break :ptr &page.base; + }, + .ARM64_RELOC_TLVP_LOAD_PAGE21 => { + var page = try parser.allocator.create(Relocation.TlvpPage); + errdefer parser.allocator.destroy(page); + + page.* = .{ + .base = .{ + .@"type" = .tlvp_page, + .code = parser.code, + .offset = offset, + .target = target, + }, + .inst = parsed_inst, + }; + + log.warn(" | emitting {}", .{page}); + + break :ptr &page.base; + }, + else => unreachable, + } + }; + + try parser.parsed.append(ptr); + } + + fn parsePageOff(parser: *Parser, reloc: macho.relocation_info) !void { + defer { + // Reset parser's addend state + parser.addend = null; + } + + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_PAGEOFF12); + assert(reloc.r_pcrel == 0); + assert(reloc.r_length == 2); + + const offset = @intCast(u32, reloc.r_address); + const inst = parser.code.*[offset..][0..4]; + + var op_kind: Relocation.PageOff.OpKind = undefined; + var parsed_inst: aarch64.Instruction = undefined; + if (isArithmeticOp(inst)) { + op_kind = .arithmetic; + parsed_inst = .{ .AddSubtractImmediate = mem.bytesToValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.AddSubtractImmediate, + ), inst) }; + } else { + op_kind = .load_store; + parsed_inst = .{ .LoadStoreRegister = mem.bytesToValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), inst) }; + } + const target = Relocation.Target.from_reloc(reloc); + + var page_off = try parser.allocator.create(Relocation.PageOff); + errdefer parser.allocator.destroy(page_off); + + page_off.* = .{ + .base = .{ + .@"type" = .page_off, + .code = parser.code, + .offset = offset, + .target = target, + }, + .op_kind = op_kind, + .inst = parsed_inst, + .addend = parser.addend, + }; + + log.warn(" | emitting {}", .{page_off}); + try parser.parsed.append(&page_off.base); + } + + fn parseGotLoadPageOff(parser: *Parser, reloc: macho.relocation_info) !void { + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12); + assert(reloc.r_pcrel == 0); + assert(reloc.r_length == 2); + + const offset = @intCast(u32, reloc.r_address); + const inst = parser.code.*[offset..][0..4]; + assert(!isArithmeticOp(inst)); + + const parsed_inst = mem.bytesToValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), inst); + assert(parsed_inst.size == 3); + + const target = Relocation.Target.from_reloc(reloc); + + var page_off = try parser.allocator.create(Relocation.GotPageOff); + errdefer parser.allocator.destroy(page_off); + + page_off.* = .{ + .base = .{ + .@"type" = .got_page_off, + .code = parser.code, + .offset = offset, + .target = target, + }, + .inst = .{ + .LoadStoreRegister = parsed_inst, + }, + }; + + log.warn(" | emitting {}", .{page_off}); + try parser.parsed.append(&page_off.base); + } + + fn parseTlvpLoadPageOff(parser: *Parser, reloc: macho.relocation_info) !void { + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12); + assert(reloc.r_pcrel == 0); + assert(reloc.r_length == 2); + + const RegInfo = struct { + rd: u5, + rn: u5, + size: u1, + }; + + const offset = @intCast(u32, reloc.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.AddSubtractImmediate, + ), 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.LoadStoreRegister, + ), inst); + break :parsed .{ + .rd = parsed_inst.rt, + .rn = parsed_inst.rn, + .size = @truncate(u1, parsed_inst.size), + }; + } + }; + + const target = Relocation.Target.from_reloc(reloc); + + var page_off = try parser.allocator.create(Relocation.TlvpPageOff); + errdefer parser.allocator.destroy(page_off); + + page_off.* = .{ + .base = .{ + .@"type" = .tlvp_page_off, + .code = parser.code, + .offset = @intCast(u32, reloc.r_address), + .target = target, + }, + .inst = .{ + .AddSubtractImmediate = .{ + .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.warn(" | emitting {}", .{page_off}); + try parser.parsed.append(&page_off.base); + } + + fn parseSubtractor(parser: *Parser, reloc: macho.relocation_info) !void { + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_SUBTRACTOR); + assert(reloc.r_pcrel == 0); + assert(parser.subtractor == null); + + parser.subtractor = Relocation.Target.from_reloc(reloc); + + // Verify SUBTRACTOR is followed by UNSIGNED. + if (parser.it.peek()) |tt| { + if (tt != .ARM64_RELOC_UNSIGNED) { + log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{tt}); + return error.UnexpectedRelocationType; + } + } else { + log.err("unexpected end of stream", .{}); + return error.UnexpectedEndOfStream; + } + } + + fn parseUnsigned(parser: *Parser, reloc: macho.relocation_info) !void { + defer { + // Reset parser's subtractor state + parser.subtractor = null; + } + + const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type); + assert(reloc_type == .ARM64_RELOC_UNSIGNED); + assert(reloc.r_pcrel == 0); + + var unsigned = try parser.allocator.create(Relocation.Unsigned); + errdefer parser.allocator.destroy(unsigned); + + const target = Relocation.Target.from_reloc(reloc); + const is_64bit: bool = switch (reloc.r_length) { + 3 => true, + 2 => false, + else => unreachable, + }; + const offset = @intCast(u32, reloc.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 = parser.code, + .offset = offset, + .target = target, + }, + .subtractor = parser.subtractor, + .is_64bit = is_64bit, + .addend = addend, + }; + + log.warn(" | emitting {}", .{unsigned}); + try parser.parsed.append(&unsigned.base); + } +}; + +fn isArithmeticOp(inst: *const [4]u8) callconv(.Inline) bool { + const group_decode = @truncate(u5, inst[3]); + return ((group_decode >> 2) == 4); +} |
