| 1 | //! Represents a defined symbol. |
| 2 | |
| 3 | /// Allocated address value of this symbol. |
| 4 | value: u64 = 0, |
| 5 | |
| 6 | /// Offset into the linker's intern table. |
| 7 | name: MachO.String = .{}, |
| 8 | |
| 9 | /// File where this symbol is defined. |
| 10 | file: File.Index = 0, |
| 11 | |
| 12 | /// Reference to Atom containing this symbol if any. |
| 13 | /// Use `getAtom` to get the pointer to the atom. |
| 14 | atom_ref: MachO.Ref = .{ .index = 0, .file = 0 }, |
| 15 | |
| 16 | /// Assigned output section index for this symbol. |
| 17 | out_n_sect: u8 = 0, |
| 18 | |
| 19 | /// Index of the source nlist this symbol references. |
| 20 | /// Use `getNlist` to pull the nlist from the relevant file. |
| 21 | nlist_idx: u32 = 0, |
| 22 | |
| 23 | /// Misc flags for the symbol packaged as packed struct for compression. |
| 24 | flags: Flags = .{}, |
| 25 | |
| 26 | sect_flags: std.atomic.Value(u8) = std.atomic.Value(u8).init(0), |
| 27 | |
| 28 | visibility: Visibility = .local, |
| 29 | |
| 30 | extra: u32 = 0, |
| 31 | |
| 32 | pub fn isLocal(symbol: Symbol) bool { |
| 33 | return !(symbol.flags.import or symbol.flags.@"export"); |
| 34 | } |
| 35 | |
| 36 | pub fn isSymbolStab(symbol: Symbol, macho_file: *MachO) bool { |
| 37 | const file = symbol.getFile(macho_file) orelse return false; |
| 38 | return switch (file) { |
| 39 | .object => symbol.getNlist(macho_file).n_type.bits.is_stab != 0, |
| 40 | else => false, |
| 41 | }; |
| 42 | } |
| 43 | |
| 44 | pub fn isTlvInit(symbol: Symbol, macho_file: *MachO) bool { |
| 45 | const name = symbol.getName(macho_file); |
| 46 | return std.mem.find(u8, name, "$tlv$init") != null; |
| 47 | } |
| 48 | |
| 49 | pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool { |
| 50 | const file = symbol.getFile(macho_file).?; |
| 51 | const is_dylib_weak = switch (file) { |
| 52 | .dylib => |x| x.weak, |
| 53 | else => false, |
| 54 | }; |
| 55 | return is_dylib_weak or symbol.flags.weak_ref; |
| 56 | } |
| 57 | |
| 58 | pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 { |
| 59 | return switch (symbol.getFile(macho_file).?) { |
| 60 | inline else => |x| x.getString(symbol.name), |
| 61 | }; |
| 62 | } |
| 63 | |
| 64 | pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom { |
| 65 | return symbol.atom_ref.getAtom(macho_file); |
| 66 | } |
| 67 | |
| 68 | pub fn getOutputSectionIndex(symbol: Symbol, macho_file: *MachO) u8 { |
| 69 | if (symbol.getAtom(macho_file)) |atom| return atom.out_n_sect; |
| 70 | return symbol.out_n_sect; |
| 71 | } |
| 72 | |
| 73 | pub fn getSectionFlags(symbol: Symbol) SectionFlags { |
| 74 | return @bitCast(symbol.sect_flags.load(.seq_cst)); |
| 75 | } |
| 76 | |
| 77 | pub fn setSectionFlags(symbol: *Symbol, flags: SectionFlags) void { |
| 78 | _ = symbol.sect_flags.fetchOr(@bitCast(flags), .seq_cst); |
| 79 | } |
| 80 | |
| 81 | pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File { |
| 82 | return macho_file.getFile(symbol.file); |
| 83 | } |
| 84 | |
| 85 | /// Asserts file is an object. |
| 86 | pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 { |
| 87 | const file = symbol.getFile(macho_file).?; |
| 88 | return switch (file) { |
| 89 | .dylib => unreachable, |
| 90 | .zig_object => unreachable, |
| 91 | .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx], |
| 92 | .internal => |x| x.symtab.items[symbol.nlist_idx], |
| 93 | }; |
| 94 | } |
| 95 | |
| 96 | pub fn getSize(symbol: Symbol, macho_file: *MachO) u64 { |
| 97 | const file = symbol.getFile(macho_file).?; |
| 98 | assert(file == .object); |
| 99 | return file.object.symtab.items(.size)[symbol.nlist_idx]; |
| 100 | } |
| 101 | |
| 102 | pub fn getDylibOrdinal(symbol: Symbol, macho_file: *MachO) ?u16 { |
| 103 | assert(symbol.flags.import); |
| 104 | const file = symbol.getFile(macho_file) orelse return null; |
| 105 | return switch (file) { |
| 106 | .dylib => |x| x.ordinal, |
| 107 | else => null, |
| 108 | }; |
| 109 | } |
| 110 | |
| 111 | pub fn getSymbolRank(symbol: Symbol, macho_file: *MachO) u32 { |
| 112 | const file = symbol.getFile(macho_file) orelse return std.math.maxInt(u32); |
| 113 | const in_archive = switch (file) { |
| 114 | .object => |x| !x.alive, |
| 115 | else => false, |
| 116 | }; |
| 117 | return file.getSymbolRank(.{ |
| 118 | .archive = in_archive, |
| 119 | .weak = symbol.flags.weak, |
| 120 | .tentative = symbol.flags.tentative, |
| 121 | }); |
| 122 | } |
| 123 | |
| 124 | pub fn getAddress(symbol: Symbol, opts: struct { |
| 125 | stubs: bool = true, |
| 126 | trampoline: bool = true, |
| 127 | }, macho_file: *MachO) u64 { |
| 128 | if (opts.stubs) { |
| 129 | if (symbol.getSectionFlags().stubs) { |
| 130 | return symbol.getStubsAddress(macho_file); |
| 131 | } else if (symbol.getSectionFlags().objc_stubs) { |
| 132 | return symbol.getObjcStubsAddress(macho_file); |
| 133 | } |
| 134 | } |
| 135 | if (symbol.flags.trampoline and opts.trampoline) { |
| 136 | return symbol.getTrampolineAddress(macho_file); |
| 137 | } |
| 138 | if (symbol.getAtom(macho_file)) |atom| return atom.getAddress(macho_file) + symbol.value; |
| 139 | return symbol.value; |
| 140 | } |
| 141 | |
| 142 | pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 143 | if (!symbol.getSectionFlags().has_got) return 0; |
| 144 | const extra = symbol.getExtra(macho_file); |
| 145 | return macho_file.got.getAddress(extra.got, macho_file); |
| 146 | } |
| 147 | |
| 148 | pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 149 | if (!symbol.getSectionFlags().stubs) return 0; |
| 150 | const extra = symbol.getExtra(macho_file); |
| 151 | return macho_file.stubs.getAddress(extra.stubs, macho_file); |
| 152 | } |
| 153 | |
| 154 | pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 155 | if (!symbol.getSectionFlags().objc_stubs) return 0; |
| 156 | const extra = symbol.getExtra(macho_file); |
| 157 | return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file); |
| 158 | } |
| 159 | |
| 160 | pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 161 | if (!symbol.getSectionFlags().objc_stubs) return 0; |
| 162 | const extra = symbol.getExtra(macho_file); |
| 163 | const file = symbol.getFile(macho_file).?; |
| 164 | return switch (file) { |
| 165 | .dylib, .zig_object => unreachable, |
| 166 | inline else => |x| x.symbols.items[extra.objc_selrefs].getAddress(.{}, macho_file), |
| 167 | }; |
| 168 | } |
| 169 | |
| 170 | pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 171 | if (!symbol.getSectionFlags().tlv_ptr) return 0; |
| 172 | const extra = symbol.getExtra(macho_file); |
| 173 | return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file); |
| 174 | } |
| 175 | |
| 176 | pub fn getTrampolineAddress(symbol: Symbol, macho_file: *MachO) u64 { |
| 177 | if (!symbol.flags.trampoline) return 0; |
| 178 | const zo = macho_file.getZigObject().?; |
| 179 | const index = symbol.getExtra(macho_file).trampoline; |
| 180 | return zo.symbols.items[index].getAddress(.{}, macho_file); |
| 181 | } |
| 182 | |
| 183 | pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 { |
| 184 | if (!symbol.flags.output_symtab) return null; |
| 185 | assert(!symbol.isSymbolStab(macho_file)); |
| 186 | const file = symbol.getFile(macho_file).?; |
| 187 | const symtab_ctx = switch (file) { |
| 188 | inline else => |x| x.output_symtab_ctx, |
| 189 | }; |
| 190 | var idx = symbol.getExtra(macho_file).symtab; |
| 191 | if (symbol.isLocal()) { |
| 192 | idx += symtab_ctx.ilocal; |
| 193 | } else if (symbol.flags.@"export") { |
| 194 | idx += symtab_ctx.iexport; |
| 195 | } else { |
| 196 | assert(symbol.flags.import); |
| 197 | idx += symtab_ctx.iimport; |
| 198 | } |
| 199 | return idx; |
| 200 | } |
| 201 | |
| 202 | const AddExtraOpts = struct { |
| 203 | got: ?u32 = null, |
| 204 | stubs: ?u32 = null, |
| 205 | objc_stubs: ?u32 = null, |
| 206 | objc_selrefs: ?u32 = null, |
| 207 | tlv_ptr: ?u32 = null, |
| 208 | symtab: ?u32 = null, |
| 209 | trampoline: ?u32 = null, |
| 210 | }; |
| 211 | |
| 212 | pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) void { |
| 213 | var extra = symbol.getExtra(macho_file); |
| 214 | inline for (@typeInfo(@TypeOf(opts)).@"struct".field_names) |field_name| { |
| 215 | if (@field(opts, field_name)) |x| { |
| 216 | @field(extra, field_name) = x; |
| 217 | } |
| 218 | } |
| 219 | symbol.setExtra(extra, macho_file); |
| 220 | } |
| 221 | |
| 222 | pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) Extra { |
| 223 | return switch (symbol.getFile(macho_file).?) { |
| 224 | inline else => |x| x.getSymbolExtra(symbol.extra), |
| 225 | }; |
| 226 | } |
| 227 | |
| 228 | pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void { |
| 229 | return switch (symbol.getFile(macho_file).?) { |
| 230 | inline else => |x| x.setSymbolExtra(symbol.extra, extra), |
| 231 | }; |
| 232 | } |
| 233 | |
| 234 | pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void { |
| 235 | if (symbol.isLocal()) { |
| 236 | out.n_type = .{ .bits = .{ |
| 237 | .ext = false, |
| 238 | .type = if (symbol.flags.abs) .abs else .sect, |
| 239 | .pext = false, |
| 240 | .is_stab = 0, |
| 241 | } }; |
| 242 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1); |
| 243 | out.n_desc = @bitCast(@as(u16, 0)); |
| 244 | out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); |
| 245 | |
| 246 | switch (symbol.visibility) { |
| 247 | .hidden => out.n_type.bits.pext = true, |
| 248 | else => {}, |
| 249 | } |
| 250 | } else if (symbol.flags.@"export") { |
| 251 | assert(symbol.visibility == .global); |
| 252 | out.n_type = .{ .bits = .{ |
| 253 | .ext = true, |
| 254 | .type = if (symbol.flags.abs) .abs else .sect, |
| 255 | .pext = false, |
| 256 | .is_stab = 0, |
| 257 | } }; |
| 258 | out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1); |
| 259 | out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); |
| 260 | out.n_desc = @bitCast(@as(u16, 0)); |
| 261 | |
| 262 | if (symbol.flags.weak) { |
| 263 | out.n_desc.weak_def_or_ref_to_weak = true; |
| 264 | } |
| 265 | if (symbol.flags.dyn_ref) { |
| 266 | out.n_desc.referenced_dynamically = true; |
| 267 | } |
| 268 | } else { |
| 269 | assert(symbol.visibility == .global); |
| 270 | out.n_type = .{ .bits = .{ |
| 271 | .ext = true, |
| 272 | .type = .undf, |
| 273 | .pext = false, |
| 274 | .is_stab = 0, |
| 275 | } }; |
| 276 | out.n_sect = 0; |
| 277 | out.n_value = 0; |
| 278 | out.n_desc = @bitCast(@as(u16, 0)); |
| 279 | |
| 280 | // TODO: |
| 281 | // const ord: u16 = if (macho_file.options.namespace == .flat) |
| 282 | // @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)) |
| 283 | // else if (symbol.getDylibOrdinal(macho_file)) |ord| |
| 284 | // ord |
| 285 | // else |
| 286 | // macho.BIND_SPECIAL_DYLIB_SELF; |
| 287 | const ord: u16 = if (symbol.getDylibOrdinal(macho_file)) |ord| |
| 288 | ord |
| 289 | else |
| 290 | macho.BIND_SPECIAL_DYLIB_SELF; |
| 291 | out.n_desc = @bitCast(macho.N_SYMBOL_RESOLVER * ord); |
| 292 | |
| 293 | if (symbol.flags.weak) { |
| 294 | out.n_desc.weak_def_or_ref_to_weak = true; |
| 295 | } |
| 296 | |
| 297 | if (symbol.weakRef(macho_file)) { |
| 298 | out.n_desc.weak_ref = true; |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | pub fn fmt(symbol: Symbol, macho_file: *MachO) std.fmt.Alt(Format, Format.default) { |
| 304 | return .{ .data = .{ |
| 305 | .symbol = symbol, |
| 306 | .macho_file = macho_file, |
| 307 | } }; |
| 308 | } |
| 309 | |
| 310 | const Format = struct { |
| 311 | symbol: Symbol, |
| 312 | macho_file: *MachO, |
| 313 | |
| 314 | fn default(f: Format, w: *Writer) Writer.Error!void { |
| 315 | const symbol = f.symbol; |
| 316 | try w.print("%{d} : {s} : @{x}", .{ |
| 317 | symbol.nlist_idx, |
| 318 | symbol.getName(f.macho_file), |
| 319 | symbol.getAddress(.{}, f.macho_file), |
| 320 | }); |
| 321 | if (symbol.getFile(f.macho_file)) |file| { |
| 322 | if (symbol.getOutputSectionIndex(f.macho_file) != 0) { |
| 323 | try w.print(" : sect({d})", .{symbol.getOutputSectionIndex(f.macho_file)}); |
| 324 | } |
| 325 | if (symbol.getAtom(f.macho_file)) |atom| { |
| 326 | try w.print(" : atom({d})", .{atom.atom_index}); |
| 327 | } |
| 328 | var buf: [3]u8 = @splat('_'); |
| 329 | if (symbol.flags.@"export") buf[0] = 'E'; |
| 330 | if (symbol.flags.import) buf[1] = 'I'; |
| 331 | switch (symbol.visibility) { |
| 332 | .local => buf[2] = 'L', |
| 333 | .hidden => buf[2] = 'H', |
| 334 | .global => buf[2] = 'G', |
| 335 | } |
| 336 | try w.print(" : {s}", .{&buf}); |
| 337 | if (symbol.flags.weak) try w.writeAll(" : weak"); |
| 338 | if (symbol.isSymbolStab(f.macho_file)) try w.writeAll(" : stab"); |
| 339 | switch (file) { |
| 340 | .zig_object => |x| try w.print(" : zig_object({d})", .{x.index}), |
| 341 | .internal => |x| try w.print(" : internal({d})", .{x.index}), |
| 342 | .object => |x| try w.print(" : object({d})", .{x.index}), |
| 343 | .dylib => |x| try w.print(" : dylib({d})", .{x.index}), |
| 344 | } |
| 345 | } else try w.writeAll(" : unresolved"); |
| 346 | } |
| 347 | }; |
| 348 | |
| 349 | pub const Flags = packed struct { |
| 350 | /// Whether the symbol is imported at runtime. |
| 351 | import: bool = false, |
| 352 | |
| 353 | /// Whether the symbol is exported at runtime. |
| 354 | @"export": bool = false, |
| 355 | |
| 356 | /// Whether this symbol is weak. |
| 357 | weak: bool = false, |
| 358 | |
| 359 | /// Whether this symbol is weakly referenced. |
| 360 | weak_ref: bool = false, |
| 361 | |
| 362 | /// Whether this symbol is dynamically referenced. |
| 363 | dyn_ref: bool = false, |
| 364 | |
| 365 | /// Whether this symbol was marked as N_NO_DEAD_STRIP. |
| 366 | no_dead_strip: bool = false, |
| 367 | |
| 368 | /// Whether this symbol can be interposed at runtime. |
| 369 | interposable: bool = false, |
| 370 | |
| 371 | /// Whether this symbol is absolute. |
| 372 | abs: bool = false, |
| 373 | |
| 374 | /// Whether this symbol is a tentative definition. |
| 375 | tentative: bool = false, |
| 376 | |
| 377 | /// Whether this symbol is a thread-local variable. |
| 378 | tlv: bool = false, |
| 379 | |
| 380 | /// Whether the symbol makes into the output symtab or not. |
| 381 | output_symtab: bool = false, |
| 382 | |
| 383 | /// ZigObject specific flags |
| 384 | /// Whether the symbol has a trampoline |
| 385 | trampoline: bool = false, |
| 386 | }; |
| 387 | |
| 388 | pub const SectionFlags = packed struct(u8) { |
| 389 | /// Whether the symbol contains __got indirection. |
| 390 | needs_got: bool = false, |
| 391 | has_got: bool = false, |
| 392 | |
| 393 | /// Whether the symbols contains __stubs indirection. |
| 394 | stubs: bool = false, |
| 395 | |
| 396 | /// Whether the symbol has a TLV pointer. |
| 397 | tlv_ptr: bool = false, |
| 398 | |
| 399 | /// Whether the symbol contains __objc_stubs indirection. |
| 400 | objc_stubs: bool = false, |
| 401 | |
| 402 | _: u3 = 0, |
| 403 | }; |
| 404 | |
| 405 | pub const Visibility = enum { |
| 406 | global, |
| 407 | hidden, |
| 408 | local, |
| 409 | |
| 410 | pub fn rank(vis: Visibility) u2 { |
| 411 | return switch (vis) { |
| 412 | .local => 2, |
| 413 | .hidden => 1, |
| 414 | .global => 0, |
| 415 | }; |
| 416 | } |
| 417 | }; |
| 418 | |
| 419 | pub const Extra = struct { |
| 420 | got: u32 = 0, |
| 421 | stubs: u32 = 0, |
| 422 | objc_stubs: u32 = 0, |
| 423 | objc_selrefs: u32 = 0, |
| 424 | tlv_ptr: u32 = 0, |
| 425 | symtab: u32 = 0, |
| 426 | trampoline: u32 = 0, |
| 427 | }; |
| 428 | |
| 429 | pub const Index = u32; |
| 430 | |
| 431 | const assert = std.debug.assert; |
| 432 | const macho = std.macho; |
| 433 | const std = @import("std"); |
| 434 | const Writer = std.Io.Writer; |
| 435 | |
| 436 | const Atom = @import("Atom.zig"); |
| 437 | const File = @import("file.zig").File; |
| 438 | const MachO = @import("../MachO.zig"); |
| 439 | const Nlist = Object.Nlist; |
| 440 | const Object = @import("Object.zig"); |
| 441 | const Symbol = @This(); |
| 442 | const ZigGotSection = @import("synthetic.zig").ZigGotSection; |