Files
zig/src/link/MachO/Symbol.zig

428 lines
13 KiB
Zig

//! Represents a defined symbol.
/// Allocated address value of this symbol.
value: u64 = 0,
/// Offset into the linker's intern table.
name: u32 = 0,
/// File where this symbol is defined.
file: File.Index = 0,
/// Atom containing this symbol if any.
/// Index of 0 means there is no associated atom with this symbol.
/// Use `getAtom` to get the pointer to the atom.
atom: Atom.Index = 0,
/// Assigned output section index for this symbol.
out_n_sect: u8 = 0,
/// Index of the source nlist this symbol references.
/// Use `getNlist` to pull the nlist from the relevant file.
nlist_idx: Index = 0,
/// Misc flags for the symbol packaged as packed struct for compression.
flags: Flags = .{},
visibility: Visibility = .local,
extra: u32 = 0,
pub fn isLocal(symbol: Symbol) bool {
return !(symbol.flags.import or symbol.flags.@"export");
}
pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool {
const file = symbol.getFile(macho_file) orelse return false;
return switch (file) {
.object => symbol.getNlist(macho_file).stab(),
else => false,
};
}
pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool {
const name = symbol.getName(macho_file);
return std.mem.indexOf(u8, name, "$tlv$init") != null;
}
pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
const file = symbol.getFile(macho_file).?;
const is_dylib_weak = switch (file) {
.dylib => |x| x.weak,
else => false,
};
return is_dylib_weak or symbol.flags.weak_ref;
}
pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
if (symbol.flags.global) return macho_file.strings.getAssumeExists(symbol.name);
return switch (symbol.getFile(macho_file).?) {
.dylib => unreachable, // There are no local symbols for dylibs
.zig_object => |x| x.strtab.getAssumeExists(symbol.name),
inline else => |x| x.getString(symbol.name),
};
}
pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
return macho_file.getAtom(symbol.atom);
}
pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
return macho_file.getFile(symbol.file);
}
/// Asserts file is an object.
pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
const file = symbol.getFile(macho_file).?;
return switch (file) {
.object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
else => unreachable,
};
}
pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 {
const file = symbol.getFile(macho_file).?;
assert(file == .object);
return file.object.symtab.items(.size)[symbol.nlist_idx];
}
pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 {
assert(symbol.flags.import);
const file = symbol.getFile(macho_file) orelse return null;
return switch (file) {
.dylib => |x| x.ordinal,
else => null,
};
}
pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 {
const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32);
const in_archive = switch (file) {
.object => |x| !x.alive,
else => false,
};
return file.getSymbolRank(.{
.archive = in_archive,
.weak = symbol.flags.weak,
.tentative = symbol.flags.tentative,
});
}
pub fn getAddress(symbol: Symbol, opts: struct {
stubs: bool = true,
}, macho_file: *MachO) u64 {
if (opts.stubs) {
if (symbol.flags.stubs) {
return symbol.getStubsAddress(macho_file);
} else if (symbol.flags.objc_stubs) {
return symbol.getObjcStubsAddress(macho_file);
}
}
if (symbol.getAtom(macho_file)) |atom| return atom.getAddress(macho_file) + symbol.value;
return symbol.value;
}
pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.has_got) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.got.getAddress(extra.got, macho_file);
}
pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.stubs.getAddress(extra.stubs, macho_file);
}
pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
}
pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?;
const atom = macho_file.getAtom(extra.objc_selrefs).?;
assert(atom.flags.alive);
return atom.getAddress(macho_file);
}
pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.tlv_ptr) return 0;
const extra = symbol.getExtra(macho_file).?;
return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
}
const GetOrCreateZigGotEntryResult = struct {
found_existing: bool,
index: ZigGotSection.Index,
};
pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
assert(!macho_file.base.isRelocatable());
assert(symbol.flags.needs_zig_got);
if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got };
const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
return .{ .found_existing = false, .index = index };
}
pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.has_zig_got) return 0;
const extras = symbol.getExtra(macho_file).?;
return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
}
pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
if (!symbol.flags.output_symtab) return null;
assert(!symbol.isSymbolStab(macho_file));
const file = symbol.getFile(macho_file).?;
const symtab_ctx = switch (file) {
inline else => |x| x.output_symtab_ctx,
};
var idx = symbol.getExtra(macho_file).?.symtab;
if (symbol.isLocal()) {
idx += symtab_ctx.ilocal;
} else if (symbol.flags.@"export") {
idx += symtab_ctx.iexport;
} else {
assert(symbol.flags.import);
idx += symtab_ctx.iimport;
}
return idx;
}
const AddExtraOpts = struct {
got: ?u32 = null,
zig_got: ?u32 = null,
stubs: ?u32 = null,
objc_stubs: ?u32 = null,
objc_selrefs: ?u32 = null,
tlv_ptr: ?u32 = null,
symtab: ?u32 = null,
};
pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
if (symbol.getExtra(macho_file) == null) {
symbol.extra = try macho_file.addSymbolExtra(.{});
}
var extra = symbol.getExtra(macho_file).?;
inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
if (@field(opts, field.name)) |x| {
@field(extra, field.name) = x;
}
}
symbol.setExtra(extra, macho_file);
}
pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra {
return macho_file.getSymbolExtra(symbol.extra);
}
pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
macho_file.setSymbolExtra(symbol.extra, extra);
}
pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
if (symbol.isLocal()) {
out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
out.n_desc = 0;
out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
switch (symbol.visibility) {
.hidden => out.n_type |= macho.N_PEXT,
else => {},
}
} else if (symbol.flags.@"export") {
assert(symbol.visibility == .global);
out.n_type = macho.N_EXT;
out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1);
out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
out.n_desc = 0;
if (symbol.flags.weak) {
out.n_desc |= macho.N_WEAK_DEF;
}
if (symbol.flags.dyn_ref) {
out.n_desc |= macho.REFERENCED_DYNAMICALLY;
}
} else {
assert(symbol.visibility == .global);
out.n_type = macho.N_EXT;
out.n_sect = 0;
out.n_value = 0;
out.n_desc = 0;
// TODO:
// const ord: u16 = if (macho_file.options.namespace == .flat)
// @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP))
// else if (symbol.getDylibOrdinal(macho_file)) |ord|
// ord
// else
// macho.BIND_SPECIAL_DYLIB_SELF;
const ord: u16 = if (symbol.getDylibOrdinal(macho_file)) |ord|
ord
else
macho.BIND_SPECIAL_DYLIB_SELF;
out.n_desc = macho.N_SYMBOL_RESOLVER * ord;
if (symbol.flags.weak) {
out.n_desc |= macho.N_WEAK_DEF;
}
if (symbol.weakRef(macho_file)) {
out.n_desc |= macho.N_WEAK_REF;
}
}
}
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,
macho_file: *MachO,
};
pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Formatter(format2) {
return .{ .data = .{
.symbol = symbol,
.macho_file = macho_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.nlist_idx,
symbol.getName(ctx.macho_file),
symbol.getAddress(.{}, ctx.macho_file),
});
if (symbol.getFile(ctx.macho_file)) |file| {
if (symbol.out_n_sect != 0) {
try writer.print(" : sect({d})", .{symbol.out_n_sect});
}
if (symbol.getAtom(ctx.macho_file)) |atom| {
try writer.print(" : atom({d})", .{atom.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");
if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab");
switch (file) {
.zig_object => |x| try writer.print(" : zig_object({d})", .{x.index}),
.internal => |x| try writer.print(" : internal({d})", .{x.index}),
.object => |x| try writer.print(" : object({d})", .{x.index}),
.dylib => |x| try writer.print(" : dylib({d})", .{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 the symbol is effectively an extern and takes part in global
/// symbol resolution. Then, its name will be saved in global string interning
/// table.
global: bool = false,
/// Whether this symbol is weak.
weak: bool = false,
/// Whether this symbol is weakly referenced.
weak_ref: bool = false,
/// Whether this symbol is dynamically referenced.
dyn_ref: bool = false,
/// Whether this symbol was marked as N_NO_DEAD_STRIP.
no_dead_strip: bool = false,
/// Whether this symbol can be interposed at runtime.
interposable: bool = false,
/// Whether this symbol is absolute.
abs: bool = false,
/// Whether this symbol is a tentative definition.
tentative: bool = false,
/// Whether this symbol is a thread-local variable.
tlv: bool = false,
/// Whether the symbol makes into the output symtab or not.
output_symtab: bool = false,
/// Whether the symbol contains __got indirection.
needs_got: bool = false,
has_got: bool = false,
/// Whether the symbol contains __got_zig indirection.
needs_zig_got: bool = false,
has_zig_got: bool = false,
/// Whether the symbols contains __stubs indirection.
stubs: bool = false,
/// Whether the symbol has a TLV pointer.
tlv_ptr: bool = false,
/// Whether the symbol contains __objc_stubs indirection.
objc_stubs: bool = false,
};
pub const Visibility = enum {
global,
hidden,
local,
};
pub const Extra = struct {
got: u32 = 0,
zig_got: u32 = 0,
stubs: u32 = 0,
objc_stubs: u32 = 0,
objc_selrefs: u32 = 0,
tlv_ptr: u32 = 0,
symtab: u32 = 0,
};
pub const Index = u32;
const assert = std.debug.assert;
const macho = std.macho;
const std = @import("std");
const Atom = @import("Atom.zig");
const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Nlist = Object.Nlist;
const Object = @import("Object.zig");
const Symbol = @This();
const ZigGotSection = @import("synthetic.zig").ZigGotSection;