zig/src/link/MachO/Archive.zig
mlugg 45143c6f04 MachO: emit absolute path in N_OSO stabs
This path being relative is unconventional and causes issues for us
if the output artifact is ever used from a different cwd than the one it
was built from. The behavior implemented by this commit of always
emitting these paths as absolute was actually the behavior in 0.14.x,
but it regressed in 0.15.1 due to internal reworks to path handling
which led to relative paths being more common in the compiler internals.

Resolves: #25433
2025-10-10 20:33:30 +01:00

297 lines
9.8 KiB
Zig

objects: std.ArrayListUnmanaged(Object) = .empty,
pub fn deinit(self: *Archive, allocator: Allocator) void {
self.objects.deinit(allocator);
}
pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
const comp = macho_file.base.comp;
const gpa = comp.gpa;
const diags = &comp.link_diags;
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
const handle = macho_file.getFileHandle(handle_index);
const offset = if (fat_arch) |ar| ar.offset else 0;
const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat()).size;
var pos: usize = offset + SARMAG;
while (true) {
if (pos >= end_pos) break;
if (!mem.isAligned(pos, 2)) pos += 1;
var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;
{
const amt = try handle.preadAll(&hdr_buffer, pos);
if (amt != @sizeOf(ar_hdr)) return error.InputOutput;
}
const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*;
pos += @sizeOf(ar_hdr);
if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{
std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower),
});
}
var hdr_size = try hdr.size();
const name = name: {
if (hdr.name()) |n| break :name n;
if (try hdr.nameLength()) |len| {
hdr_size -= len;
const buf = try arena.allocator().alloc(u8, len);
const amt = try handle.preadAll(buf, pos);
if (amt != len) return error.InputOutput;
pos += len;
const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len;
break :name buf[0..actual_len];
}
unreachable;
};
defer pos += hdr_size;
if (mem.eql(u8, name, SYMDEF) or
mem.eql(u8, name, SYMDEF64) or
mem.eql(u8, name, SYMDEF_SORTED) or
mem.eql(u8, name, SYMDEF64_SORTED)) continue;
const abs_path = try std.fs.path.resolvePosix(gpa, &.{
comp.dirs.cwd,
path.root_dir.path orelse ".",
path.sub_path,
});
errdefer gpa.free(abs_path);
const o_basename = try gpa.dupe(u8, name);
errdefer gpa.free(o_basename);
const object: Object = .{
.offset = pos,
.in_archive = .{
.path = abs_path,
.size = hdr_size,
},
.path = o_basename,
.file_handle = handle_index,
.index = undefined,
.alive = false,
.mtime = hdr.date() catch 0,
};
log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path });
try self.objects.append(gpa, object);
}
}
pub fn writeHeader(
object_name: []const u8,
object_size: usize,
format: Format,
writer: *Writer,
) !void {
var hdr: ar_hdr = .{};
const object_name_len = mem.alignForward(usize, object_name.len + 1, ptrWidth(format));
const total_object_size = object_size + object_name_len;
{
var stream: Writer = .fixed(&hdr.ar_name);
stream.print("#1/{d}", .{object_name_len}) catch unreachable;
}
{
var stream: Writer = .fixed(&hdr.ar_size);
stream.print("{d}", .{total_object_size}) catch unreachable;
}
try writer.writeAll(mem.asBytes(&hdr));
try writer.print("{s}\x00", .{object_name});
const padding = object_name_len - object_name.len - 1;
if (padding > 0) {
try writer.splatByteAll(0, padding);
}
}
// Archive files start with the ARMAG identifying string. Then follows a
// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
// member indicates, for each member file.
/// String that begins an archive file.
pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
/// Size of that string.
pub const SARMAG: u4 = 8;
/// String in ar_fmag at the end of each header.
const ARFMAG: *const [2:0]u8 = "`\n";
pub const SYMDEF = "__.SYMDEF";
pub const SYMDEF64 = "__.SYMDEF_64";
pub const SYMDEF_SORTED = "__.SYMDEF SORTED";
pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED";
pub const ar_hdr = extern struct {
/// Member file name, sometimes / terminated.
ar_name: [16]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
/// File date, decimal seconds since Epoch.
ar_date: [12]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
/// User ID, in ASCII format.
ar_uid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
/// Group ID, in ASCII format.
ar_gid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
/// File mode, in ASCII octal.
ar_mode: [8]u8 = "0\x20\x20\x20\x20\x20\x20\x20".*,
/// File size, in ASCII decimal.
ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
/// Always contains ARFMAG.
ar_fmag: [2]u8 = ARFMAG.*,
fn date(self: ar_hdr) !u64 {
const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u64, value, 10);
}
fn size(self: ar_hdr) !u32 {
const value = mem.trimEnd(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
return std.fmt.parseInt(u32, value, 10);
}
fn name(self: *const ar_hdr) ?[]const u8 {
const value = &self.ar_name;
if (mem.startsWith(u8, value, "#1/")) return null;
const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len;
return value[0..sentinel];
}
fn nameLength(self: ar_hdr) !?u32 {
const value = &self.ar_name;
if (!mem.startsWith(u8, value, "#1/")) return null;
const trimmed = mem.trimEnd(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
return try std.fmt.parseInt(u32, trimmed, 10);
}
};
pub const ArSymtab = struct {
entries: std.ArrayListUnmanaged(Entry) = .empty,
strtab: StringTable = .{},
pub fn deinit(ar: *ArSymtab, allocator: Allocator) void {
ar.entries.deinit(allocator);
ar.strtab.deinit(allocator);
}
pub fn sort(ar: *ArSymtab) void {
mem.sort(Entry, ar.entries.items, {}, Entry.lessThan);
}
pub fn size(ar: ArSymtab, format: Format) usize {
const ptr_width = ptrWidth(format);
return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width);
}
pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: *Writer) !void {
const ptr_width = ptrWidth(format);
// Header
try writeHeader(SYMDEF, ar.size(format), format, writer);
// Symtab size
try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer);
// Symtab entries
for (ar.entries.items) |entry| {
const file_off = switch (macho_file.getFile(entry.file).?) {
.zig_object => |x| x.output_ar_state.file_off,
.object => |x| x.output_ar_state.file_off,
else => unreachable,
};
// Name offset
try writeInt(format, entry.off, writer);
// File offset
try writeInt(format, file_off, writer);
}
// Strtab size
const strtab_size = mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width);
const padding = strtab_size - ar.strtab.buffer.items.len;
try writeInt(format, strtab_size, writer);
// Strtab
try writer.writeAll(ar.strtab.buffer.items);
if (padding > 0) {
try writer.splatByteAll(0, padding);
}
}
const PrintFormat = struct {
ar: ArSymtab,
macho_file: *MachO,
fn default(f: PrintFormat, bw: *Writer) Writer.Error!void {
const ar = f.ar;
const macho_file = f.macho_file;
for (ar.entries.items, 0..) |entry, i| {
const name = ar.strtab.getAssumeExists(entry.off);
const file = macho_file.getFile(entry.file).?;
try bw.print(" {d}: {s} in file({d})({f})\n", .{ i, name, entry.file, file.fmtPath() });
}
}
};
pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Alt(PrintFormat, PrintFormat.default) {
return .{ .data = .{ .ar = ar, .macho_file = macho_file } };
}
const Entry = struct {
/// Symbol name offset
off: u32,
/// Exporting file
file: File.Index,
pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool {
_ = ctx;
if (lhs.off == rhs.off) return lhs.file < rhs.file;
return lhs.off < rhs.off;
}
};
};
pub const Format = enum {
p32,
p64,
};
pub fn ptrWidth(format: Format) usize {
return switch (format) {
.p32 => @as(usize, 4),
.p64 => 8,
};
}
pub fn writeInt(format: Format, value: u64, writer: *Writer) !void {
switch (format) {
.p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little),
.p64 => try writer.writeInt(u64, value, .little),
}
}
pub const ArState = struct {
/// File offset of the ar_hdr describing the contributing
/// object in the archive.
file_off: u64 = 0,
/// Total size of the contributing object (excludes ar_hdr and long name with padding).
size: u64 = 0,
};
const fat = @import("fat.zig");
const link = @import("../../link.zig");
const log = std.log.scoped(.link);
const macho = std.macho;
const mem = std.mem;
const std = @import("std");
const Allocator = std.mem.Allocator;
const Path = std.Build.Cache.Path;
const Writer = std.Io.Writer;
const Archive = @This();
const File = @import("file.zig").File;
const MachO = @import("../MachO.zig");
const Object = @import("Object.zig");
const StringTable = @import("../StringTable.zig");