| 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`. |
| 6 | long_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. |
| 11 | toc: Toc, |
| 12 | |
| 13 | /// Key points into `LazyArchive` `file_contents`. |
| 14 | /// Value is allocated with gpa. |
| 15 | const Toc = std.array_hash_map.String(std.ArrayList(u32)); |
| 16 | |
| 17 | const ARMAG = std.elf.ARMAG; |
| 18 | const ARFMAG = std.elf.ARFMAG; |
| 19 | |
| 20 | const RelativeSlice = struct { |
| 21 | off: u32, |
| 22 | len: u32, |
| 23 | }; |
| 24 | |
| 25 | const 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.findScalar(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 | |
| 71 | pub fn deinit(archive: *Archive, gpa: Allocator) void { |
| 72 | deinitToc(gpa, &archive.toc); |
| 73 | archive.* = undefined; |
| 74 | } |
| 75 | |
| 76 | fn deinitToc(gpa: Allocator, toc: *Toc) void { |
| 77 | for (toc.values()) |*value| value.deinit(gpa); |
| 78 | toc.deinit(gpa); |
| 79 | } |
| 80 | |
| 81 | pub 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. |
| 145 | pub 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 | |
| 175 | const Archive = @This(); |
| 176 | |
| 177 | const builtin = @import("builtin"); |
| 178 | const native_endian = builtin.cpu.arch.endian(); |
| 179 | |
| 180 | const std = @import("std"); |
| 181 | const mem = std.mem; |
| 182 | const Allocator = std.mem.Allocator; |
| 183 | const Path = std.Build.Cache.Path; |
| 184 | |
| 185 | const Wasm = @import("../Wasm.zig"); |
| 186 | const Object = @import("Object.zig"); |