diff --git a/CMakeLists.txt b/CMakeLists.txt index fd49c04e60..ae48414d8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/arch/wasm/Emit.zig" "${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Disassembler.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Emit.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Encoding.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Mir.zig" @@ -584,6 +585,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/Coff/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" + "${CMAKE_SOURCE_DIR}/src/link/Elf/Archive.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig" diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 2c6e3f5a7b..2788bb4055 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -4316,7 +4316,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl); const sym = elf_file.symbol(sym_index); - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file))); try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr }); } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 937d4aa1dd..3e15b544a7 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -4296,7 +4296,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (self.bin_file.cast(link.File.Elf)) |elf_file| { const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl); const sym = elf_file.symbol(sym_index); - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file))); try self.genSetReg(Type.usize, .lr, .{ .memory = got_addr }); } else if (self.bin_file.cast(link.File.MachO)) |_| { diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index d3f162ae0f..66e5d5d965 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1749,7 +1749,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier .func => |func| { const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl); const sym = elf_file.symbol(sym_index); - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file))); try self.genSetReg(Type.usize, .ra, .{ .memory = got_addr }); _ = try self.addInst(.{ diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index df1a7cf970..e6da6d24bb 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -1351,7 +1351,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl); const sym = elf_file.symbol(sym_index); - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); break :blk @as(u32, @intCast(sym.gotAddress(elf_file))); } else unreachable; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index e535894604..15bf51489a 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -8157,7 +8157,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier const sym_index = try elf_file.getOrCreateMetadataForDecl(owner_decl); const sym = elf_file.symbol(sym_index); sym.flags.needs_got = true; - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); const got_addr = sym.gotAddress(elf_file); try self.asmMemory(.{ ._, .call }, Memory.sib(.qword, .{ .base = .{ .reg = .ds }, @@ -10236,7 +10236,7 @@ fn genLazySymbolRef( return self.fail("{s} creating lazy symbol", .{@errorName(err)}); const sym = elf_file.symbol(sym_index); sym.flags.needs_got = true; - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); const got_addr = sym.gotAddress(elf_file); const got_mem = Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(got_addr) }); diff --git a/src/arch/x86_64/Disassembler.zig b/src/arch/x86_64/Disassembler.zig new file mode 100644 index 0000000000..a0d5dea29c --- /dev/null +++ b/src/arch/x86_64/Disassembler.zig @@ -0,0 +1,475 @@ +const Disassembler = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const math = std.math; + +const bits = @import("bits.zig"); +const encoder = @import("encoder.zig"); + +const Encoding = @import("Encoding.zig"); +const Immediate = bits.Immediate; +const Instruction = encoder.Instruction; +const LegacyPrefixes = encoder.LegacyPrefixes; +const Memory = bits.Memory; +const Register = bits.Register; +const Rex = encoder.Rex; + +pub const Error = error{ + EndOfStream, + LegacyPrefixAfterRex, + UnknownOpcode, + Overflow, + Todo, +}; + +code: []const u8, +pos: usize = 0, + +pub fn init(code: []const u8) Disassembler { + return .{ .code = code }; +} + +pub fn next(dis: *Disassembler) Error!?Instruction { + const prefixes = dis.parsePrefixes() catch |err| switch (err) { + error.EndOfStream => return null, + else => |e| return e, + }; + + const enc = try dis.parseEncoding(prefixes) orelse return error.UnknownOpcode; + switch (enc.data.op_en) { + .np => return inst(enc, .{}), + .d, .i => { + const imm = try dis.parseImm(enc.data.ops[0]); + return inst(enc, .{ + .op1 = .{ .imm = imm }, + }); + }, + .zi => { + const imm = try dis.parseImm(enc.data.ops[1]); + return inst(enc, .{ + .op1 = .{ .reg = Register.rax.toBitSize(enc.data.ops[0].regBitSize()) }, + .op2 = .{ .imm = imm }, + }); + }, + .o, .oi => { + const reg_low_enc = @as(u3, @truncate(dis.code[dis.pos - 1])); + const op2: Instruction.Operand = if (enc.data.op_en == .oi) .{ + .imm = try dis.parseImm(enc.data.ops[1]), + } else .none; + return inst(enc, .{ + .op1 = .{ .reg = parseGpRegister(reg_low_enc, prefixes.rex.b, prefixes.rex, enc.data.ops[0].regBitSize()) }, + .op2 = op2, + }); + }, + .m, .mi, .m1, .mc => { + const modrm = try dis.parseModRmByte(); + const act_enc = Encoding.findByOpcode(enc.opcode(), .{ + .legacy = prefixes.legacy, + .rex = prefixes.rex, + }, modrm.op1) orelse return error.UnknownOpcode; + const sib = if (modrm.sib()) try dis.parseSibByte() else null; + + if (modrm.direct()) { + const op2: Instruction.Operand = switch (act_enc.data.op_en) { + .mi => .{ .imm = try dis.parseImm(act_enc.data.ops[1]) }, + .m1 => .{ .imm = Immediate.u(1) }, + .mc => .{ .reg = .cl }, + .m => .none, + else => unreachable, + }; + return inst(act_enc, .{ + .op1 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, act_enc.data.ops[0].regBitSize()) }, + .op2 = op2, + }); + } + + const disp = try dis.parseDisplacement(modrm, sib); + const op2: Instruction.Operand = switch (act_enc.data.op_en) { + .mi => .{ .imm = try dis.parseImm(act_enc.data.ops[1]) }, + .m1 => .{ .imm = Immediate.u(1) }, + .mc => .{ .reg = .cl }, + .m => .none, + else => unreachable, + }; + + if (modrm.rip()) { + return inst(act_enc, .{ + .op1 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(act_enc.data.ops[0].memBitSize()), disp) }, + .op2 = op2, + }); + } + + const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null; + const base = if (sib) |info| + info.baseReg(modrm, prefixes) + else + parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64); + return inst(act_enc, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(act_enc.data.ops[0].memBitSize()), .{ + .base = if (base) |base_reg| .{ .reg = base_reg } else .none, + .scale_index = scale_index, + .disp = disp, + }) }, + .op2 = op2, + }); + }, + .fd => { + const seg = segmentRegister(prefixes.legacy); + const offset = try dis.parseOffset(); + return inst(enc, .{ + .op1 = .{ .reg = Register.rax.toBitSize(enc.data.ops[0].regBitSize()) }, + .op2 = .{ .mem = Memory.moffs(seg, offset) }, + }); + }, + .td => { + const seg = segmentRegister(prefixes.legacy); + const offset = try dis.parseOffset(); + return inst(enc, .{ + .op1 = .{ .mem = Memory.moffs(seg, offset) }, + .op2 = .{ .reg = Register.rax.toBitSize(enc.data.ops[1].regBitSize()) }, + }); + }, + .mr, .mri, .mrc => { + const modrm = try dis.parseModRmByte(); + const sib = if (modrm.sib()) try dis.parseSibByte() else null; + const src_bit_size = enc.data.ops[1].regBitSize(); + + if (modrm.direct()) { + return inst(enc, .{ + .op1 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, enc.data.ops[0].regBitSize()) }, + .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.x, prefixes.rex, src_bit_size) }, + }); + } + + const dst_bit_size = enc.data.ops[0].memBitSize(); + const disp = try dis.parseDisplacement(modrm, sib); + const op3: Instruction.Operand = switch (enc.data.op_en) { + .mri => .{ .imm = try dis.parseImm(enc.data.ops[2]) }, + .mrc => .{ .reg = .cl }, + .mr => .none, + else => unreachable, + }; + + if (modrm.rip()) { + return inst(enc, .{ + .op1 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(dst_bit_size), disp) }, + .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, src_bit_size) }, + .op3 = op3, + }); + } + + const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null; + const base = if (sib) |info| + info.baseReg(modrm, prefixes) + else + parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64); + return inst(enc, .{ + .op1 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(dst_bit_size), .{ + .base = if (base) |base_reg| .{ .reg = base_reg } else .none, + .scale_index = scale_index, + .disp = disp, + }) }, + .op2 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, src_bit_size) }, + .op3 = op3, + }); + }, + .rm, .rmi => { + const modrm = try dis.parseModRmByte(); + const sib = if (modrm.sib()) try dis.parseSibByte() else null; + const dst_bit_size = enc.data.ops[0].regBitSize(); + + if (modrm.direct()) { + const op3: Instruction.Operand = switch (enc.data.op_en) { + .rm => .none, + .rmi => .{ .imm = try dis.parseImm(enc.data.ops[2]) }, + else => unreachable, + }; + return inst(enc, .{ + .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.x, prefixes.rex, dst_bit_size) }, + .op2 = .{ .reg = parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, enc.data.ops[1].regBitSize()) }, + .op3 = op3, + }); + } + + const src_bit_size = if (enc.data.ops[1] == .m) dst_bit_size else enc.data.ops[1].memBitSize(); + const disp = try dis.parseDisplacement(modrm, sib); + const op3: Instruction.Operand = switch (enc.data.op_en) { + .rmi => .{ .imm = try dis.parseImm(enc.data.ops[2]) }, + .rm => .none, + else => unreachable, + }; + + if (modrm.rip()) { + return inst(enc, .{ + .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, dst_bit_size) }, + .op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(src_bit_size), disp) }, + .op3 = op3, + }); + } + + const scale_index = if (sib) |info| info.scaleIndex(prefixes.rex) else null; + const base = if (sib) |info| + info.baseReg(modrm, prefixes) + else + parseGpRegister(modrm.op2, prefixes.rex.b, prefixes.rex, 64); + return inst(enc, .{ + .op1 = .{ .reg = parseGpRegister(modrm.op1, prefixes.rex.r, prefixes.rex, dst_bit_size) }, + .op2 = .{ .mem = Memory.sib(Memory.PtrSize.fromBitSize(src_bit_size), .{ + .base = if (base) |base_reg| .{ .reg = base_reg } else .none, + .scale_index = scale_index, + .disp = disp, + }) }, + .op3 = op3, + }); + }, + .rm0, .vmi, .rvm, .rvmr, .rvmi, .mvr => unreachable, // TODO + } +} + +fn inst(encoding: Encoding, args: struct { + prefix: Instruction.Prefix = .none, + op1: Instruction.Operand = .none, + op2: Instruction.Operand = .none, + op3: Instruction.Operand = .none, + op4: Instruction.Operand = .none, +}) Instruction { + var i = Instruction{ .encoding = encoding, .prefix = args.prefix, .ops = .{ + args.op1, + args.op2, + args.op3, + args.op4, + } }; + return i; +} + +const Prefixes = struct { + legacy: LegacyPrefixes = .{}, + rex: Rex = .{}, + // TODO add support for VEX prefix +}; + +fn parsePrefixes(dis: *Disassembler) !Prefixes { + const rex_prefix_mask: u4 = 0b0100; + var stream = std.io.fixedBufferStream(dis.code[dis.pos..]); + const reader = stream.reader(); + + var res: Prefixes = .{}; + + while (true) { + const next_byte = try reader.readByte(); + dis.pos += 1; + + switch (next_byte) { + 0xf0, 0xf2, 0xf3, 0x2e, 0x36, 0x26, 0x64, 0x65, 0x3e, 0x66, 0x67 => { + // Legacy prefix + if (res.rex.present) return error.LegacyPrefixAfterRex; + switch (next_byte) { + 0xf0 => res.legacy.prefix_f0 = true, + 0xf2 => res.legacy.prefix_f2 = true, + 0xf3 => res.legacy.prefix_f3 = true, + 0x2e => res.legacy.prefix_2e = true, + 0x36 => res.legacy.prefix_36 = true, + 0x26 => res.legacy.prefix_26 = true, + 0x64 => res.legacy.prefix_64 = true, + 0x65 => res.legacy.prefix_65 = true, + 0x3e => res.legacy.prefix_3e = true, + 0x66 => res.legacy.prefix_66 = true, + 0x67 => res.legacy.prefix_67 = true, + else => unreachable, + } + }, + else => { + if (rex_prefix_mask == @as(u4, @truncate(next_byte >> 4))) { + // REX prefix + res.rex.w = next_byte & 0b1000 != 0; + res.rex.r = next_byte & 0b100 != 0; + res.rex.x = next_byte & 0b10 != 0; + res.rex.b = next_byte & 0b1 != 0; + res.rex.present = true; + continue; + } + + // TODO VEX prefix + + dis.pos -= 1; + break; + }, + } + } + + return res; +} + +fn parseEncoding(dis: *Disassembler, prefixes: Prefixes) !?Encoding { + const o_mask: u8 = 0b1111_1000; + + var opcode: [3]u8 = .{ 0, 0, 0 }; + var stream = std.io.fixedBufferStream(dis.code[dis.pos..]); + const reader = stream.reader(); + + comptime var opc_count = 0; + inline while (opc_count < 3) : (opc_count += 1) { + const byte = try reader.readByte(); + opcode[opc_count] = byte; + dis.pos += 1; + + if (byte == 0x0f) { + // Multi-byte opcode + } else if (opc_count > 0) { + // Multi-byte opcode + if (Encoding.findByOpcode(opcode[0 .. opc_count + 1], .{ + .legacy = prefixes.legacy, + .rex = prefixes.rex, + }, null)) |mnemonic| { + return mnemonic; + } + } else { + // Single-byte opcode + if (Encoding.findByOpcode(opcode[0..1], .{ + .legacy = prefixes.legacy, + .rex = prefixes.rex, + }, null)) |mnemonic| { + return mnemonic; + } else { + // Try O* encoding + return Encoding.findByOpcode(&.{opcode[0] & o_mask}, .{ + .legacy = prefixes.legacy, + .rex = prefixes.rex, + }, null); + } + } + } + return null; +} + +fn parseGpRegister(low_enc: u3, is_extended: bool, rex: Rex, bit_size: u64) Register { + const reg_id: u4 = @as(u4, @intCast(@intFromBool(is_extended))) << 3 | low_enc; + const reg = @as(Register, @enumFromInt(reg_id)).toBitSize(bit_size); + return switch (reg) { + .spl => if (rex.present or rex.isSet()) .spl else .ah, + .dil => if (rex.present or rex.isSet()) .dil else .bh, + .bpl => if (rex.present or rex.isSet()) .bpl else .ch, + .sil => if (rex.present or rex.isSet()) .sil else .dh, + else => reg, + }; +} + +fn parseImm(dis: *Disassembler, kind: Encoding.Op) !Immediate { + var stream = std.io.fixedBufferStream(dis.code[dis.pos..]); + var creader = std.io.countingReader(stream.reader()); + const reader = creader.reader(); + const imm = switch (kind) { + .imm8s, .rel8 => Immediate.s(try reader.readInt(i8, .Little)), + .imm16s, .rel16 => Immediate.s(try reader.readInt(i16, .Little)), + .imm32s, .rel32 => Immediate.s(try reader.readInt(i32, .Little)), + .imm8 => Immediate.u(try reader.readInt(u8, .Little)), + .imm16 => Immediate.u(try reader.readInt(u16, .Little)), + .imm32 => Immediate.u(try reader.readInt(u32, .Little)), + .imm64 => Immediate.u(try reader.readInt(u64, .Little)), + else => unreachable, + }; + dis.pos += std.math.cast(usize, creader.bytes_read) orelse return error.Overflow; + return imm; +} + +fn parseOffset(dis: *Disassembler) !u64 { + var stream = std.io.fixedBufferStream(dis.code[dis.pos..]); + const reader = stream.reader(); + const offset = try reader.readInt(u64, .Little); + dis.pos += 8; + return offset; +} + +const ModRm = packed struct { + mod: u2, + op1: u3, + op2: u3, + + inline fn direct(self: ModRm) bool { + return self.mod == 0b11; + } + + inline fn rip(self: ModRm) bool { + return self.mod == 0 and self.op2 == 0b101; + } + + inline fn sib(self: ModRm) bool { + return !self.direct() and self.op2 == 0b100; + } +}; + +fn parseModRmByte(dis: *Disassembler) !ModRm { + if (dis.code[dis.pos..].len == 0) return error.EndOfStream; + const modrm_byte = dis.code[dis.pos]; + dis.pos += 1; + const mod: u2 = @as(u2, @truncate(modrm_byte >> 6)); + const op1: u3 = @as(u3, @truncate(modrm_byte >> 3)); + const op2: u3 = @as(u3, @truncate(modrm_byte)); + return ModRm{ .mod = mod, .op1 = op1, .op2 = op2 }; +} + +fn segmentRegister(prefixes: LegacyPrefixes) Register { + if (prefixes.prefix_2e) return .cs; + if (prefixes.prefix_36) return .ss; + if (prefixes.prefix_26) return .es; + if (prefixes.prefix_64) return .fs; + if (prefixes.prefix_65) return .gs; + return .ds; +} + +const Sib = packed struct { + scale: u2, + index: u3, + base: u3, + + fn scaleIndex(self: Sib, rex: Rex) ?Memory.ScaleIndex { + if (self.index == 0b100 and !rex.x) return null; + return .{ + .scale = @as(u4, 1) << self.scale, + .index = parseGpRegister(self.index, rex.x, rex, 64), + }; + } + + fn baseReg(self: Sib, modrm: ModRm, prefixes: Prefixes) ?Register { + if (self.base == 0b101 and modrm.mod == 0) { + if (self.scaleIndex(prefixes.rex)) |_| return null; + return segmentRegister(prefixes.legacy); + } + return parseGpRegister(self.base, prefixes.rex.b, prefixes.rex, 64); + } +}; + +fn parseSibByte(dis: *Disassembler) !Sib { + if (dis.code[dis.pos..].len == 0) return error.EndOfStream; + const sib_byte = dis.code[dis.pos]; + dis.pos += 1; + const scale: u2 = @as(u2, @truncate(sib_byte >> 6)); + const index: u3 = @as(u3, @truncate(sib_byte >> 3)); + const base: u3 = @as(u3, @truncate(sib_byte)); + return Sib{ .scale = scale, .index = index, .base = base }; +} + +fn parseDisplacement(dis: *Disassembler, modrm: ModRm, sib: ?Sib) !i32 { + var stream = std.io.fixedBufferStream(dis.code[dis.pos..]); + var creader = std.io.countingReader(stream.reader()); + const reader = creader.reader(); + const disp = disp: { + if (sib) |info| { + if (info.base == 0b101 and modrm.mod == 0) { + break :disp try reader.readInt(i32, .Little); + } + } + if (modrm.rip()) { + break :disp try reader.readInt(i32, .Little); + } + break :disp switch (modrm.mod) { + 0b00 => 0, + 0b01 => try reader.readInt(i8, .Little), + 0b10 => try reader.readInt(i32, .Little), + 0b11 => unreachable, + }; + }; + dis.pos += std.math.cast(usize, creader.bytes_read) orelse return error.Overflow; + return disp; +} diff --git a/src/codegen.zig b/src/codegen.zig index 3d86e4b6bc..a2d0f225bc 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -861,7 +861,7 @@ fn genDeclRef( const sym_index = try elf_file.getOrCreateMetadataForDecl(decl_index); const sym = elf_file.symbol(sym_index); sym.flags.needs_got = true; - _ = try sym.getOrCreateGotEntry(elf_file); + _ = try sym.getOrCreateGotEntry(sym_index, elf_file); return GenResult.mcv(.{ .memory = sym.gotAddress(elf_file) }); } else if (bin_file.cast(link.File.MachO)) |macho_file| { const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 16b7706f7a..b12d24745b 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -40,6 +40,8 @@ phdr_got_index: ?u16 = null, phdr_load_ro_index: ?u16 = null, /// The index into the program headers of a PT_LOAD program header with Write flag phdr_load_rw_index: ?u16 = null, +/// The index into the program headers of a PT_LOAD program header with zerofill data. +phdr_load_zerofill_index: ?u16 = null, entry_addr: ?u64 = null, page_size: u32, @@ -56,6 +58,7 @@ got: GotSection = .{}, text_section_index: ?u16 = null, rodata_section_index: ?u16 = null, data_section_index: ?u16 = null, +bss_section_index: ?u16 = null, eh_frame_section_index: ?u16 = null, eh_frame_hdr_section_index: ?u16 = null, dynamic_section_index: ?u16 = null, @@ -532,6 +535,26 @@ pub fn populateMissingMetadata(self: *Elf) !void { self.phdr_table_dirty = true; } + if (self.phdr_load_zerofill_index == null) { + self.phdr_load_zerofill_index = @as(u16, @intCast(self.phdrs.items.len)); + const p_align = if (self.base.options.target.os.tag == .linux) self.page_size else @as(u16, ptr_size); + const off = self.phdrs.items[self.phdr_load_rw_index.?].p_offset; + log.debug("found PT_LOAD zerofill free space 0x{x} to 0x{x}", .{ off, off }); + // TODO Same as for GOT + const addr: u32 = if (self.base.options.target.ptrBitWidth() >= 32) 0x14000000 else 0xf000; + try self.phdrs.append(gpa, .{ + .p_type = elf.PT_LOAD, + .p_offset = off, + .p_filesz = 0, + .p_vaddr = addr, + .p_paddr = addr, + .p_memsz = 0, + .p_align = p_align, + .p_flags = elf.PF_R | elf.PF_W, + }); + self.phdr_table_dirty = true; + } + if (self.shstrtab_section_index == null) { self.shstrtab_section_index = @as(u16, @intCast(self.shdrs.items.len)); assert(self.shstrtab.buffer.items.len == 0); @@ -655,6 +678,26 @@ pub fn populateMissingMetadata(self: *Elf) !void { self.shdr_table_dirty = true; } + if (self.bss_section_index == null) { + self.bss_section_index = @as(u16, @intCast(self.shdrs.items.len)); + const phdr = &self.phdrs.items[self.phdr_load_zerofill_index.?]; + try self.shdrs.append(gpa, .{ + .sh_name = try self.shstrtab.insert(gpa, ".bss"), + .sh_type = elf.SHT_NOBITS, + .sh_flags = elf.SHF_WRITE | elf.SHF_ALLOC, + .sh_addr = phdr.p_vaddr, + .sh_offset = phdr.p_offset, + .sh_size = phdr.p_filesz, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = @as(u16, ptr_size), + .sh_entsize = 0, + }); + try self.phdr_to_shdr_table.putNoClobber(gpa, self.bss_section_index.?, self.phdr_load_zerofill_index.?); + try self.last_atom_and_free_list_table.putNoClobber(gpa, self.bss_section_index.?, .{}); + self.shdr_table_dirty = true; + } + if (self.symtab_section_index == null) { self.symtab_section_index = @as(u16, @intCast(self.shdrs.items.len)); const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); @@ -868,8 +911,9 @@ pub fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void { const shdr = &self.shdrs.items[shdr_index]; const phdr_index = self.phdr_to_shdr_table.get(shdr_index).?; const phdr = &self.phdrs.items[phdr_index]; + const is_zerofill = shdr.sh_type == elf.SHT_NOBITS; - if (needed_size > self.allocatedSize(shdr.sh_offset)) { + if (needed_size > self.allocatedSize(shdr.sh_offset) and !is_zerofill) { // Must move the entire section. const new_offset = self.findFreeSpace(needed_size, self.page_size); const existing_size = if (self.last_atom_and_free_list_table.get(shdr_index)) |meta| blk: { @@ -893,7 +937,10 @@ pub fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void { shdr.sh_size = needed_size; phdr.p_memsz = needed_size; - phdr.p_filesz = needed_size; + + if (!is_zerofill) { + phdr.p_filesz = needed_size; + } self.markDirty(shdr_index, phdr_index); } @@ -1005,13 +1052,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - const compiler_rt_path: ?[]const u8 = blk: { - if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; - if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; - break :blk null; - }; - _ = compiler_rt_path; - // Here we will parse input positional and library files (if referenced). // This will roughly match in any linker backend we support. var positionals = std.ArrayList(Compilation.LinkObject).init(arena); @@ -1037,6 +1077,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try positionals.append(.{ .path = key.status.success.object_path }); } + const compiler_rt_path: ?[]const u8 = blk: { + if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; + if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + if (compiler_rt_path) |path| { + try positionals.append(.{ .path = path }); + } + for (positionals.items) |obj| { const in_file = try std.fs.cwd().openFile(obj.path, .{}); defer in_file.close(); @@ -1093,7 +1142,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node // input Object files. // Any qualifing unresolved symbol will be upgraded to an absolute, weak // symbol for potential resolution at load-time. - self.resolveSymbols(); + try self.resolveSymbols(); self.markImportsExports(); self.claimUnresolved(); @@ -1356,6 +1405,7 @@ const ParseError = error{ EndOfStream, FileSystem, NotSupported, + InvalidCharacter, } || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError; fn parsePositional( @@ -1367,10 +1417,32 @@ fn parsePositional( ) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - _ = must_link; if (Object.isObject(in_file)) { try self.parseObject(in_file, path, ctx); + } else { + try self.parseLibrary(in_file, path, .{ + .path = null, + .needed = false, + .weak = false, + }, must_link, ctx); + } +} + +fn parseLibrary( + self: *Elf, + in_file: std.fs.File, + path: []const u8, + lib: link.SystemLib, + must_link: bool, + ctx: *ParseErrorCtx, +) ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + _ = lib; + + if (Archive.isArchive(in_file)) { + try self.parseArchive(in_file, path, must_link, ctx); } else return error.UnknownFileType; } @@ -1395,15 +1467,109 @@ fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseEr if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch; } -fn resolveSymbols(self: *Elf) void { - if (self.zig_module_index) |index| { - const zig_module = self.file(index).?.zig_module; - zig_module.resolveSymbols(self); +fn parseArchive( + self: *Elf, + in_file: std.fs.File, + path: []const u8, + must_link: bool, + ctx: *ParseErrorCtx, +) ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); + var archive = Archive{ .path = path, .data = data }; + defer archive.deinit(gpa); + try archive.parse(self); + + for (archive.objects.items) |extracted| { + const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); + self.files.set(index, .{ .object = extracted }); + const object = &self.files.items(.data)[index].object; + object.index = index; + object.alive = must_link; + try object.parse(self); + try self.objects.append(gpa, index); + + ctx.detected_cpu_arch = object.header.?.e_machine.toTargetCpuArch().?; + if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch; + } +} + +/// When resolving symbols, we approach the problem similarly to `mold`. +/// 1. Resolve symbols across all objects (including those preemptively extracted archives). +/// 2. Resolve symbols across all shared objects. +/// 3. Mark live objects (see `Elf.markLive`) +/// 4. Reset state of all resolved globals since we will redo this bit on the pruned set. +/// 5. Remove references to dead objects/shared objects +/// 6. Re-run symbol resolution on pruned objects and shared objects sets. +fn resolveSymbols(self: *Elf) error{Overflow}!void { + // Resolve symbols in the ZigModule. For now, we assume that it's always live. + if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self); + // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). + for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); + + // Mark live objects. + self.markLive(); + + // Reset state of all globals after marking live objects. + if (self.zig_module_index) |index| self.file(index).?.resetGlobals(self); + for (self.objects.items) |index| self.file(index).?.resetGlobals(self); + + // Prune dead objects and shared objects. + var i: usize = 0; + while (i < self.objects.items.len) { + const index = self.objects.items[i]; + if (!self.file(index).?.isAlive()) { + _ = self.objects.orderedRemove(i); + } else i += 1; + } + + // Dedup comdat groups. + for (self.objects.items) |index| { + const object = self.file(index).?.object; + for (object.comdat_groups.items) |cg_index| { + const cg = self.comdatGroup(cg_index); + const cg_owner = self.comdatGroupOwner(cg.owner); + const owner_file_index = if (self.file(cg_owner.file)) |file_ptr| + file_ptr.object.index + else + std.math.maxInt(File.Index); + cg_owner.file = @min(owner_file_index, index); + } } for (self.objects.items) |index| { const object = self.file(index).?.object; - object.resolveSymbols(self); + for (object.comdat_groups.items) |cg_index| { + const cg = self.comdatGroup(cg_index); + const cg_owner = self.comdatGroupOwner(cg.owner); + if (cg_owner.file != index) { + for (try object.comdatGroupMembers(cg.shndx)) |shndx| { + const atom_index = object.atoms.items[shndx]; + if (self.atom(atom_index)) |atom_ptr| { + atom_ptr.alive = false; + // atom_ptr.markFdesDead(self); + } + } + } + } + } + + // Re-resolve the symbols. + if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self); + for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); +} + +/// Traverses all objects and shared objects marking any object referenced by +/// a live object/shared object as alive itself. +/// This routine will prune unneeded objects extracted from archives and +/// unneeded shared objects. +fn markLive(self: *Elf) void { + for (self.objects.items) |index| { + const file_ptr = self.file(index).?; + if (file_ptr.isAlive()) file_ptr.markLive(self); } } @@ -1477,11 +1643,11 @@ fn scanRelocs(self: *Elf) !void { try self.reportUndefined(&undefs); - for (self.symbols.items) |*sym| { + for (self.symbols.items, 0..) |*sym, sym_index| { if (sym.flags.needs_got) { log.debug("'{s}' needs GOT", .{sym.name(self)}); // TODO how can we tell we need to write it again, aka the entry is dirty? - const gop = try sym.getOrCreateGotEntry(self); + const gop = try sym.getOrCreateGotEntry(@intCast(sym_index), self); try self.got.writeEntry(self, gop.index); } } @@ -1500,7 +1666,7 @@ fn allocateObjects(self: *Elf) !void { const local = self.symbol(local_index); const atom_ptr = local.atom(self) orelse continue; if (!atom_ptr.alive) continue; - local.value = atom_ptr.value; + local.value += atom_ptr.value; } for (object.globals()) |global_index| { @@ -1508,7 +1674,7 @@ fn allocateObjects(self: *Elf) !void { const atom_ptr = global.atom(self) orelse continue; if (!atom_ptr.alive) continue; if (global.file_index == index) { - global.value = atom_ptr.value; + global.value += atom_ptr.value; } } } @@ -1524,7 +1690,11 @@ fn writeObjects(self: *Elf) !void { if (!atom_ptr.alive) continue; const shdr = &self.shdrs.items[atom_ptr.output_section_index]; + if (shdr.sh_type == elf.SHT_NOBITS) continue; + if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; // TODO we don't yet know how to handle non-alloc sections + const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr; + log.debug("writing atom({d}) at 0x{x}", .{ atom_ptr.atom_index, file_offset }); const code = try atom_ptr.codeInObjectUncompressAlloc(self); defer gpa.free(code); @@ -2529,7 +2699,7 @@ fn updateDeclCode( esym.st_value = atom_ptr.value; sym.flags.needs_got = true; - const gop = try sym.getOrCreateGotEntry(self); + const gop = try sym.getOrCreateGotEntry(sym_index, self); try self.got.writeEntry(self, gop.index); } @@ -2764,7 +2934,7 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol. local_esym.st_value = atom_ptr.value; local_sym.flags.needs_got = true; - const gop = try local_sym.getOrCreateGotEntry(self); + const gop = try local_sym.getOrCreateGotEntry(symbol_index, self); try self.got.writeEntry(self, gop.index); const section_offset = atom_ptr.value - self.phdrs.items[phdr_index].p_vaddr; @@ -3635,7 +3805,7 @@ pub fn addSymbol(self: *Elf) !Symbol.Index { break :blk index; } }; - self.symbols.items[index] = .{ .index = index }; + self.symbols.items[index] = .{}; return index; } @@ -4012,6 +4182,7 @@ const synthetic_sections = @import("Elf/synthetic_sections.zig"); const Air = @import("../Air.zig"); const Allocator = std.mem.Allocator; +const Archive = @import("Elf/Archive.zig"); pub const Atom = @import("Elf/Atom.zig"); const Cache = std.Build.Cache; const Compilation = @import("../Compilation.zig"); diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig new file mode 100644 index 0000000000..f34b323206 --- /dev/null +++ b/src/link/Elf/Archive.zig @@ -0,0 +1,153 @@ +path: []const u8, +data: []const u8, + +objects: std.ArrayListUnmanaged(Object) = .{}, +strtab: []const u8 = &[0]u8{}, + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +pub const ARMAG: *const [SARMAG:0]u8 = "!\n"; +/// Size of that string. +pub const SARMAG: u4 = 8; + +/// String in ar_fmag at the end of each header. +const ARFMAG: *const [2:0]u8 = "`\n"; + +const SYM64NAME: *const [7:0]u8 = "/SYM64/"; + +const ar_hdr = extern struct { + /// Member file name, sometimes / terminated. + ar_name: [16]u8, + + /// File date, decimal seconds since Epoch. + ar_date: [12]u8, + + /// User ID, in ASCII format. + ar_uid: [6]u8, + + /// Group ID, in ASCII format. + ar_gid: [6]u8, + + /// File mode, in ASCII octal. + ar_mode: [8]u8, + + /// File size, in ASCII decimal. + ar_size: [10]u8, + + /// Always contains ARFMAG. + ar_fmag: [2]u8, + + fn date(self: ar_hdr) !u64 { + const value = getValue(&self.ar_date); + return std.fmt.parseInt(u64, value, 10); + } + + fn size(self: ar_hdr) !u32 { + const value = getValue(&self.ar_size); + return std.fmt.parseInt(u32, value, 10); + } + + fn getValue(raw: []const u8) []const u8 { + return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); + } + + fn isStrtab(self: ar_hdr) bool { + return mem.eql(u8, getValue(&self.ar_name), "//"); + } + + fn isSymtab(self: ar_hdr) bool { + return mem.eql(u8, getValue(&self.ar_name), "/"); + } +}; + +pub fn isArchive(file: std.fs.File) bool { + const reader = file.reader(); + const magic = reader.readBytesNoEof(Archive.SARMAG) catch return false; + defer file.seekTo(0) catch {}; + if (!mem.eql(u8, &magic, ARMAG)) return false; + return true; +} + +pub fn deinit(self: *Archive, allocator: Allocator) void { + allocator.free(self.data); + self.objects.deinit(allocator); +} + +pub fn parse(self: *Archive, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + + var stream = std.io.fixedBufferStream(self.data); + const reader = stream.reader(); + _ = try reader.readBytesNoEof(SARMAG); + + while (true) { + if (stream.pos % 2 != 0) { + stream.pos += 1; + } + + const hdr = reader.readStruct(ar_hdr) catch break; + + if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { + // TODO convert into an error + log.debug( + "{s}: invalid header delimiter: expected '{s}', found '{s}'", + .{ self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag) }, + ); + return; + } + + const size = try hdr.size(); + defer { + _ = stream.seekBy(size) catch {}; + } + + if (hdr.isSymtab()) continue; + if (hdr.isStrtab()) { + self.strtab = self.data[stream.pos..][0..size]; + continue; + } + + const name = ar_hdr.getValue(&hdr.ar_name); + + if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; + + const object_name = blk: { + if (name[0] == '/') { + const off = try std.fmt.parseInt(u32, name[1..], 10); + break :blk self.getString(off); + } + break :blk name; + }; + + const object = Object{ + .archive = self.path, + .path = try gpa.dupe(u8, object_name[0 .. object_name.len - 1]), // To account for trailing '/' + .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]), + .index = undefined, + .alive = false, + }; + + log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path }); + + try self.objects.append(gpa, object); + } +} + +fn getString(self: Archive, off: u32) []const u8 { + assert(off < self.strtab.len); + return mem.sliceTo(@as([*:'\n']const u8, @ptrCast(self.strtab.ptr + off)), 0); +} + +const std = @import("std"); +const assert = std.debug.assert; +const elf = std.elf; +const fs = std.fs; +const log = std.log.scoped(.link); +const mem = std.mem; + +const Allocator = mem.Allocator; +const Archive = @This(); +const Elf = @import("../Elf.zig"); +const Object = @import("Object.zig"); diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 82c2b46d1d..073536fbaa 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -322,11 +322,12 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void { if (rel.r_type() == elf.R_X86_64_NONE) continue; - const symbol = switch (file_ptr) { - .zig_module => |x| elf_file.symbol(x.symbol(rel.r_sym())), - .object => |x| elf_file.symbol(x.symbols.items[rel.r_sym()]), + const symbol_index = switch (file_ptr) { + .zig_module => |x| x.symbol(rel.r_sym()), + .object => |x| x.symbols.items[rel.r_sym()], else => unreachable, }; + const symbol = elf_file.symbol(symbol_index); // Check for violation of One Definition Rule for COMDATs. if (symbol.file(elf_file) == null) { @@ -340,7 +341,7 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void { } // Report an undefined symbol. - try self.reportUndefined(elf_file, symbol, rel, undefs); + try self.reportUndefined(elf_file, symbol, symbol_index, rel, undefs); // While traversing relocations, mark symbols that require special handling such as // pointer indirection via GOT, or a stub trampoline via PLT. @@ -379,7 +380,14 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void { } // This function will report any undefined non-weak symbols that are not imports. -fn reportUndefined(self: Atom, elf_file: *Elf, sym: *const Symbol, rel: elf.Elf64_Rela, undefs: anytype) !void { +fn reportUndefined( + self: Atom, + elf_file: *Elf, + sym: *const Symbol, + sym_index: Symbol.Index, + rel: elf.Elf64_Rela, + undefs: anytype, +) !void { const rel_esym = switch (elf_file.file(self.file_index).?) { .zig_module => |x| x.elfSym(rel.r_sym()).*, .object => |x| x.symtab[rel.r_sym()], @@ -392,7 +400,7 @@ fn reportUndefined(self: Atom, elf_file: *Elf, sym: *const Symbol, rel: elf.Elf6 !sym.flags.import and esym.st_shndx == elf.SHN_UNDEF) { - const gop = try undefs.getOrPut(sym.index); + const gop = try undefs.getOrPut(sym_index); if (!gop.found_existing) { gop.value_ptr.* = std.ArrayList(Atom.Index).init(elf_file.base.allocator); } @@ -417,6 +425,7 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void { .object => |x| elf_file.symbol(x.symbols.items[rel.r_sym()]), else => unreachable, }; + const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow; // We will use equation format to resolve relocations: // https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/ @@ -446,24 +455,49 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void { relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ({s})", .{ fmtRelocType(r_type), - rel.r_offset, + r_offset, P, S + A, G + GOT + A, target.name(elf_file), }); - try stream.seekTo(rel.r_offset); + try stream.seekTo(r_offset); switch (rel.r_type()) { elf.R_X86_64_NONE => unreachable, elf.R_X86_64_64 => try cwriter.writeIntLittle(i64, S + A), + elf.R_X86_64_32 => try cwriter.writeIntLittle(u32, @as(u32, @truncate(@as(u64, @intCast(S + A))))), + elf.R_X86_64_32S => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A))), + elf.R_X86_64_PLT32, elf.R_X86_64_PC32, => try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))), + elf.R_X86_64_GOTPCREL => try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P))), + elf.R_X86_64_GOTPC32 => try cwriter.writeIntLittle(i32, @as(i32, @intCast(GOT + A - P))), + elf.R_X86_64_GOTPC64 => try cwriter.writeIntLittle(i64, GOT + A - P), + + elf.R_X86_64_GOTPCRELX => { + if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: { + x86_64.relaxGotpcrelx(code[r_offset - 2 ..]) catch break :blk; + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))); + continue; + } + try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P))); + }, + + elf.R_X86_64_REX_GOTPCRELX => { + if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: { + x86_64.relaxRexGotpcrelx(code[r_offset - 3 ..]) catch break :blk; + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))); + continue; + } + try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P))); + }, + else => { log.err("TODO: unhandled relocation type {}", .{fmtRelocType(rel.r_type())}); @panic("TODO unhandled relocation type"); @@ -591,6 +625,58 @@ fn format2( // future. pub const Index = u16; +const x86_64 = struct { + pub fn relaxGotpcrelx(code: []u8) !void { + const old_inst = disassemble(code) orelse return error.RelaxFail; + const inst = switch (old_inst.encoding.mnemonic) { + .call => try Instruction.new(old_inst.prefix, .call, &.{ + // TODO: hack to force imm32s in the assembler + .{ .imm = Immediate.s(-129) }, + }), + .jmp => try Instruction.new(old_inst.prefix, .jmp, &.{ + // TODO: hack to force imm32s in the assembler + .{ .imm = Immediate.s(-129) }, + }), + else => return error.RelaxFail, + }; + relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); + const nop = try Instruction.new(.none, .nop, &.{}); + encode(&.{ nop, inst }, code) catch return error.RelaxFail; + } + + pub fn relaxRexGotpcrelx(code: []u8) !void { + const old_inst = disassemble(code) orelse return error.RelaxFail; + switch (old_inst.encoding.mnemonic) { + .mov => { + const inst = try Instruction.new(old_inst.prefix, .lea, &old_inst.ops); + relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); + encode(&.{inst}, code) catch return error.RelaxFail; + }, + else => return error.RelaxFail, + } + } + + fn disassemble(code: []const u8) ?Instruction { + var disas = Disassembler.init(code); + const inst = disas.next() catch return null; + return inst; + } + + fn encode(insts: []const Instruction, code: []u8) !void { + var stream = std.io.fixedBufferStream(code); + const writer = stream.writer(); + for (insts) |inst| { + try inst.encode(writer, .{}); + } + } + + const bits = @import("../../arch/x86_64/bits.zig"); + const encoder = @import("../../arch/x86_64/encoder.zig"); + const Disassembler = @import("../../arch/x86_64/Disassembler.zig"); + const Immediate = bits.Immediate; + const Instruction = encoder.Instruction; +}; + const std = @import("std"); const assert = std.debug.assert; const elf = std.elf; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index b0a6ef2a1c..32c96b8d95 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -485,9 +485,9 @@ pub fn claimUnresolved(self: *Object, elf_file: *Elf) void { pub fn resetGlobals(self: *Object, elf_file: *Elf) void { for (self.globals()) |index| { const global = elf_file.symbol(index); - const name = global.name; + const off = global.name_offset; global.* = .{}; - global.name = name; + global.name_offset = off; } } @@ -499,7 +499,7 @@ pub fn markLive(self: *Object, elf_file: *Elf) void { if (sym.st_bind() == elf.STB_WEAK) continue; const global = elf_file.symbol(index); - const file = global.getFile(elf_file) orelse continue; + const file = global.file(elf_file) orelse continue; const should_keep = sym.st_shndx == elf.SHN_UNDEF or (sym.st_shndx == elf.SHN_COMMON and global.elfSym(elf_file).st_shndx != elf.SHN_COMMON); if (should_keep and !file.isAlive()) { diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 42b9b81ef9..c70d0b8229 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -1,7 +1,5 @@ //! Represents a defined symbol. -index: Index = 0, - /// Allocated address value of this symbol. value: u64 = 0, @@ -117,10 +115,10 @@ const GetOrCreateGotEntryResult = struct { index: GotSection.Index, }; -pub fn getOrCreateGotEntry(symbol: *Symbol, elf_file: *Elf) !GetOrCreateGotEntryResult { +pub fn getOrCreateGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateGotEntryResult { assert(symbol.flags.needs_got); if (symbol.flags.has_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.got }; - const index = try elf_file.got.addGotSymbol(symbol.index, elf_file); + const index = try elf_file.got.addGotSymbol(symbol_index, elf_file); symbol.flags.has_got = true; return .{ .found_existing = false, .index = index }; } @@ -270,7 +268,7 @@ fn format2( _ = options; _ = unused_fmt_string; const symbol = ctx.symbol; - try writer.print("%{d} : {s} : @{x}", .{ symbol.index, symbol.fmtName(ctx.elf_file), symbol.value }); + try writer.print("%{d} : {s} : @{x}", .{ symbol.esym_index, symbol.fmtName(ctx.elf_file), symbol.value }); if (symbol.file(ctx.elf_file)) |file_ptr| { if (symbol.isAbs(ctx.elf_file)) { if (symbol.elfSym(ctx.elf_file).st_shndx == elf.SHN_UNDEF) { diff --git a/src/link/Elf/ZigModule.zig b/src/link/Elf/ZigModule.zig index 46a382abf9..98496a2c38 100644 --- a/src/link/Elf/ZigModule.zig +++ b/src/link/Elf/ZigModule.zig @@ -148,6 +148,15 @@ pub fn scanRelocs(self: *ZigModule, elf_file: *Elf, undefs: anytype) !void { } } +pub fn resetGlobals(self: *ZigModule, elf_file: *Elf) void { + for (self.globals()) |index| { + const global = elf_file.symbol(index); + const off = global.name_offset; + global.* = .{}; + global.name_offset = off; + } +} + pub fn updateSymtabSize(self: *ZigModule, elf_file: *Elf) void { for (self.locals()) |local_index| { const local = elf_file.symbol(local_index); diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 2b49f43bf1..3b9790dffa 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -62,6 +62,19 @@ pub const File = union(enum) { return (@as(u32, base) << 24) + file.index(); } + pub fn resolveSymbols(file: File, elf_file: *Elf) void { + switch (file) { + inline else => |x| x.resolveSymbols(elf_file), + } + } + + pub fn resetGlobals(file: File, elf_file: *Elf) void { + switch (file) { + .linker_defined => unreachable, + inline else => |x| x.resetGlobals(elf_file), + } + } + pub fn setAlive(file: File) void { switch (file) { .zig_module, .linker_defined => {}, @@ -71,7 +84,7 @@ pub const File = union(enum) { pub fn markLive(file: File, elf_file: *Elf) void { switch (file) { - .zig_module, .linker_defined => {}, + .zig_module, .linker_defined => unreachable, inline else => |x| x.markLive(elf_file), } }