tiny.choir.backends.elf_object
Defined in backends.
API (7)
Actions
Public operations.
Types and contracts
Public types and contracts.
ObjectErrorRelocatableObjectRelocation: One slot to patch.TextSymbolX86_64MachineCodeObject
Source
Source: lib/choir/src/backends/elf.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const sys = @import("sys");const artifact = @import("root.zig").artifact;const machine = @import("machine.zig");const Allocator = std.mem.Allocator;const ehdr_size = 64;const shdr_size = 64;const sym_size = 24;const rela_size = 24;pub const ObjectError = Allocator.Error || ValidationError;const ValidationError = error{ DuplicateSymbol, InvalidAlignment, InvalidDataSymbol, InvalidTextSymbol, InvalidRelocationOffset, MissingRelocationSymbol, UnsupportedArchitecture, UnsupportedRelocation, ObjectSizeOverflow,};/// One slot to patch. `section` names the section holding the slot, and only a section the/// file carries bytes for may hold one: `.text`, `.rodata`, or `.data`. A `.bss` slot is/// refused because there are no bytes in the file to write an address into.pub const Relocation = struct { section: []const u8 = ".text", offset: u64, symbol: []const u8, kind: artifact.RelocationKind, addend: i64 = 0, width_bits: u16 = 64,};pub const TextSymbol = struct { name: []const u8, offset: u64, size: u64,};pub const RelocatableObject = struct { architecture: artifact.Architecture = .x86_64, entry_symbol: []const u8, text: []const u8, text_alignment: usize = 16, text_symbols: []const TextSymbol = &.{}, data_symbols: []const machine.DataSymbol = &.{}, relocations: []const Relocation = &.{}, executable_stack: bool = false,};pub const X86_64MachineCodeObject = struct { entry_symbol: []const u8, code: []const u8, text_symbols: []const TextSymbol = &.{}, relocations: []const machine.CallRelocation = &.{}, data_relocations: []const machine.DataRelocation = &.{}, data_symbols: []const machine.DataSymbol = &.{}, executable_stack: bool = false,};pub fn buildX86_64MachineCodeObject( allocator: Allocator, input: X86_64MachineCodeObject,) ObjectError![]u8 { const object_relocations = try allocator.alloc(Relocation, input.relocations.len + input.data_relocations.len); defer allocator.free(object_relocations); for (input.relocations, 0..) |relocation, index| { object_relocations[index] = .{ .offset = relocation.offset, .symbol = relocation.target, .kind = .call, .width_bits = 64, }; } for (input.data_relocations, 0..) |relocation, index| { object_relocations[input.relocations.len + index] = .{ .offset = relocation.offset, .symbol = relocation.target, .kind = .absolute, .addend = relocation.addend, .width_bits = relocation.width_bits, }; } return buildRelocatableObject(allocator, .{ .architecture = .x86_64, .entry_symbol = input.entry_symbol, .text = input.code, .text_symbols = input.text_symbols, .data_symbols = input.data_symbols, .relocations = object_relocations, .executable_stack = input.executable_stack, });}pub fn buildRelocatableObject( allocator: Allocator, input: RelocatableObject,) ObjectError![]u8 { if (input.architecture != .x86_64) return error.UnsupportedArchitecture; try validateAlignment(input.text_alignment); const default_symbols = [_]TextSymbol{.{ .name = input.entry_symbol, .offset = 0, .size = input.text.len, }}; const text_symbols = if (input.text_symbols.len == 0) &default_symbols else input.text_symbols; for (text_symbols) |symbol| try validateTextSymbol(input.text.len, symbol); var rodata = try DataSectionLayout.build(allocator, input.data_symbols, .rodata); defer rodata.deinit(allocator); var data = try DataSectionLayout.build(allocator, input.data_symbols, .data); defer data.deinit(allocator); var bss = try DataSectionLayout.build(allocator, input.data_symbols, .bss); defer bss.deinit(allocator); var sections = Sections.init(input, rodata, data, bss); var symbols = try Symbols.init(allocator); defer symbols.deinit(allocator); try symbols.collect(allocator, sections, .{ .rodata = rodata.symbols, .data = data.symbols, .bss = bss.symbols, }, text_symbols, input.relocations); const sizes: TargetSizes = .{ input.text.len, rodata.size, data.size }; var groups = try collectRelocations(allocator, input, sizes, &symbols.indices); defer groups.deinit(allocator); const layout = try sections.place(&symbols, groups); const buffer = try allocator.alloc(u8, layout.size); errdefer allocator.free(buffer); @memset(buffer, 0); const placed = [_]PlacedBytes{ .{ .section = sections.text, .target = .text, .bytes = input.text }, .{ .section = sections.rodata, .target = .rodata, .bytes = rodata.bytes }, .{ .section = sections.data, .target = .data, .bytes = data.bytes }, }; for (placed) |entry| { if (entry.section == 0) continue; const offset = sections.headers[entry.section].offset; copyInto(buffer, offset, entry.bytes); scrubRelocationSlots(buffer[offset..][0..entry.bytes.len], input.relocations, entry.target); } for (relocation_targets) |target| { const section = sections.rela[@backingInt(target)]; if (section == 0) continue; writeRelaRecords(buffer, sections.headers[section].offset, groups.slice(target)); } writeSymbolRecords(buffer, sections.headers[sections.symbols].offset, symbols.records.items); copyInto(buffer, sections.headers[sections.strings].offset, symbols.strings.bytes()); copyInto(buffer, sections.headers[sections.names].offset, Sections.names_text); writeElfHeader(buffer, .{ .section_header_offset = layout.headers, .section_count = sections.count, .section_string_table_index = sections.names, }); sections.write(buffer, layout.headers); std.debug.assert(buffer.len >= input.text.len); return buffer;}const Sections = struct { headers: [12]SectionHeader = @splat(.null_header), count: u16 = 1, text: u16 = 0, rodata: u16 = 0, data: u16 = 0, bss: u16 = 0, /// One relocation section per target that has relocations, indexed by `RelocationTarget`. rela: [relocation_target_count]u16 = @splat(0), symbols: u16 = 0, strings: u16 = 0, names: u16 = 0, /// A name is found by its first occurrence, so each plain name is spelled before the /// `.rela.` section that quotes it. const names_text = "\x00.text\x00.rodata\x00.data\x00.bss\x00.rela.text\x00" ++ ".rela.rodata\x00.rela.data\x00.symtab\x00.strtab\x00.shstrtab\x00" ++ ".note.GNU-stack\x00"; const Layout = struct { headers: usize, size: usize }; fn init( input: RelocatableObject, rodata: DataSectionLayout, data: DataSectionLayout, bss: DataSectionLayout, ) Sections { var self: Sections = .{}; self.text = self.add(".text", .{ .section_type = std.elf.SHT_PROGBITS, .flags = std.elf.SHF_ALLOC | std.elf.SHF_EXECINSTR, .size = input.text.len, .alignment = input.text_alignment, }); if (rodata.size != 0) self.rodata = self.add(".rodata", .{ .section_type = std.elf.SHT_PROGBITS, .flags = std.elf.SHF_ALLOC, .size = rodata.size, .alignment = rodata.alignment, }); if (data.size != 0) self.data = self.add(".data", .{ .section_type = std.elf.SHT_PROGBITS, .flags = std.elf.SHF_ALLOC | std.elf.SHF_WRITE, .size = data.size, .alignment = data.alignment, }); if (bss.size != 0) self.bss = self.add(".bss", .{ .section_type = std.elf.SHT_NOBITS, .flags = std.elf.SHF_ALLOC | std.elf.SHF_WRITE, .size = bss.size, .alignment = bss.alignment, }); self.addRelocationSections(input.relocations); self.symbols = self.add(".symtab", .{ .section_type = std.elf.SHT_SYMTAB, .alignment = 8, .entry_size = sym_size, }); self.strings = self.add(".strtab", .{ .section_type = std.elf.SHT_STRTAB, .alignment = 1 }); self.names = self.add(".shstrtab", .{ .section_type = std.elf.SHT_STRTAB, .size = names_text.len, .alignment = 1, }); _ = self.add(".note.GNU-stack", .{ .section_type = std.elf.SHT_PROGBITS, .flags = if (input.executable_stack) std.elf.SHF_EXECINSTR else 0, .alignment = 1, }); self.headers[self.symbols].link = self.strings; for (self.rela) |section| { if (section != 0) self.headers[section].link = self.symbols; } std.debug.assert(self.count <= self.headers.len); return self; } /// Adds one `SHT_RELA` section per target that has relocations. A target with none gets no /// section, which is what keeps an object holding only `.text` slots identical to what this /// writer produced before data sections existed. fn addRelocationSections(self: *Sections, relocations: []const Relocation) void { if (relocationCount(relocations, .text) != 0) { self.rela[@backingInt(RelocationTarget.text)] = self.add(".rela.text", relaHeader(self.text)); } if (self.rodata != 0 and relocationCount(relocations, .rodata) != 0) { self.rela[@backingInt(RelocationTarget.rodata)] = self.add(".rela.rodata", relaHeader(self.rodata)); } if (self.data != 0 and relocationCount(relocations, .data) != 0) { self.rela[@backingInt(RelocationTarget.data)] = self.add(".rela.data", relaHeader(self.data)); } } fn relaHeader(target_section: u16) SectionHeader { return .{ .section_type = std.elf.SHT_RELA, .info = target_section, .alignment = 8, .entry_size = rela_size, }; } fn add(self: *Sections, comptime name: []const u8, header: SectionHeader) u16 { std.debug.assert(self.count < self.headers.len); const index = self.count; self.headers[index] = header; self.headers[index].name = comptime std.mem.indexOf(u8, names_text, name).?; self.count += 1; return index; } /// Assigns every section its file offset. A `SHT_NOBITS` section takes an offset and no /// bytes, because its size is what the loader zeroes rather than what the file carries. fn place( self: *Sections, symbols: *const Symbols, groups: RelocationGroups, ) ObjectError!Layout { self.headers[self.symbols].size = std.math.mul(usize, symbols.records.items.len, sym_size) catch { return error.ObjectSizeOverflow; }; self.headers[self.symbols].info = symbols.first_global; self.headers[self.strings].size = symbols.strings.bytes().len; for (self.rela, groups.counts) |section, count| { if (section == 0) { std.debug.assert(count == 0); continue; } self.headers[section].size = std.math.mul(usize, count, rela_size) catch { return error.ObjectSizeOverflow; }; } var offset: usize = ehdr_size; for (self.headers[1..self.count]) |*header| { const mask = header.alignment - 1; const aligned = std.math.add(usize, offset, mask) catch return error.ObjectSizeOverflow; offset = aligned & ~mask; header.offset = offset; if (header.section_type == std.elf.SHT_NOBITS) continue; offset = std.math.add(usize, offset, header.size) catch return error.ObjectSizeOverflow; } const padded = std.math.add(usize, offset, 7) catch return error.ObjectSizeOverflow; const headers = padded & ~@as(usize, 7); const size = std.math.add(usize, headers, @as(usize, self.count) * shdr_size) catch { return error.ObjectSizeOverflow; }; std.debug.assert(size >= headers); return .{ .headers = headers, .size = size }; } fn write(self: *const Sections, buffer: []u8, table_offset: usize) void { std.debug.assert(self.count <= self.headers.len); for (self.headers[0..self.count], 0..) |header, index| { writeSectionHeader(buffer, table_offset, @intCast(index), header); } }};const Symbols = struct { records: std.ArrayListUnmanaged(SymbolRecord) = .empty, indices: std.StringHashMapUnmanaged(u32) = .{}, strings: StringTable, first_global: u32 = 0, fn init(allocator: Allocator) Allocator.Error!Symbols { return .{ .strings = try StringTable.init(allocator) }; } fn deinit(self: *Symbols, allocator: Allocator) void { self.records.deinit(allocator); self.indices.deinit(allocator); self.strings.deinit(allocator); self.* = undefined; } /// Writes the table in the one order ELF permits: every local symbol first, then /// `first_global` and the rest. A section symbol is local, so the data sections announce /// themselves before any datum does. fn collect( self: *Symbols, allocator: Allocator, sections: Sections, layouts: SymbolLayouts, text: []const TextSymbol, relocations: []const Relocation, ) ObjectError!void { std.debug.assert(self.records.items.len == 0); const data_sections = [_]DataSectionSymbols{ .{ .section = sections.rodata, .symbols = layouts.rodata }, .{ .section = sections.data, .symbols = layouts.data }, .{ .section = sections.bss, .symbols = layouts.bss }, }; try self.add(allocator, .{}); try self.add(allocator, .{ .kind = .section, .section = sections.text }); for (data_sections) |entry| { if (entry.section == 0) continue; try self.add(allocator, .{ .kind = .section, .section = entry.section }); } for (data_sections) |entry| { if (entry.section == 0) continue; try self.data(allocator, entry.section, entry.symbols, .local); } self.first_global = @intCast(self.records.items.len); for (data_sections) |entry| { if (entry.section == 0) continue; try self.data(allocator, entry.section, entry.symbols, .global); } for (text) |symbol| try self.add(allocator, .{ .name = symbol.name, .binding = .global, .kind = .function, .section = sections.text, .value = symbol.offset, .size = symbol.size, }); for (relocations) |relocation| { if (self.indices.contains(relocation.symbol)) continue; try self.add(allocator, .{ .name = relocation.symbol, .binding = .global, .kind = symbolKindForRelocation(relocation.kind), .section = std.elf.SHN_UNDEF, }); } std.debug.assert(self.first_global <= self.records.items.len); } fn data( self: *Symbols, allocator: Allocator, section: u16, symbols: []const DataSymbolLayout, binding: machine.DataSymbolBinding, ) ObjectError!void { for (symbols) |symbol| { if (symbol.binding != binding) continue; try self.add(allocator, .{ .name = symbol.name, .binding = if (binding == .local) .local else .global, .kind = .object, .section = section, .value = symbol.offset, .size = symbol.size, }); } } fn add(self: *Symbols, allocator: Allocator, fields: SymbolFields) ObjectError!void { try appendSymbol(allocator, &self.strings, &self.records, &self.indices, fields); }};/// The sections a relocation may name. `.bss` is absent on purpose: a `SHT_NOBITS` section/// occupies no file bytes, so there is no slot in the object to write an address into. A/// relocation naming `.bss` is refused rather than silently dropped.const RelocationTarget = enum(u2) { text = 0, rodata = 1, data = 2 };const relocation_targets = [_]RelocationTarget{ .text, .rodata, .data };const relocation_target_count = relocation_targets.len;/// The byte length of each target section, indexed by `RelocationTarget`.const TargetSizes = [relocation_target_count]usize;const PlacedBytes = struct { section: u16, target: RelocationTarget, bytes: []const u8 };const SymbolLayouts = struct { rodata: []const DataSymbolLayout, data: []const DataSymbolLayout, bss: []const DataSymbolLayout,};const DataSectionSymbols = struct { section: u16, symbols: []const DataSymbolLayout };fn relocationTarget(section: []const u8) ObjectError!RelocationTarget { if (std.mem.eql(u8, section, ".text")) return .text; if (std.mem.eql(u8, section, ".rodata")) return .rodata; if (std.mem.eql(u8, section, ".data")) return .data; return error.UnsupportedRelocation;}/// Counts the relocations landing in one target. A section header is only written for a/// target that has some, so this runs before any of them are built.fn relocationCount(relocations: []const Relocation, target: RelocationTarget) usize { var count: usize = 0; for (relocations) |relocation| { const found = relocationTarget(relocation.section) catch continue; if (found == target) count += 1; } return count;}/// Every relocation record, ordered so that one target's records are contiguous. A/// `SHT_RELA` section is a run of records, so grouping is what lets three sections share one/// allocation.const RelocationGroups = struct { records: []RelaRecord = &.{}, starts: [relocation_target_count]usize = @splat(0), counts: [relocation_target_count]usize = @splat(0), fn slice(self: RelocationGroups, target: RelocationTarget) []const RelaRecord { const index = @backingInt(target); return self.records[self.starts[index]..][0..self.counts[index]]; } fn deinit(self: *RelocationGroups, allocator: Allocator) void { if (self.records.len != 0) allocator.free(self.records); self.* = .{}; }};fn collectRelocations( allocator: Allocator, input: RelocatableObject, sizes: TargetSizes, symbols: *const std.StringHashMapUnmanaged(u32),) ObjectError!RelocationGroups { if (input.relocations.len == 0) return .{}; const records = try allocator.alloc(RelaRecord, input.relocations.len); errdefer allocator.free(records); var groups: RelocationGroups = .{ .records = records }; var written: usize = 0; for (relocation_targets) |target| { groups.starts[@backingInt(target)] = written; for (input.relocations) |relocation| { if ((try relocationTarget(relocation.section)) != target) continue; const symbol_index = symbols.get(relocation.symbol) orelse return error.MissingRelocationSymbol; const relocation_type = try x86_64RelocationType(relocation); try validateRelocationSlot(sizes[@backingInt(target)], relocation); records[written] = .{ .offset = relocation.offset, .info = (@as(u64, symbol_index) << 32) | relocation_type, .addend = x86_64RelocationAddend(relocation), }; written += 1; } groups.counts[@backingInt(target)] = written - groups.starts[@backingInt(target)]; } std.debug.assert(written == input.relocations.len); return groups;}/// The bytes of one `SHT_PROGBITS` data section and where each of its symbols sits in them./// One data section's placed contents: the bytes the file carries, the extent those bytes/// occupy once loaded, and where each symbol sits inside it.////// `.bss` uses this type too. There `bytes` is empty and `size` is the extent, which is the one/// difference between a section the file carries and a section the loader supplies.const DataSectionLayout = struct { bytes: []u8 = &.{}, size: usize = 0, symbols: []DataSymbolLayout = &.{}, alignment: usize = 1, /// Packs the symbols belonging to `section` by the same rule as JIT data mappings. The /// packing itself stays with `machine.DataLayout`, so no section can drift from another. fn build( allocator: Allocator, symbols: []const machine.DataSymbol, section: machine.DataSection, ) ObjectError!DataSectionLayout { var count: usize = 0; for (symbols) |symbol| { if (symbol.section == section) count += 1; } if (count == 0) return .{}; const selected = try allocator.alloc(machine.DataSymbol, count); defer allocator.free(selected); var filled: usize = 0; for (symbols) |symbol| { if (symbol.section != section) continue; selected[filled] = symbol; filled += 1; } std.debug.assert(filled == count); var layout = machine.DataLayout.init(allocator, selected) catch |err| return switch (err) { error.OutOfMemory => error.OutOfMemory, error.InvalidDataSymbol => error.InvalidDataSymbol, error.DataTooLarge => error.ObjectSizeOverflow, }; defer layout.deinit(allocator); const placed = try allocator.alloc(DataSymbolLayout, count); errdefer allocator.free(placed); for (selected, layout.offsets, placed) |symbol, offset, *entry| { entry.* = .{ .name = symbol.name, .binding = symbol.binding, .offset = offset, .size = symbol.size(), }; } if (!section.carriesBytes()) { return .{ .size = layout.size, .symbols = placed, .alignment = layout.alignment }; } const bytes = try allocator.alloc(u8, layout.size); layout.write(selected, bytes); return .{ .bytes = bytes, .size = layout.size, .symbols = placed, .alignment = layout.alignment, }; } fn deinit(self: *DataSectionLayout, allocator: Allocator) void { if (self.bytes.len != 0) allocator.free(self.bytes); if (self.symbols.len != 0) allocator.free(self.symbols); self.* = .{}; }};const DataSymbolLayout = struct { name: []const u8, binding: machine.DataSymbolBinding, offset: u64, size: u64,};const StringTable = struct { data: std.ArrayListUnmanaged(u8) = .empty, fn init(allocator: Allocator) Allocator.Error!StringTable { var table = StringTable{}; try table.data.append(allocator, 0); return table; } fn deinit(self: *StringTable, allocator: Allocator) void { self.data.deinit(allocator); } fn add(self: *StringTable, allocator: Allocator, name: []const u8) Allocator.Error!u32 { const offset: u32 = @intCast(self.data.items.len); try self.data.appendSlice(allocator, name); try self.data.append(allocator, 0); return offset; } fn bytes(self: *const StringTable) []const u8 { return self.data.items; }};const SymbolBinding = enum { local, global,};const SymbolKind = enum { none, section, function, object,};const SymbolFields = struct { name: []const u8 = "", binding: SymbolBinding = .local, kind: SymbolKind = .none, section: u16 = std.elf.SHN_UNDEF, value: u64 = 0, size: u64 = 0,};const SymbolRecord = struct { name: u32 = 0, info: u8 = 0, other: u8 = 0, section: u16 = std.elf.SHN_UNDEF, value: u64 = 0, size: u64 = 0,};fn appendSymbol( allocator: Allocator, strtab: *StringTable, symbols: *std.ArrayListUnmanaged(SymbolRecord), symbol_indices: *std.StringHashMapUnmanaged(u32), fields: SymbolFields,) ObjectError!void { if (fields.name.len != 0 and symbol_indices.contains(fields.name)) return error.DuplicateSymbol; const index: u32 = @intCast(symbols.items.len); const name_offset = if (fields.name.len == 0) 0 else try strtab.add(allocator, fields.name); try symbols.append(allocator, .{ .name = name_offset, .info = (@as(u8, elfBinding(fields.binding)) << 4) | elfSymbolKind(fields.kind), .section = fields.section, .value = fields.value, .size = fields.size, }); if (fields.name.len != 0) { try symbol_indices.putNoClobber(allocator, fields.name, index); }}const RelaRecord = struct { offset: u64, info: u64, addend: i64,};const ElfHeaderSpec = struct { section_header_offset: usize, section_count: u16, section_string_table_index: u16,};fn writeElfHeader(buffer: []u8, spec: ElfHeaderSpec) void { std.mem.copyForwards(u8, buffer[0..4], std.elf.MAGIC); buffer[std.elf.EI_CLASS] = std.elf.ELFCLASS64; buffer[std.elf.EI_DATA] = std.elf.ELFDATA2LSB; buffer[std.elf.EI_VERSION] = 1; buffer[std.elf.EI_OSABI] = 0; writeU16(buffer, 16, @backingInt(std.elf.ET.REL)); writeU16(buffer, 18, @backingInt(std.elf.EM.X86_64)); writeU32(buffer, 20, 1); writeU64(buffer, 24, 0); writeU64(buffer, 32, 0); writeU64(buffer, 40, @intCast(spec.section_header_offset)); writeU32(buffer, 48, 0); writeU16(buffer, 52, ehdr_size); writeU16(buffer, 54, 0); writeU16(buffer, 56, 0); writeU16(buffer, 58, shdr_size); writeU16(buffer, 60, spec.section_count); writeU16(buffer, 62, spec.section_string_table_index);}const SectionHeader = struct { name: u32 = 0, section_type: u32 = std.elf.SHT_NULL, flags: u64 = 0, address: u64 = 0, offset: usize = 0, size: usize = 0, link: u32 = 0, info: u32 = 0, alignment: usize = 0, entry_size: usize = 0, const null_header: SectionHeader = .{};};fn writeSectionHeader(buffer: []u8, table_offset: usize, section_index: u16, header: SectionHeader) void { const offset = table_offset + @as(usize, section_index) * shdr_size; writeU32(buffer, offset + 0, header.name); writeU32(buffer, offset + 4, header.section_type); writeU64(buffer, offset + 8, header.flags); writeU64(buffer, offset + 16, header.address); writeU64(buffer, offset + 24, @intCast(header.offset)); writeU64(buffer, offset + 32, @intCast(header.size)); writeU32(buffer, offset + 40, header.link); writeU32(buffer, offset + 44, header.info); writeU64(buffer, offset + 48, @intCast(header.alignment)); writeU64(buffer, offset + 56, @intCast(header.entry_size));}fn writeSymbolRecords(buffer: []u8, offset: usize, symbols: []const SymbolRecord) void { for (symbols, 0..) |symbol, index| { const start = offset + index * sym_size; writeU32(buffer, start + 0, symbol.name); buffer[start + 4] = symbol.info; buffer[start + 5] = symbol.other; writeU16(buffer, start + 6, symbol.section); writeU64(buffer, start + 8, symbol.value); writeU64(buffer, start + 16, symbol.size); }}fn writeRelaRecords(buffer: []u8, offset: usize, records: []const RelaRecord) void { for (records, 0..) |record, index| { const start = offset + index * rela_size; writeU64(buffer, start + 0, record.offset); writeU64(buffer, start + 8, record.info); writeU64(buffer, start + 16, @bitCast(record.addend)); }}fn copyInto(buffer: []u8, offset: usize, bytes: []const u8) void { if (bytes.len == 0) return; std.mem.copyForwards(u8, buffer[offset .. offset + bytes.len], bytes);}fn validateAlignment(alignment: usize) ObjectError!void { if (alignment == 0 or (alignment & (alignment - 1)) != 0) return error.InvalidAlignment;}fn validateTextSymbol(text_len: usize, symbol: TextSymbol) ObjectError!void { if (symbol.name.len == 0) return error.InvalidTextSymbol; if (symbol.offset > std.math.maxInt(usize) or symbol.size > std.math.maxInt(usize)) { return error.InvalidTextSymbol; } const offset: usize = @intCast(symbol.offset); const size: usize = @intCast(symbol.size); if (offset > text_len or size > text_len - offset) return error.InvalidTextSymbol;}fn validateRelocationSlot(section_len: usize, relocation: Relocation) ObjectError!void { const width_bytes = relocation.width_bits / 8; if (width_bytes == 0 or relocation.width_bits % 8 != 0) return error.UnsupportedRelocation; if (relocation.offset > std.math.maxInt(usize)) return error.InvalidRelocationOffset; const offset: usize = @intCast(relocation.offset); if (offset > section_len or width_bytes > section_len - offset) { return error.InvalidRelocationOffset; }}/// Zeroes every slot the linker will write in one section, so a stale value cannot be read/// as an address if the relocation is never applied.fn scrubRelocationSlots( bytes: []u8, relocations: []const Relocation, target: RelocationTarget,) void { for (relocations) |relocation| { const found = relocationTarget(relocation.section) catch continue; if (found != target) continue; const width_bytes = relocation.width_bits / 8; const offset: usize = @intCast(relocation.offset); @memset(bytes[offset .. offset + width_bytes], 0); }}fn symbolKindForRelocation(kind: artifact.RelocationKind) SymbolKind { return switch (kind) { .call, .plt => .function, else => .none, };}fn elfBinding(binding: SymbolBinding) u8 { return switch (binding) { .local => std.elf.STB_LOCAL, .global => std.elf.STB_GLOBAL, };}fn elfSymbolKind(kind: SymbolKind) u8 { return switch (kind) { .none => std.elf.STT_NOTYPE, .section => std.elf.STT_SECTION, .function => std.elf.STT_FUNC, .object => std.elf.STT_OBJECT, };}fn x86_64RelocationType(relocation: Relocation) ObjectError!u64 { return switch (relocation.kind) { .call => switch (relocation.width_bits) { 64 => @as(u64, @backingInt(std.elf.R_X86_64.@"64")), 32 => @as(u64, @backingInt(std.elf.R_X86_64.PLT32)), else => error.UnsupportedRelocation, }, .absolute => switch (relocation.width_bits) { 64 => @as(u64, @backingInt(std.elf.R_X86_64.@"64")), 32 => @as(u64, @backingInt(std.elf.R_X86_64.@"32")), else => error.UnsupportedRelocation, }, .relative => switch (relocation.width_bits) { 32 => @as(u64, @backingInt(std.elf.R_X86_64.PC32)), else => error.UnsupportedRelocation, }, .plt => switch (relocation.width_bits) { 32 => @as(u64, @backingInt(std.elf.R_X86_64.PLT32)), else => error.UnsupportedRelocation, }, .got => switch (relocation.width_bits) { 32 => @as(u64, @backingInt(std.elf.R_X86_64.GOTPCREL)), else => error.UnsupportedRelocation, }, else => error.UnsupportedRelocation, };}fn x86_64RelocationAddend(relocation: Relocation) i64 { if (relocation.addend != 0) return relocation.addend; return switch (relocation.kind) { .call, .plt => if (relocation.width_bits == 32) -4 else 0, else => 0, };}fn writeU16(buffer: []u8, offset: usize, value: u16) void { std.mem.writeInt(u16, buffer[offset..][0..2], value, .little);}fn writeU32(buffer: []u8, offset: usize, value: u32) void { std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);}fn writeU64(buffer: []u8, offset: usize, value: u64) void { std.mem.writeInt(u64, buffer[offset..][0..8], value, .little);}fn readU16(bytes: []const u8, offset: usize) u16 { return std.mem.readInt(u16, bytes[offset..][0..2], .little);}fn readU32(bytes: []const u8, offset: usize) u32 { return std.mem.readInt(u32, bytes[offset..][0..4], .little);}fn readU64(bytes: []const u8, offset: usize) u64 { return std.mem.readInt(u64, bytes[offset..][0..8], .little);}const TestSection = struct { index: u16, name: []const u8, header_offset: usize, offset: usize, size: usize, section_type: u32, link: u32, info: u32, entry_size: usize,};fn findTestSection(object: []const u8, name: []const u8) ?TestSection { const shoff: usize = @intCast(readU64(object, 40)); const shnum = readU16(object, 60); const shstrndx = readU16(object, 62); const shstr_header = shoff + @as(usize, shstrndx) * shdr_size; const shstr_offset: usize = @intCast(readU64(object, shstr_header + 24)); const shstr_size: usize = @intCast(readU64(object, shstr_header + 32)); const shstrtab = object[shstr_offset .. shstr_offset + shstr_size]; for (0..shnum) |index| { const header_offset = shoff + index * shdr_size; const name_offset = readU32(object, header_offset); const actual_name = stringFromTable(shstrtab, name_offset); if (!std.mem.eql(u8, actual_name, name)) continue; return .{ .index = @intCast(index), .name = actual_name, .header_offset = header_offset, .offset = @intCast(readU64(object, header_offset + 24)), .size = @intCast(readU64(object, header_offset + 32)), .section_type = readU32(object, header_offset + 4), .link = readU32(object, header_offset + 40), .info = readU32(object, header_offset + 44), .entry_size = @intCast(readU64(object, header_offset + 56)), }; } return null;}fn testSymbolName(object: []const u8, symtab: TestSection, symbol_index: usize) []const u8 { const shoff: usize = @intCast(readU64(object, 40)); const strtab_header = shoff + @as(usize, symtab.link) * shdr_size; const strtab_offset: usize = @intCast(readU64(object, strtab_header + 24)); const strtab_size: usize = @intCast(readU64(object, strtab_header + 32)); const strtab = object[strtab_offset .. strtab_offset + strtab_size]; const symbol_offset = symtab.offset + symbol_index * sym_size; return stringFromTable(strtab, readU32(object, symbol_offset));}fn stringFromTable(table: []const u8, offset: u32) []const u8 { if (offset >= table.len) return ""; const start: usize = @intCast(offset); const end = std.mem.indexOfScalarPos(u8, table, start, 0) orelse table.len; return table[start..end];}test "ELF64 objects declare their stack execution requirement" { for ([_]bool{ false, true }) |executable| { const bytes = try buildX86_64MachineCodeObject(std.testing.allocator, .{ .entry_symbol = "value", .code = &.{0xc3}, .executable_stack = executable, }); defer std.testing.allocator.free(bytes); const section = findTestSection(bytes, ".note.GNU-stack").?; try std.testing.expectEqual(std.elf.SHT_PROGBITS, section.section_type); try std.testing.expectEqual(@as(usize, 0), section.size); const flags = readU64(bytes, section.header_offset + 8); try std.testing.expectEqual( @as(u64, if (executable) std.elf.SHF_EXECINSTR else 0), flags, ); }}test "ELF64 object layout rejects unrepresentable section offsets before allocation" { var sections = Sections.init( .{ .entry_symbol = "value", .text = &.{0xc3} }, .{}, .{}, .{}, ); sections.headers[sections.text].size = std.math.maxInt(usize); var symbols = try Symbols.init(std.testing.allocator); defer symbols.deinit(std.testing.allocator); try std.testing.expectError(error.ObjectSizeOverflow, sections.place(&symbols, .{}));}test "ELF64 relocatable object records x86_64 call slots" { const code = [_]u8{ 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xd0, 0xc3, }; const relocations = [_]machine.CallRelocation{ .{ .offset = 2, .target = "__tiny_runtime_call" }, }; const object = try buildX86_64MachineCodeObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .code = &code, .relocations = &relocations, }); defer std.testing.allocator.free(object); try std.testing.expectEqualSlices(u8, std.elf.MAGIC, object[0..4]); try std.testing.expectEqual(@backingInt(std.elf.ET.REL), readU16(object, 16)); try std.testing.expectEqual(@backingInt(std.elf.EM.X86_64), readU16(object, 18)); const text = findTestSection(object, ".text").?; try std.testing.expectEqual(std.elf.SHT_PROGBITS, text.section_type); try std.testing.expectEqualSlices(u8, &code, object[text.offset .. text.offset + text.size]); const rela_text = findTestSection(object, ".rela.text").?; try std.testing.expectEqual(std.elf.SHT_RELA, rela_text.section_type); try std.testing.expectEqual(@as(u32, text.index), rela_text.info); try std.testing.expectEqual(@as(usize, rela_size), rela_text.entry_size); try std.testing.expectEqual(@as(u64, 2), readU64(object, rela_text.offset)); const info = readU64(object, rela_text.offset + 8); const symbol_index: usize = @intCast(info >> 32); try std.testing.expectEqual(@as(u32, @backingInt(std.elf.R_X86_64.@"64")), @as(u32, @truncate(info))); try std.testing.expectEqual(@as(u64, 0), readU64(object, rela_text.offset + 16)); const symtab = findTestSection(object, ".symtab").?; try std.testing.expectEqualStrings("__tiny_runtime_call", testSymbolName(object, symtab, symbol_index));}test "ELF64 relocatable object supports direct PLT32 call relocations" { const object = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .text = &.{ 0xe8, 0, 0, 0, 0, 0xc3 }, .relocations = &.{.{ .offset = 1, .symbol = "__tiny_runtime_call", .kind = .call, .width_bits = 32, }}, }); defer std.testing.allocator.free(object); const rela_text = findTestSection(object, ".rela.text").?; const info = readU64(object, rela_text.offset + 8); try std.testing.expectEqual(@as(u32, @backingInt(std.elf.R_X86_64.PLT32)), @as(u32, @truncate(info))); try std.testing.expectEqual(@as(u64, @bitCast(@as(i64, -4))), readU64(object, rela_text.offset + 16));}test "ELF64 relocatable object defines multiple text symbols" { const text_symbols = [_]TextSymbol{ .{ .name = "tiny_entry", .offset = 0, .size = 4 }, .{ .name = "tiny_helper", .offset = 16, .size = 3 }, }; const object = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .text = &.{ 0x90, 0x90, 0x90, 0xc3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x90, 0x90, 0xc3, }, .text_symbols = &text_symbols, .relocations = &.{.{ .offset = 1, .symbol = "tiny_helper", .kind = .call, .width_bits = 32, }}, }); defer std.testing.allocator.free(object); const text = findTestSection(object, ".text").?; const symtab = findTestSection(object, ".symtab").?; var found_entry = false; var found_helper = false; const symbol_count = symtab.size / sym_size; for (0..symbol_count) |index| { const symbol_offset = symtab.offset + index * sym_size; const symbol_name = testSymbolName(object, symtab, index); if (std.mem.eql(u8, symbol_name, "tiny_entry")) { found_entry = true; try std.testing.expectEqual(text.index, readU16(object, symbol_offset + 6)); try std.testing.expectEqual(@as(u64, 0), readU64(object, symbol_offset + 8)); try std.testing.expectEqual(@as(u64, 4), readU64(object, symbol_offset + 16)); } if (std.mem.eql(u8, symbol_name, "tiny_helper")) { found_helper = true; try std.testing.expectEqual(text.index, readU16(object, symbol_offset + 6)); try std.testing.expectEqual(@as(u64, 16), readU64(object, symbol_offset + 8)); try std.testing.expectEqual(@as(u64, 3), readU64(object, symbol_offset + 16)); } } try std.testing.expect(found_entry); try std.testing.expect(found_helper);}test "ELF64 relocatable object lays out rodata symbols" { const data_symbols = [_]machine.DataSymbol{ .{ .name = ".Lstring0", .bytes = "abc", .alignment = 8 }, .{ .name = ".Lsymbol0", .bytes = "xy", .alignment = 4 }, }; const object = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .text = &.{0xc3}, .data_symbols = &data_symbols, }); defer std.testing.allocator.free(object); const rodata = findTestSection(object, ".rodata").?; try std.testing.expectEqual(std.elf.SHT_PROGBITS, rodata.section_type); try std.testing.expectEqual(@as(usize, 8), readU64(object, rodata.header_offset + 48)); try std.testing.expectEqualSlices(u8, "abc\x00xy", object[rodata.offset .. rodata.offset + rodata.size]); const symtab = findTestSection(object, ".symtab").?; var found_string = false; var found_symbol = false; const symbol_count = symtab.size / sym_size; for (0..symbol_count) |index| { const symbol_offset = symtab.offset + index * sym_size; const symbol_name = testSymbolName(object, symtab, index); if (std.mem.eql(u8, symbol_name, ".Lstring0")) { found_string = true; try std.testing.expectEqual(rodata.index, readU16(object, symbol_offset + 6)); try std.testing.expectEqual(@as(u64, 0), readU64(object, symbol_offset + 8)); try std.testing.expectEqual(@as(u64, 3), readU64(object, symbol_offset + 16)); } if (std.mem.eql(u8, symbol_name, ".Lsymbol0")) { found_symbol = true; try std.testing.expectEqual(rodata.index, readU16(object, symbol_offset + 6)); try std.testing.expectEqual(@as(u64, 4), readU64(object, symbol_offset + 8)); try std.testing.expectEqual(@as(u64, 2), readU64(object, symbol_offset + 16)); } } try std.testing.expect(found_string); try std.testing.expect(found_symbol);}test "ELF64 relocatable object can export rodata symbols" { const data_symbols = [_]machine.DataSymbol{ .{ .name = "__tiny_aot_runtime_import_count", .bytes = "\x01\x00\x00\x00\x00\x00\x00\x00", .alignment = 8, .binding = .global, }, }; const object = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .text = &.{0xc3}, .data_symbols = &data_symbols, }); defer std.testing.allocator.free(object); const rodata = findTestSection(object, ".rodata").?; const symtab = findTestSection(object, ".symtab").?; var found_symbol = false; const symbol_count = symtab.size / sym_size; for (0..symbol_count) |index| { const symbol_offset = symtab.offset + index * sym_size; const symbol_name = testSymbolName(object, symtab, index); if (std.mem.eql(u8, symbol_name, "__tiny_aot_runtime_import_count")) { found_symbol = true; try std.testing.expectEqual(rodata.index, readU16(object, symbol_offset + 6)); try std.testing.expectEqual(std.elf.STB_GLOBAL, object[symbol_offset + 4] >> 4); try std.testing.expectEqual(std.elf.STT_OBJECT, object[symbol_offset + 4] & 0xf); } } try std.testing.expect(found_symbol);}test "ELF64 relocatable object relocates text slots to rodata symbols" { const data_symbols = [_]machine.DataSymbol{ .{ .name = ".Lstring0", .bytes = "abc", .alignment = 1 }, }; const object = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "tiny_entry", .text = &.{ 0x48, 0xb8, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xc3 }, .data_symbols = &data_symbols, .relocations = &.{.{ .offset = 2, .symbol = ".Lstring0", .kind = .absolute, .width_bits = 64, }}, }); defer std.testing.allocator.free(object); const text = findTestSection(object, ".text").?; try std.testing.expectEqualSlices( u8, &.{ 0, 0, 0, 0, 0, 0, 0, 0 }, object[text.offset + 2 .. text.offset + 10], ); const rela_text = findTestSection(object, ".rela.text").?; const info = readU64(object, rela_text.offset + 8); const symbol_index: usize = @intCast(info >> 32); try std.testing.expectEqual( @as(u32, @backingInt(std.elf.R_X86_64.@"64")), @as(u32, @truncate(info)), ); try std.testing.expectEqual(@as(u64, 0), readU64(object, rela_text.offset + 16)); const symtab = findTestSection(object, ".symtab").?; try std.testing.expectEqualStrings(".Lstring0", testSymbolName(object, symtab, symbol_index));}/// One object the data-section tests agree on. A `.rodata` word holds the address of a `.data`/// word, that word holds the address of a second `.data` word, a `.bss` reservation sits beside/// them, and `_start` walks the chain and exits with what it finds. Every section and every/// relocation direction the writer gained appears once, so the structural check and the run/// check read the same artifact rather than two artifacts that might drift.const DataSectionProbe = struct { const counter_initial: u8 = 40; const counter_increment: u8 = 2; const expected_status: u8 = counter_initial + counter_increment; const exit_syscall: u8 = 60; const reservation_size: usize = 16; const word_alignment: usize = 8; /// `.text` byte offsets of the two slots the linker fills with an absolute address. const counter_ptr_slot: u64 = 2; const scratch_slot: u64 = 25; /// `.data` byte offset of `counter_alias`, which is the second word of the section. const alias_slot: u64 = 8; const text = [_]u8{ 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x00, 0x48, 0x83, 0x00, counter_increment, 0x48, 0x8b, 0x38, 0x48, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x03, 0x3b, 0x48, 0xc7, 0xc0, exit_syscall, 0x00, 0x00, 0x00, 0x0f, 0x05, }; const zero_word: [8]u8 = @splat(0); const counter_word = [_]u8{ counter_initial, 0, 0, 0, 0, 0, 0, 0 }; const data_symbols = [_]machine.DataSymbol{ .{ .name = "counter_ptr", .bytes = &zero_word, .alignment = word_alignment, .binding = .global, .section = .rodata, }, .{ .name = "counter", .bytes = &counter_word, .alignment = word_alignment, .binding = .global, .section = .data, }, .{ .name = "counter_alias", .bytes = &zero_word, .alignment = word_alignment, .binding = .global, .section = .data, }, .{ .name = "scratch", .reserved_size = reservation_size, .alignment = word_alignment, .binding = .global, .section = .bss, }, }; const relocations = [_]Relocation{ .{ .offset = counter_ptr_slot, .symbol = "counter_ptr", .kind = .absolute }, .{ .offset = scratch_slot, .symbol = "scratch", .kind = .absolute }, .{ .section = ".rodata", .offset = 0, .symbol = "counter_alias", .kind = .absolute }, .{ .section = ".data", .offset = alias_slot, .symbol = "counter", .kind = .absolute }, }; fn build(allocator: Allocator) ObjectError![]u8 { return buildRelocatableObject(allocator, .{ .entry_symbol = "_start", .text = &text, .data_symbols = &data_symbols, .relocations = &relocations, }); }};fn expectSectionFlags(object: []const u8, section: TestSection, flags: u64) !void { try std.testing.expectEqual(flags, readU64(object, section.header_offset + 8));}test "ELF64 relocatable object carries writable data beside a zero reservation" { const object = try DataSectionProbe.build(std.testing.allocator); defer std.testing.allocator.free(object); const data = findTestSection(object, ".data").?; try std.testing.expectEqual(std.elf.SHT_PROGBITS, data.section_type); try expectSectionFlags(object, data, std.elf.SHF_ALLOC | std.elf.SHF_WRITE); try std.testing.expectEqual(@as(usize, 16), data.size); try std.testing.expectEqual( @as(u64, DataSectionProbe.counter_initial), readU64(object, data.offset), ); const bss = findTestSection(object, ".bss").?; try std.testing.expectEqual(std.elf.SHT_NOBITS, bss.section_type); try expectSectionFlags(object, bss, std.elf.SHF_ALLOC | std.elf.SHF_WRITE); try std.testing.expectEqual(DataSectionProbe.reservation_size, bss.size); const rodata = findTestSection(object, ".rodata").?; try expectSectionFlags(object, rodata, std.elf.SHF_ALLOC); const symtab = findTestSection(object, ".symtab").?; var counter_section: u16 = 0; var scratch_section: u16 = 0; var scratch_value: u64 = 0; for (0..symtab.size / sym_size) |index| { const record = symtab.offset + index * sym_size; const name = testSymbolName(object, symtab, index); if (std.mem.eql(u8, name, "counter")) { counter_section = readU16(object, record + 6); const info = (@as(u8, std.elf.STB_GLOBAL) << 4) | @as(u8, std.elf.STT_OBJECT); try std.testing.expectEqual(info, object[record + 4]); } if (std.mem.eql(u8, name, "scratch")) { scratch_section = readU16(object, record + 6); scratch_value = readU64(object, record + 8); const size = readU64(object, record + 16); try std.testing.expectEqual(@as(u64, DataSectionProbe.reservation_size), size); } } try std.testing.expectEqual(data.index, counter_section); try std.testing.expectEqual(bss.index, scratch_section); try std.testing.expectEqual(@as(u64, 0), scratch_value);}test "ELF64 bss reservations cost no file bytes" { const text = [_]u8{0xc3}; const small = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "_start", .text = &text, .data_symbols = &.{ .{ .name = "scratch", .reserved_size = 16, .alignment = 8, .section = .bss }, }, }); defer std.testing.allocator.free(small); const huge_size = 1 << 20; const huge = try buildRelocatableObject(std.testing.allocator, .{ .entry_symbol = "_start", .text = &text, .data_symbols = &.{ .{ .name = "scratch", .reserved_size = huge_size, .alignment = 8, .section = .bss }, }, }); defer std.testing.allocator.free(huge); try std.testing.expectEqual(small.len, huge.len); try std.testing.expectEqual(@as(usize, huge_size), findTestSection(huge, ".bss").?.size);}test "ELF64 relocatable object relocates from rodata and from data" { const object = try DataSectionProbe.build(std.testing.allocator); defer std.testing.allocator.free(object); const symtab = findTestSection(object, ".symtab").?; const absolute_64: u32 = @backingInt(std.elf.R_X86_64.@"64"); const rela_text = findTestSection(object, ".rela.text").?; try std.testing.expectEqual(@as(u32, findTestSection(object, ".text").?.index), rela_text.info); try std.testing.expectEqual(@as(usize, 2 * rela_size), rela_text.size); const rela_rodata = findTestSection(object, ".rela.rodata").?; const rodata_index = findTestSection(object, ".rodata").?.index; try std.testing.expectEqual(@as(u32, rodata_index), rela_rodata.info); try std.testing.expectEqual(@as(usize, rela_size), rela_rodata.size); try std.testing.expectEqual(@as(u64, 0), readU64(object, rela_rodata.offset)); const rodata_info = readU64(object, rela_rodata.offset + 8); try std.testing.expectEqual(absolute_64, @as(u32, @truncate(rodata_info))); try std.testing.expectEqualStrings( "counter_alias", testSymbolName(object, symtab, @intCast(rodata_info >> 32)), ); const rela_data = findTestSection(object, ".rela.data").?; try std.testing.expectEqual(@as(u32, findTestSection(object, ".data").?.index), rela_data.info); try std.testing.expectEqual(@as(usize, rela_size), rela_data.size); try std.testing.expectEqual(DataSectionProbe.alias_slot, readU64(object, rela_data.offset)); const data_info = readU64(object, rela_data.offset + 8); try std.testing.expectEqual(absolute_64, @as(u32, @truncate(data_info))); try std.testing.expectEqualStrings( "counter", testSymbolName(object, symtab, @intCast(data_info >> 32)), ); try std.testing.expectEqual(@as(u32, symtab.index), rela_rodata.link); try std.testing.expectEqual(@as(u32, symtab.index), rela_data.link);}test "ELF64 writer refuses a relocation into a section with no file bytes" { try std.testing.expectError(error.UnsupportedRelocation, buildRelocatableObject( std.testing.allocator, .{ .entry_symbol = "_start", .text = &.{0xc3}, .data_symbols = &.{ .{ .name = "scratch", .reserved_size = 8, .alignment = 8, .section = .bss }, }, .relocations = &.{.{ .section = ".bss", .offset = 0, .symbol = "scratch", .kind = .absolute, }}, }, ));}/// Where a caller names the `tldr-link` binary. The linker is another package's artifact and/// nothing in this package's build graph produces it, so the gate reads a path rather than/// guessing one, and reports absence as a skip rather than a pass.const tldr_link_env = "CHOIR_TLDR_LINK";fn expectExitCode(process_io: anytype, argv: []const []const u8, expected: i64) !void { var child = try sys.process.spawn(process_io, .{ .argv = argv, .stdin = .ignore, .stdout = .ignore, .stderr = .inherit, }); defer sys.process.killAndReap(&child, process_io); const termination = try sys.process.wait(&child, process_io); try std.testing.expectEqual(expected, sys.process.exitCode(termination));}test "ELF64 data sections link and run through tldr" { if (!sys.capabilities.current.isLinux()) return error.SkipZigTest; if (!sys.capabilities.current.supportsX86_64Execution()) return error.SkipZigTest; const allocator = std.testing.allocator; const linker = (try sys.env.getOwned(allocator, tldr_link_env)) orelse return error.SkipZigTest; defer allocator.free(linker); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const root = try tmp.parent_dir.realPathFileAlloc( std.Options.debug_io, tmp.sub_path[0..], allocator, ); defer allocator.free(root); const object_path = try std.fs.path.join(allocator, &.{ root, "probe.o" }); defer allocator.free(object_path); const program_path = try std.fs.path.join(allocator, &.{ root, "probe" }); defer allocator.free(program_path); const object = try DataSectionProbe.build(allocator); defer allocator.free(object); try sys.fs.writeFile(object_path, object); var io_state = sys.thread.initThreadedIo(allocator, .{}); defer io_state.deinit(); const process_io = io_state.io(); const link_argv = [_][]const u8{ linker, "-o", program_path, "-e", "_start", object_path }; try expectExitCode(process_io, &link_argv, 0); try expectExitCode(process_io, &.{program_path}, DataSectionProbe.expected_status);}comptime { alloc_phase.capacity.declareDynamicUnbounded("choir.elf_symbols", Symbols);}Source: lib/choir/src/backends/root.zig:8
zig
pub const elf_object = @import("elf.zig");Complete caller list for backends.elf_object.buildRelocatableObject
9 direct callers.
lib.choir.src.backends.elf.DataSectionProbe.build[function] — private source atlib/choir/src/backends/elf.zig:1265in nearest public ownertiny.choir.backends.elf_objecttiny.choir.backends.elf_object.buildX86_64MachineCodeObject[function] atlib/choir/src/backends/elf.zig:67lib.choir.src.backends.elf.test_ELF64_bss_reservations_cost_no_file_bytes[function] — test source atlib/choir/src/backends/elf.zig:1324in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_relocatable_object_can_export_rodata_symbols[function] — test source atlib/choir/src/backends/elf.zig:1126in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_relocatable_object_defines_multiple_text_symbols[function] — test source atlib/choir/src/backends/elf.zig:1036in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_relocatable_object_lays_out_rodata_symbols[function] — test source atlib/choir/src/backends/elf.zig:1085in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_relocatable_object_relocates_text_slots_to_rodata_symbols[function] — test source atlib/choir/src/backends/elf.zig:1160in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_relocatable_object_supports_direct_PLT32_call_relocations[function] — test source atlib/choir/src/backends/elf.zig:1017in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.test_ELF64_writer_refuses_a_relocation_into_a_section_with_no_file_bytes[function] — test source atlib/choir/src/backends/elf.zig:1386in nearest public ownertiny.choir.backends.elf_object
Complete call list for backends.elf_object.buildRelocatableObject
15 direct calls.
lib.choir.src.backends.elf.DataSectionLayout.build[function] — private source atlib/choir/src/backends/elf.zig:528in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Sections.init[function] — private source atlib/choir/src/backends/elf.zig:191in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Sections.place[method] — private source atlib/choir/src/backends/elf.zig:285in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Sections.write[method] — private source atlib/choir/src/backends/elf.zig:322in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Symbols.collect[method] — private source atlib/choir/src/backends/elf.zig:350in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Symbols.deinit[method] — private source atlib/choir/src/backends/elf.zig:340in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.Symbols.init[function] — private source atlib/choir/src/backends/elf.zig:336in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.collectRelocations[function] — private source atlib/choir/src/backends/elf.zig:482in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.copyInto[function] — private source atlib/choir/src/backends/elf.zig:756in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.scrubRelocationSlots[function] — private source atlib/choir/src/backends/elf.zig:787in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.validateAlignment[function] — private source atlib/choir/src/backends/elf.zig:761in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.validateTextSymbol[function] — private source atlib/choir/src/backends/elf.zig:765in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.writeElfHeader[function] — private source atlib/choir/src/backends/elf.zig:684in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.writeRelaRecords[function] — private source atlib/choir/src/backends/elf.zig:747in nearest public ownertiny.choir.backends.elf_objectlib.choir.src.backends.elf.writeSymbolRecords[function] — private source atlib/choir/src/backends/elf.zig:735in nearest public ownertiny.choir.backends.elf_object
Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 24 |
| Version | 26.7.0 |
| Revision | daab053ee433 |