zld: simplify relocation parsing

This commit is contained in:
Jakub Konka 2021-07-06 19:09:49 +02:00
parent 15b85df3dd
commit dbd2eb7c7f
6 changed files with 753 additions and 1203 deletions

View File

@ -588,8 +588,6 @@ set(ZIG_STAGE2_SOURCES
"${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"

View File

@ -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 {

View File

@ -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);

View File

@ -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,
// 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(),
// };
// }
/// Addend embedded directly in the relocation slot
addend: i64,
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,
/// 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 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});
pub const Branch = struct {
arch: Arch,
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 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);
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,
// log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
// log.debug(" | {s} opcode", .{page_off.op_kind});
pub const base_type: Relocation.Type = .unsigned;
// 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;
// }
// 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;
// mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
// const result = if (args.subtractor) |subtractor|
// @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
// else
// @intCast(i64, args.target_addr) + addend;
},
.got => {
// const narrowed = @truncate(u12, args.target_addr);
// log.debug(" | calculated addend 0x{x}", .{addend});
// log.debug(" | calculated unsigned value 0x{x}", .{result});
// log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
// 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)),
// );
// }
// }
// var inst = page_off.inst;
// const offset = try math.divExact(u12, narrowed, 8);
// inst.load_store_register.offset = offset;
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});
// 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());
},
}
}
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: 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 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);
}

View File

@ -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);
}

View File

@ -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;
}
};