zig/src/link/Elf/Symbol.zig
2023-10-30 19:09:13 +01:00

409 lines
13 KiB
Zig

//! Represents a defined symbol.
/// Allocated address value of this symbol.
value: u64 = 0,
/// Offset into the linker's string table.
name_offset: u32 = 0,
/// Index of file where this symbol is defined.
file_index: File.Index = 0,
/// Index of atom containing this symbol.
/// Index of 0 means there is no associated atom with this symbol.
/// Use `atom` to get the pointer to the atom.
atom_index: Atom.Index = 0,
/// Assigned output section index for this atom.
output_section_index: u16 = 0,
/// Index of the source symbol this symbol references.
/// Use `elfSym` to pull the source symbol from the relevant file.
esym_index: Index = 0,
/// Index of the source version symbol this symbol references if any.
/// If the symbol is unversioned it will have either VER_NDX_LOCAL or VER_NDX_GLOBAL.
version_index: elf.Elf64_Versym = elf.VER_NDX_LOCAL,
/// Misc flags for the symbol packaged as packed struct for compression.
flags: Flags = .{},
extra_index: u32 = 0,
pub fn isAbs(symbol: Symbol, elf_file: *Elf) bool {
const file_ptr = symbol.file(elf_file).?;
if (file_ptr == .shared_object) return symbol.elfSym(elf_file).st_shndx == elf.SHN_ABS;
return !symbol.flags.import and symbol.atom(elf_file) == null and symbol.outputShndx() == null and
file_ptr != .linker_defined;
}
pub fn outputShndx(symbol: Symbol) ?u16 {
if (symbol.output_section_index == 0) return null;
return symbol.output_section_index;
}
pub fn isLocal(symbol: Symbol) bool {
return !(symbol.flags.import or symbol.flags.@"export");
}
pub fn isIFunc(symbol: Symbol, elf_file: *Elf) bool {
return symbol.type(elf_file) == elf.STT_GNU_IFUNC;
}
pub fn @"type"(symbol: Symbol, elf_file: *Elf) u4 {
const esym = symbol.elfSym(elf_file);
const file_ptr = symbol.file(elf_file).?;
if (esym.st_type() == elf.STT_GNU_IFUNC and file_ptr == .shared_object) return elf.STT_FUNC;
return esym.st_type();
}
pub fn name(symbol: Symbol, elf_file: *Elf) [:0]const u8 {
return elf_file.strtab.getAssumeExists(symbol.name_offset);
}
pub fn atom(symbol: Symbol, elf_file: *Elf) ?*Atom {
return elf_file.atom(symbol.atom_index);
}
pub fn file(symbol: Symbol, elf_file: *Elf) ?File {
return elf_file.file(symbol.file_index);
}
pub fn elfSym(symbol: Symbol, elf_file: *Elf) elf.Elf64_Sym {
const file_ptr = symbol.file(elf_file).?;
switch (file_ptr) {
.zig_object => |x| return x.elfSym(symbol.esym_index).*,
.linker_defined => |x| return x.symtab.items[symbol.esym_index],
inline else => |x| return x.symtab[symbol.esym_index],
}
}
pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 {
const file_ptr = symbol.file(elf_file) orelse return std.math.maxInt(u32);
const sym = symbol.elfSym(elf_file);
const in_archive = switch (file_ptr) {
.object => |x| !x.alive,
else => false,
};
return file_ptr.symbolRank(sym, in_archive);
}
pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf) u64 {
if (symbol.flags.has_copy_rel) {
return symbol.copyRelAddress(elf_file);
}
if (symbol.flags.has_plt and opts.plt) {
if (!symbol.flags.is_canonical and symbol.flags.has_got) {
// We have a non-lazy bound function pointer, use that!
return symbol.pltGotAddress(elf_file);
}
// Lazy-bound function it is!
return symbol.pltAddress(elf_file);
}
return symbol.value;
}
pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_got) return 0;
const extras = symbol.extra(elf_file).?;
const entry = elf_file.got.entries.items[extras.got];
return entry.address(elf_file);
}
pub fn pltGotAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!(symbol.flags.has_plt and symbol.flags.has_got)) return 0;
const extras = symbol.extra(elf_file).?;
const shdr = elf_file.shdrs.items[elf_file.plt_got_section_index.?];
return shdr.sh_addr + extras.plt_got * 16;
}
pub fn pltAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_plt) return 0;
const extras = symbol.extra(elf_file).?;
const shdr = elf_file.shdrs.items[elf_file.plt_section_index.?];
return shdr.sh_addr + extras.plt * 16 + PltSection.preamble_size;
}
pub fn gotPltAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_plt) return 0;
const extras = symbol.extra(elf_file).?;
const shdr = elf_file.shdrs.items[elf_file.got_plt_section_index.?];
return shdr.sh_addr + extras.plt * 8 + GotPltSection.preamble_size;
}
pub fn copyRelAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_copy_rel) return 0;
const shdr = elf_file.shdrs.items[elf_file.copy_rel_section_index.?];
return shdr.sh_addr + symbol.value;
}
pub fn tlsGdAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_tlsgd) return 0;
const extras = symbol.extra(elf_file).?;
const entry = elf_file.got.entries.items[extras.tlsgd];
return entry.address(elf_file);
}
pub fn gotTpAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_gottp) return 0;
const extras = symbol.extra(elf_file).?;
const entry = elf_file.got.entries.items[extras.gottp];
return entry.address(elf_file);
}
pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_tlsdesc) return 0;
const extras = symbol.extra(elf_file).?;
const entry = elf_file.got.entries.items[extras.tlsdesc];
return entry.address(elf_file);
}
const GetOrCreateZigGotEntryResult = struct {
found_existing: bool,
index: ZigGotSection.Index,
};
pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateZigGotEntryResult {
if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.zig_got };
const index = try elf_file.zig_got.addSymbol(symbol_index, elf_file);
return .{ .found_existing = false, .index = index };
}
pub fn zigGotAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_zig_got) return 0;
const extras = symbol.extra(elf_file).?;
return elf_file.zig_got.entryAddress(extras.zig_got, elf_file);
}
pub fn dsoAlignment(symbol: Symbol, elf_file: *Elf) !u64 {
const file_ptr = symbol.file(elf_file) orelse return 0;
assert(file_ptr == .shared_object);
const shared_object = file_ptr.shared_object;
const esym = symbol.elfSym(elf_file);
const shdr = shared_object.shdrs.items[esym.st_shndx];
const alignment = @max(1, shdr.sh_addralign);
return if (esym.st_value == 0)
alignment
else
@min(alignment, try std.math.powi(u64, 2, @ctz(esym.st_value)));
}
pub fn addExtra(symbol: *Symbol, extras: Extra, elf_file: *Elf) !void {
symbol.extra_index = try elf_file.addSymbolExtra(extras);
}
pub fn extra(symbol: Symbol, elf_file: *Elf) ?Extra {
return elf_file.symbolExtra(symbol.extra_index);
}
pub fn setExtra(symbol: Symbol, extras: Extra, elf_file: *Elf) void {
elf_file.setSymbolExtra(symbol.extra_index, extras);
}
pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
const file_ptr = symbol.file(elf_file) orelse {
out.* = Elf.null_sym;
return;
};
const esym = symbol.elfSym(elf_file);
const st_type = symbol.type(elf_file);
const st_bind: u8 = blk: {
if (symbol.isLocal()) break :blk 0;
if (symbol.flags.weak) break :blk elf.STB_WEAK;
if (file_ptr == .shared_object) break :blk elf.STB_GLOBAL;
break :blk esym.st_bind();
};
const st_shndx = blk: {
if (symbol.flags.has_copy_rel) break :blk elf_file.copy_rel_section_index.?;
if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
if (symbol.atom(elf_file) == null and file_ptr != .linker_defined)
break :blk elf.SHN_ABS;
break :blk symbol.outputShndx() orelse elf.SHN_UNDEF;
};
const st_value = blk: {
if (symbol.flags.has_copy_rel) break :blk symbol.address(.{}, elf_file);
if (file_ptr == .shared_object or esym.st_shndx == elf.SHN_UNDEF) {
if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file);
break :blk 0;
}
if (st_shndx == elf.SHN_ABS) break :blk symbol.value;
const shdr = &elf_file.shdrs.items[st_shndx];
if (shdr.sh_flags & elf.SHF_TLS != 0 and file_ptr != .linker_defined)
break :blk symbol.value - elf_file.tlsAddress();
break :blk symbol.value;
};
out.* = .{
.st_name = symbol.name_offset,
.st_info = (st_bind << 4) | st_type,
.st_other = esym.st_other,
.st_shndx = st_shndx,
.st_value = st_value,
.st_size = esym.st_size,
};
}
pub fn format(
symbol: Symbol,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = symbol;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format symbols directly");
}
const FormatContext = struct {
symbol: Symbol,
elf_file: *Elf,
};
pub fn fmtName(symbol: Symbol, elf_file: *Elf) std.fmt.Formatter(formatName) {
return .{ .data = .{
.symbol = symbol,
.elf_file = elf_file,
} };
}
fn formatName(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const elf_file = ctx.elf_file;
const symbol = ctx.symbol;
try writer.writeAll(symbol.name(elf_file));
switch (symbol.version_index & elf.VERSYM_VERSION) {
elf.VER_NDX_LOCAL, elf.VER_NDX_GLOBAL => {},
else => {
const file_ptr = symbol.file(elf_file).?;
assert(file_ptr == .shared_object);
const shared_object = file_ptr.shared_object;
try writer.print("@{s}", .{shared_object.versionString(symbol.version_index)});
},
}
}
pub fn fmt(symbol: Symbol, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.symbol = symbol,
.elf_file = elf_file,
} };
}
fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const symbol = ctx.symbol;
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) {
try writer.writeAll(" : undef");
} else {
try writer.writeAll(" : absolute");
}
} else if (symbol.outputShndx()) |shndx| {
try writer.print(" : shdr({d})", .{shndx});
}
if (symbol.atom(ctx.elf_file)) |atom_ptr| {
try writer.print(" : atom({d})", .{atom_ptr.atom_index});
}
var buf: [2]u8 = .{'_'} ** 2;
if (symbol.flags.@"export") buf[0] = 'E';
if (symbol.flags.import) buf[1] = 'I';
try writer.print(" : {s}", .{&buf});
if (symbol.flags.weak) try writer.writeAll(" : weak");
switch (file_ptr) {
inline else => |x| try writer.print(" : {s}({d})", .{ @tagName(file_ptr), x.index }),
}
} else try writer.writeAll(" : unresolved");
}
pub const Flags = packed struct {
/// Whether the symbol is imported at runtime.
import: bool = false,
/// Whether the symbol is exported at runtime.
@"export": bool = false,
/// Whether this symbol is weak.
weak: bool = false,
/// Whether the symbol makes into the output symtab.
output_symtab: bool = false,
/// Whether the symbol has entry in dynamic symbol table.
has_dynamic: bool = false,
/// Whether the symbol contains GOT indirection.
needs_got: bool = false,
has_got: bool = false,
/// Whether the symbol contains PLT indirection.
needs_plt: bool = false,
has_plt: bool = false,
/// Whether the PLT entry is canonical.
is_canonical: bool = false,
/// Whether the symbol contains COPYREL directive.
needs_copy_rel: bool = false,
has_copy_rel: bool = false,
/// Whether the symbol contains TLSGD indirection.
needs_tlsgd: bool = false,
has_tlsgd: bool = false,
/// Whether the symbol contains GOTTP indirection.
needs_gottp: bool = false,
has_gottp: bool = false,
/// Whether the symbol contains TLSDESC indirection.
needs_tlsdesc: bool = false,
has_tlsdesc: bool = false,
/// Whether the symbol contains .zig.got indirection.
has_zig_got: bool = false,
};
pub const Extra = struct {
got: u32 = 0,
plt: u32 = 0,
plt_got: u32 = 0,
dynamic: u32 = 0,
copy_rel: u32 = 0,
tlsgd: u32 = 0,
gottp: u32 = 0,
tlsdesc: u32 = 0,
zig_got: u32 = 0,
};
pub const Index = u32;
const assert = std.debug.assert;
const elf = std.elf;
const std = @import("std");
const synthetic_sections = @import("synthetic_sections.zig");
const Atom = @import("Atom.zig");
const Elf = @import("../Elf.zig");
const File = @import("file.zig").File;
const GotSection = synthetic_sections.GotSection;
const GotPltSection = synthetic_sections.GotPltSection;
const LinkerDefined = @import("LinkerDefined.zig");
const Object = @import("Object.zig");
const PltSection = synthetic_sections.PltSection;
const SharedObject = @import("SharedObject.zig");
const Symbol = @This();
const ZigGotSection = synthetic_sections.ZigGotSection;