| author | |
| committer | |
| log | 79457fc76a61695560e6314246b0a8c21a7e2d2c |
| tree | 7a35a25f880c057e27098b240f8fac7e42b842eb |
| parent | ec40c6b28fb1612f401db3c43b68aba670327e2e |
By pulling out the parallel hashing setup from `CodeSignature.zig`,
we can now reuse it different places across MachO linker (for now;
I can totally see its usefulness beyond MachO, eg. in COFF or ELF too).
The parallel hasher is generic over actual hasher such as Sha256 or MD5.
The implementation is kept as it was.
For UUID calculation, depending on the linking mode:
* incremental - since it only supports debug mode, we don't bother with MD5
hashing of the contents, and populate it with random data but only once
per a sequence of in-place binary patches
* traditional - in debug, we use random string (for speed); in release,
we calculate the hash, however we use LLVM/LLD's trick in that we
calculate a series of MD5 hashes in parallel and then one an MD5 of MD5
final hash to generate digest.7 files changed, 160 insertions(+), 59 deletions(-)
CMakeLists.txt+1| ... | @@ -591,6 +591,7 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -591,6 +591,7 @@ set(ZIG_STAGE2_SOURCES |
| 591 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" | 591 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" |
| 592 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" | 592 | "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" |
| 593 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" | 593 | "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" |
| 594 | "${CMAKE_SOURCE_DIR}/src/link/MachO/hasher.zig" | ||
| 594 | "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" | 595 | "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" |
| 595 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" | 596 | "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" |
| 596 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" | 597 | "${CMAKE_SOURCE_DIR}/src/link/MachO/uuid.zig" |
src/link/MachO.zig+8-8| ... | @@ -99,10 +99,10 @@ page_size: u16, | ... | @@ -99,10 +99,10 @@ page_size: u16, |
| 99 | /// fashion (default for LLVM backend). | 99 | /// fashion (default for LLVM backend). |
| 100 | mode: enum { incremental, one_shot }, | 100 | mode: enum { incremental, one_shot }, |
| 101 | 101 | ||
| 102 | uuid: macho.uuid_command = .{ | 102 | uuid: struct { |
| 103 | .cmdsize = @sizeOf(macho.uuid_command), | 103 | buf: [16]u8 = undefined, |
| 104 | .uuid = undefined, | 104 | final: bool = false, |
| 105 | }, | 105 | } = .{}, |
| 106 | 106 | ||
| 107 | dylibs: std.ArrayListUnmanaged(Dylib) = .{}, | 107 | dylibs: std.ArrayListUnmanaged(Dylib) = .{}, |
| 108 | dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, | 108 | dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, |
| ... | @@ -588,11 +588,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No | ... | @@ -588,11 +588,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No |
| 588 | 588 | ||
| 589 | try load_commands.writeBuildVersionLC(&self.base.options, &ncmds, lc_writer); | 589 | try load_commands.writeBuildVersionLC(&self.base.options, &ncmds, lc_writer); |
| 590 | 590 | ||
| 591 | { | 591 | if (!self.uuid.final) { |
| 592 | std.crypto.random.bytes(&self.uuid.uuid); | 592 | std.crypto.random.bytes(&self.uuid.buf); |
| 593 | try lc_writer.writeStruct(self.uuid); | 593 | self.uuid.final = true; |
| 594 | ncmds += 1; | ||
| 595 | } | 594 | } |
| 595 | try load_commands.writeUuidLC(&self.uuid.buf, &ncmds, lc_writer); | ||
| 596 | 596 | ||
| 597 | try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), &ncmds, lc_writer); | 597 | try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), &ncmds, lc_writer); |
| 598 | 598 |
src/link/MachO/CodeSignature.zig+11-45| ... | @@ -1,6 +1,4 @@ | ... | @@ -1,6 +1,4 @@ |
| 1 | const CodeSignature = @This(); | 1 | const CodeSignature = @This(); |
| 2 | const Compilation = @import("../../Compilation.zig"); | ||
| 3 | const WaitGroup = @import("../../WaitGroup.zig"); | ||
| 4 | 2 | ||
| 5 | const std = @import("std"); | 3 | const std = @import("std"); |
| 6 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| ... | @@ -9,10 +7,13 @@ const log = std.log.scoped(.link); | ... | @@ -9,10 +7,13 @@ const log = std.log.scoped(.link); |
| 9 | const macho = std.macho; | 7 | const macho = std.macho; |
| 10 | const mem = std.mem; | 8 | const mem = std.mem; |
| 11 | const testing = std.testing; | 9 | const testing = std.testing; |
| 10 | |||
| 12 | const Allocator = mem.Allocator; | 11 | const Allocator = mem.Allocator; |
| 12 | const Compilation = @import("../../Compilation.zig"); | ||
| 13 | const Hasher = @import("hasher.zig").ParallelHasher; | ||
| 13 | const Sha256 = std.crypto.hash.sha2.Sha256; | 14 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 14 | 15 | ||
| 15 | const hash_size: u8 = 32; | 16 | const hash_size = Sha256.digest_length; |
| 16 | 17 | ||
| 17 | const Blob = union(enum) { | 18 | const Blob = union(enum) { |
| 18 | code_directory: *CodeDirectory, | 19 | code_directory: *CodeDirectory, |
| ... | @@ -109,7 +110,7 @@ const CodeDirectory = struct { | ... | @@ -109,7 +110,7 @@ const CodeDirectory = struct { |
| 109 | fn size(self: CodeDirectory) u32 { | 110 | fn size(self: CodeDirectory) u32 { |
| 110 | const code_slots = self.inner.nCodeSlots * hash_size; | 111 | const code_slots = self.inner.nCodeSlots * hash_size; |
| 111 | const special_slots = self.inner.nSpecialSlots * hash_size; | 112 | const special_slots = self.inner.nSpecialSlots * hash_size; |
| 112 | return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1) + special_slots + code_slots; | 113 | return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1 + special_slots + code_slots); |
| 113 | } | 114 | } |
| 114 | 115 | ||
| 115 | fn write(self: CodeDirectory, writer: anytype) !void { | 116 | fn write(self: CodeDirectory, writer: anytype) !void { |
| ... | @@ -287,33 +288,11 @@ pub fn writeAdhocSignature( | ... | @@ -287,33 +288,11 @@ pub fn writeAdhocSignature( |
| 287 | self.code_directory.inner.nCodeSlots = total_pages; | 288 | self.code_directory.inner.nCodeSlots = total_pages; |
| 288 | 289 | ||
| 289 | // Calculate hash for each page (in file) and write it to the buffer | 290 | // Calculate hash for each page (in file) and write it to the buffer |
| 290 | var wg: WaitGroup = .{}; | 291 | var hasher = Hasher(Sha256){}; |
| 291 | { | 292 | try hasher.hash(gpa, comp.thread_pool, opts.file, self.code_directory.code_slots.items, .{ |
| 292 | const buffer = try gpa.alloc(u8, self.page_size * total_pages); | 293 | .chunk_size = self.page_size, |
| 293 | defer gpa.free(buffer); | 294 | .max_file_size = opts.file_size, |
| 294 | 295 | }); | |
| 295 | const results = try gpa.alloc(fs.File.PReadError!usize, total_pages); | ||
| 296 | defer gpa.free(results); | ||
| 297 | { | ||
| 298 | wg.reset(); | ||
| 299 | defer wg.wait(); | ||
| 300 | |||
| 301 | var i: usize = 0; | ||
| 302 | while (i < total_pages) : (i += 1) { | ||
| 303 | const fstart = i * self.page_size; | ||
| 304 | const fsize = if (fstart + self.page_size > opts.file_size) | ||
| 305 | opts.file_size - fstart | ||
| 306 | else | ||
| 307 | self.page_size; | ||
| 308 | const out_hash = &self.code_directory.code_slots.items[i]; | ||
| 309 | wg.start(); | ||
| 310 | try comp.thread_pool.spawn(workerSha256Hash, .{ | ||
| 311 | opts.file, fstart, buffer[fstart..][0..fsize], out_hash, &results[i], &wg, | ||
| 312 | }); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | for (results) |result| _ = try result; | ||
| 316 | } | ||
| 317 | 296 | ||
| 318 | try blobs.append(.{ .code_directory = &self.code_directory }); | 297 | try blobs.append(.{ .code_directory = &self.code_directory }); |
| 319 | header.length += @sizeOf(macho.BlobIndex); | 298 | header.length += @sizeOf(macho.BlobIndex); |
| ... | @@ -352,7 +331,7 @@ pub fn writeAdhocSignature( | ... | @@ -352,7 +331,7 @@ pub fn writeAdhocSignature( |
| 352 | } | 331 | } |
| 353 | 332 | ||
| 354 | self.code_directory.inner.hashOffset = | 333 | self.code_directory.inner.hashOffset = |
| 355 | @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1) + self.code_directory.inner.nSpecialSlots * hash_size; | 334 | @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size); |
| 356 | self.code_directory.inner.length = self.code_directory.size(); | 335 | self.code_directory.inner.length = self.code_directory.size(); |
| 357 | header.length += self.code_directory.size(); | 336 | header.length += self.code_directory.size(); |
| 358 | 337 | ||
| ... | @@ -372,19 +351,6 @@ pub fn writeAdhocSignature( | ... | @@ -372,19 +351,6 @@ pub fn writeAdhocSignature( |
| 372 | } | 351 | } |
| 373 | } | 352 | } |
| 374 | 353 | ||
| 375 | fn workerSha256Hash( | ||
| 376 | file: fs.File, | ||
| 377 | fstart: usize, | ||
| 378 | buffer: []u8, | ||
| 379 | hash: *[hash_size]u8, | ||
| 380 | err: *fs.File.PReadError!usize, | ||
| 381 | wg: *WaitGroup, | ||
| 382 | ) void { | ||
| 383 | defer wg.finish(); | ||
| 384 | err.* = file.preadAll(buffer, fstart); | ||
| 385 | Sha256.hash(buffer, hash, .{}); | ||
| 386 | } | ||
| 387 | |||
| 388 | pub fn size(self: CodeSignature) u32 { | 354 | pub fn size(self: CodeSignature) u32 { |
| 389 | var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); | 355 | var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); |
| 390 | if (self.requirements) |req| { | 356 | if (self.requirements) |req| { |
src/link/MachO/DebugSymbols.zig+2-4| ... | @@ -5,6 +5,7 @@ const build_options = @import("build_options"); | ... | @@ -5,6 +5,7 @@ const build_options = @import("build_options"); |
| 5 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 6 | const fs = std.fs; | 6 | const fs = std.fs; |
| 7 | const link = @import("../../link.zig"); | 7 | const link = @import("../../link.zig"); |
| 8 | const load_commands = @import("load_commands.zig"); | ||
| 8 | const log = std.log.scoped(.dsym); | 9 | const log = std.log.scoped(.dsym); |
| 9 | const macho = std.macho; | 10 | const macho = std.macho; |
| 10 | const makeStaticString = MachO.makeStaticString; | 11 | const makeStaticString = MachO.makeStaticString; |
| ... | @@ -303,10 +304,7 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void { | ... | @@ -303,10 +304,7 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void { |
| 303 | self.finalizeDwarfSegment(macho_file); | 304 | self.finalizeDwarfSegment(macho_file); |
| 304 | try self.writeLinkeditSegmentData(macho_file, &ncmds, lc_writer); | 305 | try self.writeLinkeditSegmentData(macho_file, &ncmds, lc_writer); |
| 305 | 306 | ||
| 306 | { | 307 | try load_commands.writeUuidLC(&macho_file.uuid.buf, &ncmds, lc_writer); |
| 307 | try lc_writer.writeStruct(macho_file.uuid); | ||
| 308 | ncmds += 1; | ||
| 309 | } | ||
| 310 | 308 | ||
| 311 | var headers_buf = std.ArrayList(u8).init(self.allocator); | 309 | var headers_buf = std.ArrayList(u8).init(self.allocator); |
| 312 | defer headers_buf.deinit(); | 310 | defer headers_buf.deinit(); |
src/link/MachO/hasher.zig created+60| ... | @@ -0,0 +1,60 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const fs = std.fs; | ||
| 4 | const mem = std.mem; | ||
| 5 | |||
| 6 | const Allocator = mem.Allocator; | ||
| 7 | const ThreadPool = @import("../../ThreadPool.zig"); | ||
| 8 | const WaitGroup = @import("../../WaitGroup.zig"); | ||
| 9 | |||
| 10 | pub fn ParallelHasher(comptime Hasher: type) type { | ||
| 11 | const hash_size = Hasher.digest_length; | ||
| 12 | |||
| 13 | return struct { | ||
| 14 | pub fn hash(self: @This(), gpa: Allocator, pool: *ThreadPool, file: fs.File, out: [][hash_size]u8, opts: struct { | ||
| 15 | chunk_size: u16 = 0x4000, | ||
| 16 | max_file_size: ?u64 = null, | ||
| 17 | }) !void { | ||
| 18 | _ = self; | ||
| 19 | |||
| 20 | var wg: WaitGroup = .{}; | ||
| 21 | |||
| 22 | const file_size = opts.max_file_size orelse try file.getEndPos(); | ||
| 23 | const total_num_chunks = mem.alignForward(file_size, opts.chunk_size) / opts.chunk_size; | ||
| 24 | assert(out.len >= total_num_chunks); | ||
| 25 | |||
| 26 | const buffer = try gpa.alloc(u8, opts.chunk_size * total_num_chunks); | ||
| 27 | defer gpa.free(buffer); | ||
| 28 | |||
| 29 | const results = try gpa.alloc(fs.File.PReadError!usize, total_num_chunks); | ||
| 30 | defer gpa.free(results); | ||
| 31 | |||
| 32 | { | ||
| 33 | wg.reset(); | ||
| 34 | defer wg.wait(); | ||
| 35 | |||
| 36 | var i: usize = 0; | ||
| 37 | while (i < total_num_chunks) : (i += 1) { | ||
| 38 | const fstart = i * opts.chunk_size; | ||
| 39 | const fsize = if (fstart + opts.chunk_size > file_size) file_size - fstart else opts.chunk_size; | ||
| 40 | wg.start(); | ||
| 41 | try pool.spawn(worker, .{ file, fstart, buffer[fstart..][0..fsize], &out[i], &results[i], &wg }); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | for (results) |result| _ = try result; | ||
| 45 | } | ||
| 46 | |||
| 47 | fn worker( | ||
| 48 | file: fs.File, | ||
| 49 | fstart: usize, | ||
| 50 | buffer: []u8, | ||
| 51 | out: *[hash_size]u8, | ||
| 52 | err: *fs.File.PReadError!usize, | ||
| 53 | wg: *WaitGroup, | ||
| 54 | ) void { | ||
| 55 | defer wg.finish(); | ||
| 56 | err.* = file.preadAll(buffer, fstart); | ||
| 57 | Hasher.hash(buffer, out, .{}); | ||
| 58 | } | ||
| 59 | }; | ||
| 60 | } | ||
src/link/MachO/uuid.zig created+69| ... | @@ -0,0 +1,69 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const fs = std.fs; | ||
| 3 | const mem = std.mem; | ||
| 4 | |||
| 5 | const Allocator = mem.Allocator; | ||
| 6 | const Compilation = @import("../../Compilation.zig"); | ||
| 7 | const Md5 = std.crypto.hash.Md5; | ||
| 8 | const Hasher = @import("hasher.zig").ParallelHasher; | ||
| 9 | |||
| 10 | /// Somewhat random chunk size for MD5 hash calculation. | ||
| 11 | pub const chunk_size = 0x4000; | ||
| 12 | |||
| 13 | /// Calculates Md5 hash of the file contents. | ||
| 14 | /// Hash is calculated in a streaming manner which may be slow. | ||
| 15 | pub fn calcUuidStreaming(file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | ||
| 16 | const total_num_chunks = mem.alignForward(file_size, chunk_size) / chunk_size; | ||
| 17 | |||
| 18 | var hasher = Md5.init(.{}); | ||
| 19 | var buffer: [chunk_size]u8 = undefined; | ||
| 20 | |||
| 21 | var i: usize = 0; | ||
| 22 | while (i < total_num_chunks) : (i += 1) { | ||
| 23 | const start = i * chunk_size; | ||
| 24 | const size = if (start + chunk_size > file_size) | ||
| 25 | file_size - start | ||
| 26 | else | ||
| 27 | chunk_size; | ||
| 28 | const amt = try file.preadAll(&buffer, start); | ||
| 29 | if (amt != size) return error.InputOutput; | ||
| 30 | |||
| 31 | hasher.update(buffer[0..size]); | ||
| 32 | } | ||
| 33 | |||
| 34 | hasher.final(out); | ||
| 35 | conform(out); | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Calculates Md5 hash of each chunk in parallel and then hashes all Md5 hashes to produce | ||
| 39 | /// the final digest. | ||
| 40 | /// While this is NOT a correct MD5 hash of the contents, this methodology is used by LLVM/LLD | ||
| 41 | /// and we will use it too as it seems accepted by Apple OSes. | ||
| 42 | pub fn calcUuidParallel(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { | ||
| 43 | const total_hashes = mem.alignForward(file_size, chunk_size) / chunk_size; | ||
| 44 | |||
| 45 | const hashes = try comp.gpa.alloc([Md5.digest_length]u8, total_hashes); | ||
| 46 | defer comp.gpa.free(hashes); | ||
| 47 | |||
| 48 | var hasher = Hasher(Md5){}; | ||
| 49 | try hasher.hash(comp.gpa, comp.thread_pool, file, hashes, .{ | ||
| 50 | .chunk_size = chunk_size, | ||
| 51 | .max_file_size = file_size, | ||
| 52 | }); | ||
| 53 | |||
| 54 | const final_buffer = try comp.gpa.alloc(u8, total_hashes * Md5.digest_length); | ||
| 55 | defer comp.gpa.free(final_buffer); | ||
| 56 | |||
| 57 | for (hashes) |hash, i| { | ||
| 58 | mem.copy(u8, final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); | ||
| 59 | } | ||
| 60 | |||
| 61 | Md5.hash(final_buffer, out, .{}); | ||
| 62 | conform(out); | ||
| 63 | } | ||
| 64 | |||
| 65 | inline fn conform(out: *[Md5.digest_length]u8) void { | ||
| 66 | // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats | ||
| 67 | out[6] = (out[6] & 0x0F) | (3 << 4); | ||
| 68 | out[8] = (out[8] & 0x3F) | 0x80; | ||
| 69 | } | ||
src/link/MachO/zld.zig+9-2| ... | @@ -4037,8 +4037,15 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr | ... | @@ -4037,8 +4037,15 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 4037 | const uuid_offset_backpatch: ?usize = blk: { | 4037 | const uuid_offset_backpatch: ?usize = blk: { |
| 4038 | const index = lc_buffer.items.len; | 4038 | const index = lc_buffer.items.len; |
| 4039 | var uuid_buf: [16]u8 = [_]u8{0} ** 16; | 4039 | var uuid_buf: [16]u8 = [_]u8{0} ** 16; |
| 4040 | |||
| 4041 | if (zld.options.optimize_mode == .Debug) { | ||
| 4042 | // In Debug we don't really care about reproducibility, so put in a random value | ||
| 4043 | // and be done with it. | ||
| 4044 | std.crypto.random.bytes(&uuid_buf); | ||
| 4045 | } | ||
| 4046 | |||
| 4040 | try load_commands.writeUuidLC(&uuid_buf, &ncmds, lc_writer); | 4047 | try load_commands.writeUuidLC(&uuid_buf, &ncmds, lc_writer); |
| 4041 | break :blk index; | 4048 | break :blk if (zld.options.optimize_mode == .Debug) null else index; |
| 4042 | }; | 4049 | }; |
| 4043 | 4050 | ||
| 4044 | try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), &ncmds, lc_writer); | 4051 | try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), &ncmds, lc_writer); |
| ... | @@ -4076,7 +4083,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr | ... | @@ -4076,7 +4083,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 4076 | const seg = zld.getLinkeditSegmentPtr(); | 4083 | const seg = zld.getLinkeditSegmentPtr(); |
| 4077 | const file_size = seg.fileoff + seg.filesize; | 4084 | const file_size = seg.fileoff + seg.filesize; |
| 4078 | var uuid_buf: [16]u8 = undefined; | 4085 | var uuid_buf: [16]u8 = undefined; |
| 4079 | try uuid.calcMd5Hash(zld.gpa, zld.file, file_size, &uuid_buf); | 4086 | try uuid.calcUuidParallel(comp, zld.file, file_size, &uuid_buf); |
| 4080 | const offset = @sizeOf(macho.mach_header_64) + headers_buf.items.len + backpatch + @sizeOf(macho.load_command); | 4087 | const offset = @sizeOf(macho.mach_header_64) + headers_buf.items.len + backpatch + @sizeOf(macho.load_command); |
| 4081 | try zld.file.pwriteAll(&uuid_buf, offset); | 4088 | try zld.file.pwriteAll(&uuid_buf, offset); |
| 4082 | } | 4089 | } |