zld: thin out Relocation by not storing *TextBlock

this way we shave off 8 bytes per Relocation structure, and instead
we can pass the *TextBlock as args to resolve function.
This commit is contained in:
Jakub Konka
2021-07-15 18:25:10 +02:00
parent f8678c48ff
commit c47ce31071
2 changed files with 178 additions and 181 deletions

View File

@@ -246,7 +246,91 @@ pub const TextBlock = struct {
pub fn resolveRelocs(self: *TextBlock, zld: *Zld) !void {
for (self.relocs.items) |rel| {
try rel.resolve(zld);
log.debug("relocating {}", .{rel});
const source_addr = blk: {
const sym = zld.locals.items[self.local_sym_index];
break :blk sym.payload.regular.address + rel.offset;
};
const target_addr = blk: {
const is_via_got = switch (rel.payload) {
.pointer_to_got => true,
.page => |page| page.kind == .got,
.page_off => |page_off| page_off.kind == .got,
.load => |load| load.kind == .got,
else => 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 = rel.target.got_index orelse {
log.err("expected GOT entry for symbol '{s}'", .{zld.getString(rel.target.strx)});
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
};
break :blk got.addr + got_index * @sizeOf(u64);
}
switch (rel.target.payload) {
.regular => |reg| {
const is_tlv = is_tlv: {
const sym = zld.locals.items[self.local_sym_index];
const seg = zld.load_commands.items[sym.payload.regular.segment_id].Segment;
const sect = seg.sections.items[sym.payload.regular.section_id];
break :is_tlv sectionType(sect) == macho.S_THREAD_LOCAL_VARIABLES;
};
if (is_tlv) {
// For TLV relocations, the value specified as a relocation is the displacement from the
// TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first
// defined TLV template init section in the following order:
// * wrt to __thread_data if defined, then
// * wrt to __thread_bss
const seg = zld.load_commands.items[zld.data_segment_cmd_index.?].Segment;
const base_address = inner: {
if (zld.tlv_data_section_index) |i| {
break :inner seg.sections.items[i].addr;
} else if (zld.tlv_bss_section_index) |i| {
break :inner seg.sections.items[i].addr;
} else {
log.err("threadlocal variables present but no initializer sections found", .{});
log.err(" __thread_data not found", .{});
log.err(" __thread_bss not found", .{});
return error.FailedToResolveRelocationTarget;
}
};
break :blk reg.address - base_address;
}
break :blk reg.address;
},
.proxy => {
if (mem.eql(u8, zld.getString(rel.target.strx), "__tlv_bootstrap")) {
break :blk 0; // Dynamically bound by dyld.
}
const segment = zld.load_commands.items[zld.text_segment_cmd_index.?].Segment;
const stubs = segment.sections.items[zld.stubs_section_index.?];
const stubs_index = rel.target.stubs_index orelse {
// TODO verify in TextBlock that the symbol is indeed dynamically bound.
break :blk 0; // Dynamically bound by dyld.
};
break :blk stubs.addr + stubs_index * stubs.reserved2;
},
else => {
log.err("failed to resolve symbol '{s}' as a relocation target", .{
zld.getString(rel.target.strx),
});
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
},
}
};
log.debug(" | source_addr = 0x{x}", .{source_addr});
log.debug(" | target_addr = 0x{x}", .{target_addr});
try rel.resolve(self, source_addr, target_addr);
}
}

View File

@@ -20,9 +20,6 @@ pub const Relocation = struct {
/// 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,
@@ -36,6 +33,13 @@ pub const Relocation = struct {
load: Load,
},
const ResolveArgs = struct {
block: *TextBlock,
offset: u32,
source_addr: u64,
target_addr: u64,
};
pub const Unsigned = struct {
subtractor: ?*Symbol = null,
@@ -48,16 +52,16 @@ pub const Relocation = struct {
/// => * is unreachable
is_64bit: bool,
pub fn resolve(self: Unsigned, base: Relocation, _: u64, target_addr: u64) !void {
pub fn resolve(self: Unsigned, args: ResolveArgs) !void {
const result = if (self.subtractor) |subtractor|
@intCast(i64, target_addr) - @intCast(i64, subtractor.payload.regular.address) + self.addend
@intCast(i64, args.target_addr) - @intCast(i64, subtractor.payload.regular.address) + self.addend
else
@intCast(i64, target_addr) + self.addend;
@intCast(i64, args.target_addr) + self.addend;
if (self.is_64bit) {
mem.writeIntLittle(u64, base.block.code[base.offset..][0..8], @bitCast(u64, result));
mem.writeIntLittle(u64, args.block.code[args.offset..][0..8], @bitCast(u64, result));
} else {
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @truncate(u32, @bitCast(u64, result)));
mem.writeIntLittle(u32, args.block.code[args.offset..][0..4], @truncate(u32, @bitCast(u64, result)));
}
}
@@ -78,25 +82,29 @@ pub const Relocation = struct {
pub const Branch = struct {
arch: Arch,
pub fn resolve(self: Branch, base: Relocation, source_addr: u64, target_addr: u64) !void {
pub fn resolve(self: Branch, args: ResolveArgs) !void {
switch (self.arch) {
.aarch64 => {
const displacement = try math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr));
const displacement = try math.cast(
i28,
@intCast(i64, args.target_addr) - @intCast(i64, args.source_addr),
);
const code = args.block.code[args.offset..][0..4];
var inst = aarch64.Instruction{
.unconditional_branch_immediate = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.unconditional_branch_immediate,
),
base.block.code[base.offset..][0..4],
),
.unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.unconditional_branch_immediate,
), code),
};
inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
mem.writeIntLittle(u32, code, 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));
const displacement = try math.cast(
i32,
@intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4,
);
mem.writeIntLittle(u32, args.block.code[args.offset..][0..4], @bitCast(u32, displacement));
},
else => return error.UnsupportedCpuArchitecture,
}
@@ -118,25 +126,23 @@ pub const Relocation = struct {
},
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);
pub fn resolve(self: Page, args: ResolveArgs) !void {
const target_addr = if (self.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));
const code = args.block.code[args.offset..][0..4];
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],
),
.pc_relative_address = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.pc_relative_address,
), code),
};
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());
mem.writeIntLittle(u32, code, inst.toU32());
}
pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
@@ -173,35 +179,31 @@ pub const Relocation = struct {
load,
};
pub fn resolve(self: PageOff, base: Relocation, _: u64, target_addr: u64) !void {
pub fn resolve(self: PageOff, args: ResolveArgs) !void {
const code = args.block.code[args.offset..][0..4];
switch (self.kind) {
.page => {
const actual_target_addr = if (self.addend) |addend| target_addr + addend else target_addr;
const narrowed = @truncate(u12, actual_target_addr);
const target_addr = if (self.addend) |addend| args.target_addr + addend else args.target_addr;
const narrowed = @truncate(u12, target_addr);
const op_kind = self.op_kind orelse unreachable;
var inst: aarch64.Instruction = blk: {
switch (op_kind) {
.arithmetic => {
break :blk .{
.add_subtract_immediate = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
),
base.block.code[base.offset..][0..4],
),
.add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
), code),
};
},
.load => {
break :blk .{
.load_store_register = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
),
base.block.code[base.offset..][0..4],
),
.load_store_register = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
), code),
};
},
}
@@ -226,22 +228,19 @@ pub const Relocation = struct {
inst.load_store_register.offset = offset;
}
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
mem.writeIntLittle(u32, code, inst.toU32());
},
.got => {
const narrowed = @truncate(u12, target_addr);
const narrowed = @truncate(u12, args.target_addr);
var inst: aarch64.Instruction = .{
.load_store_register = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
),
base.block.code[base.offset..][0..4],
),
.load_store_register = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
), code),
};
const offset = try math.divExact(u12, narrowed, 8);
inst.load_store_register.offset = offset;
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
mem.writeIntLittle(u32, code, inst.toU32());
},
.tlvp => {
const RegInfo = struct {
@@ -250,27 +249,21 @@ pub const Relocation = struct {
size: u1,
};
const reg_info: RegInfo = blk: {
if (isArithmeticOp(base.block.code[base.offset..][0..4])) {
const inst = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
),
base.block.code[base.offset..][0..4],
);
if (isArithmeticOp(code)) {
const inst = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
), code);
break :blk .{
.rd = inst.rd,
.rn = inst.rn,
.size = inst.sf,
};
} else {
const inst = mem.bytesToValue(
meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
),
base.block.code[base.offset..][0..4],
);
const inst = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
), code);
break :blk .{
.rd = inst.rt,
.rn = inst.rn,
@@ -278,7 +271,7 @@ pub const Relocation = struct {
};
}
};
const narrowed = @truncate(u12, target_addr);
const narrowed = @truncate(u12, args.target_addr);
var inst = aarch64.Instruction{
.add_subtract_immediate = .{
.rd = reg_info.rd,
@@ -290,7 +283,7 @@ pub const Relocation = struct {
.sf = reg_info.size,
},
};
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], inst.toU32());
mem.writeIntLittle(u32, code, inst.toU32());
},
}
}
@@ -319,9 +312,9 @@ pub const Relocation = struct {
};
pub const PointerToGot = struct {
pub fn resolve(_: 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 resolve(_: PointerToGot, args: ResolveArgs) !void {
const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
mem.writeIntLittle(u32, args.block.code[args.offset..][0..4], @bitCast(u32, result));
}
pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
@@ -336,13 +329,13 @@ pub const Relocation = struct {
addend: i64,
correction: i4,
pub fn resolve(self: Signed, base: Relocation, source_addr: u64, target_addr: u64) !void {
const actual_target_addr = @intCast(i64, target_addr) + self.addend;
pub fn resolve(self: Signed, args: ResolveArgs) !void {
const target_addr = @intCast(i64, args.target_addr) + self.addend;
const displacement = try math.cast(
i32,
actual_target_addr - @intCast(i64, source_addr) - self.correction - 4,
target_addr - @intCast(i64, args.source_addr) - self.correction - 4,
);
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement));
mem.writeIntLittle(u32, args.block.code[args.offset..][0..4], @bitCast(u32, displacement));
}
pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
@@ -362,17 +355,17 @@ pub const Relocation = struct {
},
addend: ?i32 = null,
pub fn resolve(self: Load, base: Relocation, source_addr: u64, target_addr: u64) !void {
pub fn resolve(self: Load, args: ResolveArgs) !void {
if (self.kind == .tlvp) {
// We need to rewrite the opcode from movq to leaq.
base.block.code[base.offset - 2] = 0x8d;
args.block.code[args.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,
@intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + addend,
);
mem.writeIntLittle(u32, base.block.code[base.offset..][0..4], @bitCast(u32, displacement));
mem.writeIntLittle(u32, args.block.code[args.offset..][0..4], @bitCast(u32, displacement));
}
pub fn format(self: Load, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
@@ -387,106 +380,27 @@ pub const Relocation = struct {
}
};
pub fn resolve(self: Relocation, zld: *Zld) !void {
log.debug("relocating {}", .{self});
const source_addr = blk: {
const sym = zld.locals.items[self.block.local_sym_index];
break :blk sym.payload.regular.address + self.offset;
pub fn resolve(self: Relocation, block: *TextBlock, source_addr: u64, target_addr: u64) !void {
const args = ResolveArgs{
.block = block,
.offset = self.offset,
.source_addr = source_addr,
.target_addr = target_addr,
};
const target_addr = blk: {
const is_via_got = switch (self.payload) {
.pointer_to_got => true,
.page => |page| page.kind == .got,
.page_off => |page_off| page_off.kind == .got,
.load => |load| load.kind == .got,
else => 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}'", .{zld.getString(self.target.strx)});
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| {
const is_tlv = is_tlv: {
const sym = zld.locals.items[self.block.local_sym_index];
const seg = zld.load_commands.items[sym.payload.regular.segment_id].Segment;
const sect = seg.sections.items[sym.payload.regular.section_id];
break :is_tlv commands.sectionType(sect) == macho.S_THREAD_LOCAL_VARIABLES;
};
if (is_tlv) {
// For TLV relocations, the value specified as a relocation is the displacement from the
// TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first
// defined TLV template init section in the following order:
// * wrt to __thread_data if defined, then
// * wrt to __thread_bss
const seg = zld.load_commands.items[zld.data_segment_cmd_index.?].Segment;
const base_address = inner: {
if (zld.tlv_data_section_index) |i| {
break :inner seg.sections.items[i].addr;
} else if (zld.tlv_bss_section_index) |i| {
break :inner seg.sections.items[i].addr;
} else {
log.err("threadlocal variables present but no initializer sections found", .{});
log.err(" __thread_data not found", .{});
log.err(" __thread_bss not found", .{});
return error.FailedToResolveRelocationTarget;
}
};
break :blk reg.address - base_address;
}
break :blk reg.address;
},
.proxy => {
if (mem.eql(u8, zld.getString(self.target.strx), "__tlv_bootstrap")) {
break :blk 0; // Dynamically bound by dyld.
}
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 {
// TODO verify in TextBlock that the symbol is indeed dynamically bound.
break :blk 0; // Dynamically bound by dyld.
};
break :blk stubs.addr + stubs_index * stubs.reserved2;
},
else => {
log.err("failed to resolve symbol '{s}' as a relocation target", .{
zld.getString(self.target.strx),
});
log.err(" this is an internal linker error", .{});
return error.FailedToResolveRelocationTarget;
},
}
};
log.debug(" | source_addr = 0x{x}", .{source_addr});
log.debug(" | target_addr = 0x{x}", .{target_addr});
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),
.unsigned => |unsigned| try unsigned.resolve(args),
.branch => |branch| try branch.resolve(args),
.page => |page| try page.resolve(args),
.page_off => |page_off| try page_off.resolve(args),
.pointer_to_got => |pointer_to_got| try pointer_to_got.resolve(args),
.signed => |signed| try signed.resolve(args),
.load => |load| try load.resolve(args),
}
}
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) {
@@ -713,7 +627,6 @@ pub const Parser = struct {
return Relocation{
.offset = offset,
.target = target,
.block = self.block,
.payload = undefined,
};
}