master
  1/// A list of long file names, delimited by a LF character (0x0a).
  2/// This is stored as a single slice of bytes, as the header-names
  3/// point to the character index of a file name, rather than the index
  4/// in the list.
  5/// Points into `file_contents`.
  6long_file_names: RelativeSlice,
  7
  8/// Parsed table of contents.
  9/// Each symbol name points to a list of all definition
 10/// sites within the current static archive.
 11toc: Toc,
 12
 13/// Key points into `LazyArchive` `file_contents`.
 14/// Value is allocated with gpa.
 15const Toc = std.StringArrayHashMapUnmanaged(std.ArrayList(u32));
 16
 17const ARMAG = std.elf.ARMAG;
 18const ARFMAG = std.elf.ARFMAG;
 19
 20const RelativeSlice = struct {
 21    off: u32,
 22    len: u32,
 23};
 24
 25const Header = extern struct {
 26    /// Member file name, sometimes / terminated.
 27    name: [16]u8,
 28    /// File date, decimal seconds since Epoch.
 29    date: [12]u8,
 30    /// User ID, in ASCII format.
 31    uid: [6]u8,
 32    /// Group ID, in ASCII format.
 33    gid: [6]u8,
 34    /// File mode, in ASCII octal.
 35    mode: [8]u8,
 36    /// File size, in ASCII decimal.
 37    size: [10]u8,
 38    /// Always contains ARFMAG.
 39    fmag: [2]u8,
 40
 41    const NameOrIndex = union(enum) {
 42        name: []const u8,
 43        index: u32,
 44    };
 45
 46    fn nameOrIndex(archive: Header) !NameOrIndex {
 47        const value = getValue(&archive.name);
 48        const slash_index = mem.indexOfScalar(u8, value, '/') orelse return error.MalformedArchive;
 49        const len = value.len;
 50        if (slash_index == len - 1) {
 51            // Name stored directly
 52            return .{ .name = value };
 53        } else {
 54            // Name follows the header directly and its length is encoded in
 55            // the name field.
 56            const index = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
 57            return .{ .index = index };
 58        }
 59    }
 60
 61    fn parsedSize(archive: Header) !u32 {
 62        const value = getValue(&archive.size);
 63        return std.fmt.parseInt(u32, value, 10);
 64    }
 65
 66    fn getValue(raw: []const u8) []const u8 {
 67        return mem.trimEnd(u8, raw, &[_]u8{@as(u8, 0x20)});
 68    }
 69};
 70
 71pub fn deinit(archive: *Archive, gpa: Allocator) void {
 72    deinitToc(gpa, &archive.toc);
 73    archive.* = undefined;
 74}
 75
 76fn deinitToc(gpa: Allocator, toc: *Toc) void {
 77    for (toc.values()) |*value| value.deinit(gpa);
 78    toc.deinit(gpa);
 79}
 80
 81pub fn parse(gpa: Allocator, file_contents: []const u8) !Archive {
 82    var pos: usize = 0;
 83
 84    if (!mem.eql(u8, file_contents[0..ARMAG.len], ARMAG)) return error.BadArchiveMagic;
 85    pos += ARMAG.len;
 86
 87    const header = mem.bytesAsValue(Header, file_contents[pos..][0..@sizeOf(Header)]);
 88    if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter;
 89    pos += @sizeOf(Header);
 90
 91    // The size field can have extra spaces padded in front as well as
 92    // the end, so we trim those first before parsing the ASCII value.
 93    const size_trimmed = mem.trim(u8, &header.size, " ");
 94    const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);
 95
 96    const num_symbols = mem.readInt(u32, file_contents[pos..][0..4], .big);
 97    pos += 4;
 98
 99    const symbol_positions_size = @sizeOf(u32) * num_symbols;
100    const symbol_positions_be = mem.bytesAsSlice(u32, file_contents[pos..][0..symbol_positions_size]);
101    pos += symbol_positions_size;
102
103    const sym_tab = file_contents[pos..][0 .. sym_tab_size - 4 - symbol_positions_size];
104    pos += sym_tab.len;
105
106    var toc: Toc = .empty;
107    errdefer deinitToc(gpa, &toc);
108
109    var sym_tab_pos: usize = 0;
110    for (0..num_symbols) |i| {
111        const name = mem.sliceTo(sym_tab[sym_tab_pos..], 0);
112        sym_tab_pos += name.len + 1;
113        if (name.len == 0) continue;
114
115        const gop = try toc.getOrPut(gpa, name);
116        if (!gop.found_existing) gop.value_ptr.* = .empty;
117        try gop.value_ptr.append(gpa, switch (native_endian) {
118            .big => symbol_positions_be[i],
119            .little => @byteSwap(symbol_positions_be[i]),
120        });
121    }
122
123    const long_file_names: RelativeSlice = s: {
124        const sub_header = mem.bytesAsValue(Header, file_contents[pos..][0..@sizeOf(Header)]);
125        pos += @sizeOf(Header);
126
127        if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter;
128        if (!mem.eql(u8, sub_header.name[0..2], "//")) return error.MissingTableName;
129        const table_size = try sub_header.parsedSize();
130
131        break :s .{
132            .off = @intCast(pos),
133            .len = table_size,
134        };
135    };
136
137    return .{
138        .toc = toc,
139        .long_file_names = long_file_names,
140    };
141}
142
143/// From a given file offset, starts reading for a file header.
144/// When found, parses the object file into an `Object` and returns it.
145pub fn parseObject(
146    archive: Archive,
147    wasm: *Wasm,
148    file_contents: []const u8,
149    object_offset: u32,
150    path: Path,
151    host_name: Wasm.OptionalString,
152    scratch_space: *Object.ScratchSpace,
153    must_link: bool,
154    gc_sections: bool,
155) !Object {
156    const header = mem.bytesAsValue(Header, file_contents[object_offset..][0..@sizeOf(Header)]);
157    if (!mem.eql(u8, &header.fmag, ARFMAG)) return error.BadHeaderDelimiter;
158
159    const name_or_index = try header.nameOrIndex();
160    const object_name = switch (name_or_index) {
161        .name => |name| name,
162        .index => |index| n: {
163            const long_file_names = file_contents[archive.long_file_names.off..][0..archive.long_file_names.len];
164            const name = mem.sliceTo(long_file_names[index..], 0x0a);
165            break :n mem.trimEnd(u8, name, "/");
166        },
167    };
168
169    const object_file_size = try header.parsedSize();
170    const contents = file_contents[object_offset + @sizeOf(Header) ..][0..object_file_size];
171
172    return Object.parse(wasm, contents, path, object_name, host_name, scratch_space, must_link, gc_sections);
173}
174
175const Archive = @This();
176
177const builtin = @import("builtin");
178const native_endian = builtin.cpu.arch.endian();
179
180const std = @import("std");
181const mem = std.mem;
182const Allocator = std.mem.Allocator;
183const Path = std.Build.Cache.Path;
184
185const Wasm = @import("../Wasm.zig");
186const Object = @import("Object.zig");