diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2023-05-17 07:19:15 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-17 07:19:15 -0700 |
| commit | c1add1e19ea35b4d96fbab317410276f757510ef (patch) | |
| tree | 9791032b09046f6d5b785cd643439b266737cab4 | |
| parent | 5b06daf52bdeaf18b40909ef878e8b19b3d14019 (diff) | |
| parent | 728ce2d7c18e23ca6c36d86f4ee1ea4ce3ac81e2 (diff) | |
| download | zig-c1add1e19ea35b4d96fbab317410276f757510ef.tar.gz zig-c1add1e19ea35b4d96fbab317410276f757510ef.zip | |
Merge pull request #15459 from motiejus/build-id-full
stage2: implement `--build-id` styles
| -rw-r--r-- | build.zig | 8 | ||||
| -rw-r--r-- | lib/std/Build.zig | 62 | ||||
| -rw-r--r-- | lib/std/Build/Cache.zig | 4 | ||||
| -rw-r--r-- | lib/std/Build/Step/Compile.zig | 88 | ||||
| -rw-r--r-- | src/Compilation.zig | 7 | ||||
| -rw-r--r-- | src/link.zig | 3 | ||||
| -rw-r--r-- | src/link/Elf.zig | 14 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 45 | ||||
| -rw-r--r-- | src/main.zig | 35 |
9 files changed, 210 insertions, 56 deletions
@@ -165,8 +165,14 @@ pub fn build(b: *std.Build) !void { exe.strip = strip; exe.pie = pie; exe.sanitize_thread = sanitize_thread; - exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false; exe.entitlements = entitlements; + + exe.build_id = b.option( + std.Build.Step.Compile.BuildId, + "build-id", + "Request creation of '.note.gnu.build-id' section", + ); + b.installArtifact(exe); test_step.dependOn(&exe.step); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index ca55d23937..7ef504851e 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -181,6 +181,7 @@ const TypeId = enum { @"enum", string, list, + build_id, }; const TopLevelStep = struct { @@ -832,13 +833,13 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ } else if (mem.eql(u8, s, "false")) { return false; } else { - log.err("Expected -D{s} to be a boolean, but received '{s}'\n", .{ name, s }); + log.err("Expected -D{s} to be a boolean, but received '{s}'", .{ name, s }); self.markInvalidUserInput(); return null; } }, .list, .map => { - log.err("Expected -D{s} to be a boolean, but received a {s}.\n", .{ + log.err("Expected -D{s} to be a boolean, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -847,7 +848,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .int => switch (option_ptr.value) { .flag, .list, .map => { - log.err("Expected -D{s} to be an integer, but received a {s}.\n", .{ + log.err("Expected -D{s} to be an integer, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -856,12 +857,12 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ .scalar => |s| { const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { error.Overflow => { - log.err("-D{s} value {s} cannot fit into type {s}.\n", .{ name, s, @typeName(T) }); + log.err("-D{s} value {s} cannot fit into type {s}.", .{ name, s, @typeName(T) }); self.markInvalidUserInput(); return null; }, else => { - log.err("Expected -D{s} to be an integer of type {s}.\n", .{ name, @typeName(T) }); + log.err("Expected -D{s} to be an integer of type {s}.", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; }, @@ -871,7 +872,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .float => switch (option_ptr.value) { .flag, .map, .list => { - log.err("Expected -D{s} to be a float, but received a {s}.\n", .{ + log.err("Expected -D{s} to be a float, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -879,7 +880,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .scalar => |s| { const n = std.fmt.parseFloat(T, s) catch { - log.err("Expected -D{s} to be a float of type {s}.\n", .{ name, @typeName(T) }); + log.err("Expected -D{s} to be a float of type {s}.", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; }; @@ -888,7 +889,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .@"enum" => switch (option_ptr.value) { .flag, .map, .list => { - log.err("Expected -D{s} to be an enum, but received a {s}.\n", .{ + log.err("Expected -D{s} to be an enum, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -898,7 +899,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ if (std.meta.stringToEnum(T, s)) |enum_lit| { return enum_lit; } else { - log.err("Expected -D{s} to be of type {s}.\n", .{ name, @typeName(T) }); + log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(T) }); self.markInvalidUserInput(); return null; } @@ -906,7 +907,7 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .string => switch (option_ptr.value) { .flag, .list, .map => { - log.err("Expected -D{s} to be a string, but received a {s}.\n", .{ + log.err("Expected -D{s} to be a string, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -914,9 +915,27 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_ }, .scalar => |s| return s, }, + .build_id => switch (option_ptr.value) { + .flag, .map, .list => { + log.err("Expected -D{s} to be an enum, but received a {s}.", .{ + name, @tagName(option_ptr.value), + }); + self.markInvalidUserInput(); + return null; + }, + .scalar => |s| { + if (Step.Compile.BuildId.parse(s)) |build_id| { + return build_id; + } else |err| { + log.err("unable to parse option '-D{s}': {s}", .{ name, @errorName(err) }); + self.markInvalidUserInput(); + return null; + } + }, + }, .list => switch (option_ptr.value) { .flag, .map => { - log.err("Expected -D{s} to be a list, but received a {s}.\n", .{ + log.err("Expected -D{s} to be a list, but received a {s}.", .{ name, @tagName(option_ptr.value), }); self.markInvalidUserInput(); @@ -1183,15 +1202,18 @@ pub fn addUserInputFlag(self: *Build, name_raw: []const u8) !bool { } fn typeToEnum(comptime T: type) TypeId { - return switch (@typeInfo(T)) { - .Int => .int, - .Float => .float, - .Bool => .bool, - .Enum => .@"enum", - else => switch (T) { - []const u8 => .string, - []const []const u8 => .list, - else => @compileError("Unsupported type: " ++ @typeName(T)), + return switch (T) { + Step.Compile.BuildId => .build_id, + else => return switch (@typeInfo(T)) { + .Int => .int, + .Float => .float, + .Bool => .bool, + .Enum => .@"enum", + else => switch (T) { + []const u8 => .string, + []const []const u8 => .list, + else => @compileError("Unsupported type: " ++ @typeName(T)), + }, }, }; } diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 17429c0370..e991aff5b5 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -235,6 +235,10 @@ pub const HashHelper = struct { .none => {}, } }, + std.Build.Step.Compile.BuildId => switch (x) { + .none, .fast, .uuid, .sha1, .md5 => hh.add(std.meta.activeTag(x)), + .hexstring => |hex_string| hh.addBytes(hex_string.toSlice()), + }, else => switch (@typeInfo(@TypeOf(x))) { .Bool, .Int, .Enum, .Array => hh.addBytes(mem.asBytes(&x)), else => @compileError("unable to hash type " ++ @typeName(@TypeOf(x))), diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 2371f49daf..d0a2d69bfe 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -116,7 +116,7 @@ each_lib_rpath: ?bool = null, /// As an example, the bloaty project refuses to work unless its inputs have /// build ids, in order to prevent accidental mismatches. /// The default is to not include this section because it slows down linking. -build_id: ?bool = null, +build_id: ?BuildId = null, /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF /// file. @@ -288,6 +288,82 @@ pub const Options = struct { use_lld: ?bool = null, }; +pub const BuildId = union(enum) { + none, + fast, + uuid, + sha1, + md5, + hexstring: HexString, + + pub fn eql(a: BuildId, b: BuildId) bool { + const a_tag = std.meta.activeTag(a); + const b_tag = std.meta.activeTag(b); + if (a_tag != b_tag) return false; + return switch (a) { + .none, .fast, .uuid, .sha1, .md5 => true, + .hexstring => |a_hexstring| mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()), + }; + } + + pub const HexString = struct { + bytes: [32]u8, + len: u8, + + /// Result is byte values, *not* hex-encoded. + pub fn toSlice(hs: *const HexString) []const u8 { + return hs.bytes[0..hs.len]; + } + }; + + /// Input is byte values, *not* hex-encoded. + /// Asserts `bytes` fits inside `HexString` + pub fn initHexString(bytes: []const u8) BuildId { + var result: BuildId = .{ .hexstring = .{ + .bytes = undefined, + .len = @intCast(u8, bytes.len), + } }; + @memcpy(result.hexstring.bytes[0..bytes.len], bytes); + return result; + } + + /// Converts UTF-8 text to a `BuildId`. + pub fn parse(text: []const u8) !BuildId { + if (mem.eql(u8, text, "none")) { + return .none; + } else if (mem.eql(u8, text, "fast")) { + return .fast; + } else if (mem.eql(u8, text, "uuid")) { + return .uuid; + } else if (mem.eql(u8, text, "sha1") or mem.eql(u8, text, "tree")) { + return .sha1; + } else if (mem.eql(u8, text, "md5")) { + return .md5; + } else if (mem.startsWith(u8, text, "0x")) { + var result: BuildId = .{ .hexstring = undefined }; + const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]); + result.hexstring.len = @intCast(u8, slice.len); + return result; + } + return error.InvalidBuildIdStyle; + } + + test parse { + try std.testing.expectEqual(BuildId.md5, try parse("md5")); + try std.testing.expectEqual(BuildId.none, try parse("none")); + try std.testing.expectEqual(BuildId.fast, try parse("fast")); + try std.testing.expectEqual(BuildId.uuid, try parse("uuid")); + try std.testing.expectEqual(BuildId.sha1, try parse("sha1")); + try std.testing.expectEqual(BuildId.sha1, try parse("tree")); + + try std.testing.expect(BuildId.initHexString("").eql(try parse("0x"))); + try std.testing.expect(BuildId.initHexString("\x12\x34\x56").eql(try parse("0x123456"))); + try std.testing.expectError(error.InvalidLength, parse("0x12-34")); + try std.testing.expectError(error.InvalidCharacter, parse("0xfoobbb")); + try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx")); + } +}; + pub const Kind = enum { exe, lib, @@ -1810,7 +1886,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try addFlag(&zig_args, "valgrind", self.valgrind_support); try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); - try addFlag(&zig_args, "build-id", self.build_id); + + if (self.build_id) |build_id| { + try zig_args.append(switch (build_id) { + .hexstring => |hs| b.fmt("--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + }), + .none, .fast, .uuid, .sha1, .md5 => b.fmt("--build-id={s}", .{@tagName(build_id)}), + }); + } if (self.zig_lib_dir) |dir| { try zig_args.append("--zig-lib-dir"); diff --git a/src/Compilation.zig b/src/Compilation.zig index 0818eaafdd..0a01e9465c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -29,6 +29,7 @@ const wasi_libc = @import("wasi_libc.zig"); const fatal = @import("main.zig").fatal; const clangMain = @import("main.zig").clangMain; const Module = @import("Module.zig"); +const BuildId = std.Build.CompileStep.BuildId; const Cache = std.Build.Cache; const translate_c = @import("translate_c.zig"); const clang = @import("clang.zig"); @@ -563,7 +564,7 @@ pub const InitOptions = struct { linker_print_map: bool = false, linker_opt_bisect_limit: i32 = -1, each_lib_rpath: ?bool = null, - build_id: ?bool = null, + build_id: ?BuildId = null, disable_c_depfile: bool = false, linker_z_nodelete: bool = false, linker_z_notext: bool = false, @@ -797,7 +798,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const unwind_tables = options.want_unwind_tables orelse (link_libunwind or target_util.needUnwindTables(options.target)); const link_eh_frame_hdr = options.link_eh_frame_hdr or unwind_tables; - const build_id = options.build_id orelse false; + const build_id = options.build_id orelse .none; // Make a decision on whether to use LLD or our own linker. const use_lld = options.use_lld orelse blk: { @@ -828,7 +829,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { options.output_mode == .Lib or options.linker_script != null or options.version_script != null or options.emit_implib != null or - build_id or + build_id != .none or options.symbol_wrap_set.count() > 0) { break :blk true; diff --git a/src/link.zig b/src/link.zig index 74e3ca85fc..79ac33b892 100644 --- a/src/link.zig +++ b/src/link.zig @@ -10,6 +10,7 @@ const wasi_libc = @import("wasi_libc.zig"); const Air = @import("Air.zig"); const Allocator = std.mem.Allocator; +const BuildId = std.Build.CompileStep.BuildId; const Cache = std.Build.Cache; const Compilation = @import("Compilation.zig"); const LibCInstallation = @import("libc_installation.zig").LibCInstallation; @@ -157,7 +158,7 @@ pub const Options = struct { skip_linker_dependencies: bool, parent_compilation_link_libc: bool, each_lib_rpath: bool, - build_id: bool, + build_id: BuildId, disable_lld_caching: bool, is_test: bool, hash_style: HashStyle, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 724ec76500..f90f4ebd46 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1542,8 +1542,18 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v try argv.append("-z"); try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); - if (self.base.options.build_id) { - try argv.append("--build-id"); + switch (self.base.options.build_id) { + .none => {}, + .fast, .uuid, .sha1, .md5 => { + try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{ + @tagName(self.base.options.build_id), + })); + }, + .hexstring => |hs| { + try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + })); + }, } } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index b6f4a4cc59..cd9c44d656 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3797,8 +3797,29 @@ fn writeToFile( if (!wasm.base.options.strip) { // The build id must be computed on the main sections only, // so we have to do it now, before the debug sections. - if (wasm.base.options.build_id) { - try emitBuildIdSection(&binary_bytes); + switch (wasm.base.options.build_id) { + .none => {}, + .fast => { + var id: [16]u8 = undefined; + std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); + var uuid: [36]u8 = undefined; + _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ + std.fmt.fmtSliceHexLower(id[0..4]), + std.fmt.fmtSliceHexLower(id[4..6]), + std.fmt.fmtSliceHexLower(id[6..8]), + std.fmt.fmtSliceHexLower(id[8..10]), + std.fmt.fmtSliceHexLower(id[10..]), + }); + try emitBuildIdSection(&binary_bytes, &uuid); + }, + .hexstring => |hs| { + var buffer: [32 * 2]u8 = undefined; + const str = std.fmt.bufPrint(&buffer, "{s}", .{ + std.fmt.fmtSliceHexLower(hs.toSlice()), + }) catch unreachable; + try emitBuildIdSection(&binary_bytes, str); + }, + else => |mode| log.err("build-id '{s}' is not supported for WASM", .{@tagName(mode)}), } // if (wasm.dwarf) |*dwarf| { @@ -3942,25 +3963,17 @@ fn emitProducerSection(binary_bytes: *std.ArrayList(u8)) !void { ); } -fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8)) !void { +fn emitBuildIdSection(binary_bytes: *std.ArrayList(u8), build_id: []const u8) !void { const header_offset = try reserveCustomSectionHeader(binary_bytes); const writer = binary_bytes.writer(); - const build_id = "build_id"; - try leb.writeULEB128(writer, @intCast(u32, build_id.len)); - try writer.writeAll(build_id); - - var id: [16]u8 = undefined; - std.crypto.hash.sha3.TurboShake128(null).hash(binary_bytes.items, &id, .{}); - var uuid: [36]u8 = undefined; - _ = try std.fmt.bufPrint(&uuid, "{s}-{s}-{s}-{s}-{s}", .{ - std.fmt.fmtSliceHexLower(id[0..4]), std.fmt.fmtSliceHexLower(id[4..6]), std.fmt.fmtSliceHexLower(id[6..8]), - std.fmt.fmtSliceHexLower(id[8..10]), std.fmt.fmtSliceHexLower(id[10..]), - }); + const hdr_build_id = "build_id"; + try leb.writeULEB128(writer, @intCast(u32, hdr_build_id.len)); + try writer.writeAll(hdr_build_id); try leb.writeULEB128(writer, @as(u32, 1)); - try leb.writeULEB128(writer, @as(u32, uuid.len)); - try writer.writeAll(&uuid); + try leb.writeULEB128(writer, @intCast(u32, build_id.len)); + try writer.writeAll(build_id); try writeCustomSectionHeader( binary_bytes.items, diff --git a/src/main.zig b/src/main.zig index 6ac8636c88..4e64c06aa9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,6 +22,7 @@ const LibCInstallation = @import("libc_installation.zig").LibCInstallation; const wasi_libc = @import("wasi_libc.zig"); const translate_c = @import("translate_c.zig"); const clang = @import("clang.zig"); +const BuildId = std.Build.CompileStep.BuildId; const Cache = std.Build.Cache; const target_util = @import("target.zig"); const crash_report = @import("crash_report.zig"); @@ -493,8 +494,10 @@ const usage_build_generic = \\ -fno-each-lib-rpath Prevent adding rpath for each used dynamic library \\ -fallow-shlib-undefined Allows undefined symbols in shared libraries \\ -fno-allow-shlib-undefined Disallows undefined symbols in shared libraries - \\ -fbuild-id Helps coordinate stripped binaries with debug symbols - \\ -fno-build-id (default) Saves a bit of time linking + \\ --build-id[=style] At a minor link-time expense, coordinates stripped binaries + \\ fast, uuid, sha1, md5 with debug symbols via a '.note.gnu.build-id' section + \\ 0x[hexstring] Maximum 32 bytes + \\ none (default) Disable build-id \\ --eh-frame-hdr Enable C++ exception handling by passing --eh-frame-hdr to linker \\ --emit-relocs Enable output of relocation sections for post build tools \\ -z [arg] Set linker extension flags @@ -817,7 +820,7 @@ fn buildOutputType( var link_eh_frame_hdr = false; var link_emit_relocs = false; var each_lib_rpath: ?bool = null; - var build_id: ?bool = null; + var build_id: ?BuildId = null; var sysroot: ?[]const u8 = null; var libc_paths_file: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIBC"); var machine_code_model: std.builtin.CodeModel = .default; @@ -1202,10 +1205,6 @@ fn buildOutputType( each_lib_rpath = true; } else if (mem.eql(u8, arg, "-fno-each-lib-rpath")) { each_lib_rpath = false; - } else if (mem.eql(u8, arg, "-fbuild-id")) { - build_id = true; - } else if (mem.eql(u8, arg, "-fno-build-id")) { - build_id = false; } else if (mem.eql(u8, arg, "--test-cmd-bin")) { try test_exec_args.append(null); } else if (mem.eql(u8, arg, "--test-evented-io")) { @@ -1446,6 +1445,15 @@ fn buildOutputType( linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { linker_gc_sections = false; + } else if (mem.eql(u8, arg, "--build-id")) { + build_id = .fast; + } else if (mem.startsWith(u8, arg, "--build-id=")) { + const style = arg["--build-id=".len..]; + build_id = BuildId.parse(style) catch |err| { + fatal("unable to parse --build-id style '{s}': {s}", .{ + style, @errorName(err), + }); + }; } else if (mem.eql(u8, arg, "--debug-compile-errors")) { if (!crash_report.is_enabled) { std.log.warn("Zig was compiled in a release mode. --debug-compile-errors has no effect.", .{}); @@ -1684,9 +1692,12 @@ fn buildOutputType( if (mem.indexOfScalar(u8, linker_arg, '=')) |equals_pos| { const key = linker_arg[0..equals_pos]; const value = linker_arg[equals_pos + 1 ..]; - if (mem.eql(u8, key, "build-id")) { - build_id = true; - warn("ignoring build-id style argument: '{s}'", .{value}); + if (mem.eql(u8, key, "--build-id")) { + build_id = BuildId.parse(value) catch |err| { + fatal("unable to parse --build-id style '{s}': {s}", .{ + value, @errorName(err), + }); + }; continue; } else if (mem.eql(u8, key, "--sort-common")) { // this ignores --sort=common=<anything>; ignoring plain --sort-common @@ -1698,7 +1709,9 @@ fn buildOutputType( continue; } } - if (mem.eql(u8, linker_arg, "--as-needed")) { + if (mem.eql(u8, linker_arg, "--build-id")) { + build_id = .fast; + } else if (mem.eql(u8, linker_arg, "--as-needed")) { needed = false; } else if (mem.eql(u8, linker_arg, "--no-as-needed")) { needed = true; |
