From 172e7161a48658942c1428963448a68fa07467fa Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:34 +0200 Subject: [PATCH 1/9] 19009: zig objcopy: add --add-section support --- lib/compiler/objcopy.zig | 114 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index ac609c94e5..f534763f57 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -40,6 +40,7 @@ fn cmdObjCopy( var only_keep_debug: bool = false; var compress_debug_sections: bool = false; var listen = false; + var add_section: ?AddSectionOptions = null; while (i < args.len) : (i += 1) { const arg = args[i]; if (!mem.startsWith(u8, arg, "-")) { @@ -104,6 +105,15 @@ fn cmdObjCopy( i += 1; if (i >= args.len) fatal("expected another argument after '{s}'", .{arg}); opt_extract = args[i]; + } else if (mem.eql(u8, arg, "--add-section")) { + i += 1; + if (i >= args.len) fatal("expected name and filename arguments after '{s}'", .{arg}); + + if (splitOption(args[i])) |split| { + add_section = .{ .section_name = split.first, .file = split.second }; + } else { + fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); + } } else { fatal("unrecognized argument: '{s}'", .{arg}); } @@ -156,6 +166,7 @@ fn cmdObjCopy( .ofmt = out_fmt, .only_section = only_section, .pad_to = pad_to, + .add_section = add_section, }); }, .elf => { @@ -175,6 +186,7 @@ fn cmdObjCopy( .add_debuglink = opt_add_debuglink, .extract_to = opt_extract, .compress_debug = compress_debug_sections, + .add_section = add_section, }); return std.process.cleanExit(); }, @@ -229,6 +241,7 @@ const usage = \\ --add-gnu-debuglink= Creates a .gnu_debuglink section which contains a reference to and adds it to the output file. \\ --extract-to Extract the removed sections into , and add a .gnu-debuglink section. \\ --compress-debug-sections Compress DWARF debug sections with zlib + \\ --add-section = Add file content from with the section name . \\ ; @@ -236,6 +249,12 @@ pub const EmitRawElfOptions = struct { ofmt: std.Target.ObjectFormat, only_section: ?[]const u8 = null, pad_to: ?u64 = null, + add_section: ?AddSectionOptions, +}; + +const AddSectionOptions = struct { + section_name: []const u8, + file: []const u8, }; fn emitElf( @@ -678,6 +697,7 @@ const StripElfOptions = struct { strip_debug: bool = false, only_keep_debug: bool = false, compress_debug: bool = false, + add_section: ?AddSectionOptions, }; fn stripElf( @@ -721,6 +741,14 @@ fn stripElf( var elf_file = try ElfFile(is_64).parse(allocator, in_file, elf_hdr); defer elf_file.deinit(); + if (options.add_section) |user_section| { + for (elf_file.sections) |section| { + if (std.mem.eql(u8, section.name, user_section.section_name)) { + fatal("zig objcopy: unable to add section '{s}'. Section already exists in input", .{user_section.section_name}); + } + } + } + if (filter_complement) |flt| { // write the .dbg file and close it, so it can be read back to compute the debuglink checksum. const path = options.extract_to.?; @@ -733,7 +761,7 @@ fn stripElf( } const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null; - try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug }); + try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug, .add_section = options.add_section }); }, } } @@ -896,6 +924,7 @@ fn ElfFile(comptime is_64: bool) type { section_filter: Filter = .all, debuglink: ?DebugLink = null, compress_debug: bool = false, + add_section: ?AddSectionOptions = null, }; fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void { var arena = std.heap.ArenaAllocator.init(gpa); @@ -934,6 +963,10 @@ fn ElfFile(comptime is_64: bool) type { if (options.debuglink != null) next_idx += 1; + if (options.add_section != null) { + next_idx += 1; + } + break :blk next_idx; }; @@ -959,6 +992,28 @@ fn ElfFile(comptime is_64: bool) type { break :blk new_offset; }; + // add user section to the string table if needed + const user_section_name: u32 = blk: { + if (options.add_section == null) break :blk elf.SHN_UNDEF; + if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF) + fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed? + + const strtab = &self.sections[self.raw_elf_header.e_shstrndx]; + const update = §ions_update[self.raw_elf_header.e_shstrndx]; + + const name = options.add_section.?.section_name; + const new_offset: u32 = @intCast(strtab.payload.?.len); + const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1); + @memcpy(buf[0..new_offset], strtab.payload.?); + @memcpy(buf[new_offset..][0..name.len], name); + buf[new_offset + name.len] = 0; + + assert(update.action == .keep); + update.payload = buf; + + break :blk new_offset; + }; + // maybe compress .debug sections if (options.compress_debug) { for (self.sections[1..], sections_update[1..]) |section, *update| { @@ -1134,6 +1189,35 @@ fn ElfFile(comptime is_64: bool) type { eof_offset += @as(Elf_OffSize, @intCast(payload.len)); } + // add user section + if (options.add_section) |add_section| { + var section_file = fs.cwd().openFile(add_section.file, .{}) catch |err| + fatal("unable to open '{s}': {s}", .{ add_section.file, @errorName(err) }); + defer section_file.close(); + + const max_size = std.math.maxInt(usize); + const payload = try section_file.readToEndAlloc(arena.allocator(), max_size); + const flags = 0; // TODO: 19009: support --set-section-flags + const alignment = 4; // TODO: 19009: support --set-section-alignment + + dest_sections[dest_section_idx] = Elf_Shdr{ + .sh_name = user_section_name, + .sh_type = elf.SHT_PROGBITS, + .sh_flags = flags, + .sh_addr = 0, + .sh_offset = eof_offset, + .sh_size = @intCast(payload.len), + .sh_link = elf.SHN_UNDEF, + .sh_info = elf.SHN_UNDEF, + .sh_addralign = alignment, + .sh_entsize = 0, + }; + dest_section_idx += 1; + + cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = payload, .out_offset = eof_offset } }); + eof_offset += @as(Elf_OffSize, @intCast(payload.len)); + } + assert(dest_section_idx == new_shnum); break :blk dest_sections; }; @@ -1361,3 +1445,31 @@ const ElfFileHelper = struct { return hasher.final(); } }; + +const SplitResult = struct { first: []const u8, second: []const u8 }; + +fn splitOption(option: []const u8) ?SplitResult { + const separator = '='; + if (option.len < 3) return null; // minimum "a=b" + for (1..option.len - 1) |i| { + if (option[i] == separator) return .{ + .first = option[0..i], + .second = option[i + 1 ..], + }; + } + return null; +} + +test "Split option" { + { + const split = splitOption("a=123"); + try std.testing.expect(split != null); + try std.testing.expectEqualStrings("a", split.?.first); + try std.testing.expectEqualStrings("123", split.?.second); + } + + try std.testing.expectEqual(null, splitOption("")); + try std.testing.expectEqual(null, splitOption("=abc")); + try std.testing.expectEqual(null, splitOption("abc=")); + try std.testing.expectEqual(null, splitOption("abc")); +} From f72961ee306408cdd3ee4ef5a8bc5301f94fc7e5 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:34 +0200 Subject: [PATCH 2/9] 19009: add --set-section-alignment and --set-section-flags arguments to zig objcopy --- lib/compiler/objcopy.zig | 116 +++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index f534763f57..51bdab5b17 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -41,6 +41,8 @@ fn cmdObjCopy( var compress_debug_sections: bool = false; var listen = false; var add_section: ?AddSectionOptions = null; + var set_section_alignment: ?SectionAlignmentOptions = null; + var set_section_flags: ?SectionFlagsOptions = null; while (i < args.len) : (i += 1) { const arg = args[i]; if (!mem.startsWith(u8, arg, "-")) { @@ -105,12 +107,34 @@ fn cmdObjCopy( i += 1; if (i >= args.len) fatal("expected another argument after '{s}'", .{arg}); opt_extract = args[i]; - } else if (mem.eql(u8, arg, "--add-section")) { + } else if (mem.eql(u8, arg, "--set-section-alignment")) { i += 1; - if (i >= args.len) fatal("expected name and filename arguments after '{s}'", .{arg}); + if (i >= args.len) fatal("expected section name and alignment arguments after '{s}'", .{arg}); if (splitOption(args[i])) |split| { - add_section = .{ .section_name = split.first, .file = split.second }; + const alignment = std.fmt.parseInt(u32, split.second, 10) catch |err| { + fatal("unable to parse alignment number: '{s}': {s}", .{ split.second, @errorName(err) }); + }; + if (!std.math.isPowerOfTwo(alignment)) fatal("alignment must be a power of two", .{}); + set_section_alignment = .{ .section_name = split.first, .alignment = alignment }; + } else { + fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); + } + } else if (mem.eql(u8, arg, "--set-section-flags")) { + i += 1; + if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg}); + + if (splitOption(args[i])) |split| { + set_section_flags = .{ .section_name = split.first, .flags = split.second }; + } else { + fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); + } + } else if (mem.eql(u8, arg, "--add-section")) { + i += 1; + if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg}); + + if (splitOption(args[i])) |split| { + add_section = .{ .section_name = split.first, .file_path = split.second }; } else { fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); } @@ -167,6 +191,8 @@ fn cmdObjCopy( .only_section = only_section, .pad_to = pad_to, .add_section = add_section, + .set_section_alignment = set_section_alignment, + .set_section_flags = set_section_flags, }); }, .elf => { @@ -187,6 +213,8 @@ fn cmdObjCopy( .extract_to = opt_extract, .compress_debug = compress_debug_sections, .add_section = add_section, + .set_section_alignment = set_section_alignment, + .set_section_flags = set_section_flags, }); return std.process.cleanExit(); }, @@ -229,19 +257,21 @@ const usage = \\Usage: zig objcopy [options] input output \\ \\Options: - \\ -h, --help Print this help and exit - \\ --output-target= Format of the output file - \\ -O Alias for --output-target - \\ --only-section=
Remove all but
- \\ -j Alias for --only-section - \\ --pad-to Pad the last section up to address - \\ --strip-debug, -g Remove all debug sections from the output. - \\ --strip-all, -S Remove all debug sections and symbol table from the output. - \\ --only-keep-debug Strip a file, removing contents of any sections that would not be stripped by --strip-debug and leaving the debugging sections intact. - \\ --add-gnu-debuglink= Creates a .gnu_debuglink section which contains a reference to and adds it to the output file. - \\ --extract-to Extract the removed sections into , and add a .gnu-debuglink section. - \\ --compress-debug-sections Compress DWARF debug sections with zlib - \\ --add-section = Add file content from with the section name . + \\ -h, --help Print this help and exit + \\ --output-target= Format of the output file + \\ -O Alias for --output-target + \\ --only-section=
Remove all but
+ \\ -j Alias for --only-section + \\ --pad-to Pad the last section up to address + \\ --strip-debug, -g Remove all debug sections from the output. + \\ --strip-all, -S Remove all debug sections and symbol table from the output. + \\ --only-keep-debug Strip a file, removing contents of any sections that would not be stripped by --strip-debug and leaving the debugging sections intact. + \\ --add-gnu-debuglink= Creates a .gnu_debuglink section which contains a reference to and adds it to the output file. + \\ --extract-to Extract the removed sections into , and add a .gnu-debuglink section. + \\ --compress-debug-sections Compress DWARF debug sections with zlib + \\ --set-section-alignment = Set alignment of section to bytes. Must be a power of two. + \\ --set-section-flags = Set flags of section to represented as a comma separated set of flags. + \\ --add-section = Add file content from with the section name . \\ ; @@ -250,11 +280,25 @@ pub const EmitRawElfOptions = struct { only_section: ?[]const u8 = null, pad_to: ?u64 = null, add_section: ?AddSectionOptions, + set_section_alignment: ?SectionAlignmentOptions, + set_section_flags: ?SectionFlagsOptions, }; const AddSectionOptions = struct { section_name: []const u8, - file: []const u8, + // file to store in new section + file_path: []const u8, +}; + +const SectionAlignmentOptions = struct { + section_name: []const u8, + alignment: u32, +}; + +const SectionFlagsOptions = struct { + section_name: []const u8, + // comma separated string representation of the SHF_x flags for Shdr.sh_flags + flags: []const u8, }; fn emitElf( @@ -698,6 +742,8 @@ const StripElfOptions = struct { only_keep_debug: bool = false, compress_debug: bool = false, add_section: ?AddSectionOptions, + set_section_alignment: ?SectionAlignmentOptions, + set_section_flags: ?SectionFlagsOptions, }; fn stripElf( @@ -761,7 +807,14 @@ fn stripElf( } const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null; - try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug, .add_section = options.add_section }); + try elf_file.emit(allocator, out_file, in_file, .{ + .section_filter = filter, + .debuglink = debuglink, + .compress_debug = options.compress_debug, + .add_section = options.add_section, + .set_section_alignment = options.set_section_alignment, + .set_section_flags = options.set_section_flags, + }); }, } } @@ -925,6 +978,8 @@ fn ElfFile(comptime is_64: bool) type { debuglink: ?DebugLink = null, compress_debug: bool = false, add_section: ?AddSectionOptions = null, + set_section_alignment: ?SectionAlignmentOptions = null, + set_section_flags: ?SectionFlagsOptions = null, }; fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void { var arena = std.heap.ArenaAllocator.init(gpa); @@ -1191,14 +1246,19 @@ fn ElfFile(comptime is_64: bool) type { // add user section if (options.add_section) |add_section| { - var section_file = fs.cwd().openFile(add_section.file, .{}) catch |err| - fatal("unable to open '{s}': {s}", .{ add_section.file, @errorName(err) }); + var section_file = fs.cwd().openFile(add_section.file_path, .{}) catch |err| + fatal("unable to open '{s}': {s}", .{ add_section.file_path, @errorName(err) }); defer section_file.close(); const max_size = std.math.maxInt(usize); const payload = try section_file.readToEndAlloc(arena.allocator(), max_size); const flags = 0; // TODO: 19009: support --set-section-flags - const alignment = 4; // TODO: 19009: support --set-section-alignment + const alignment = align_bytes: { + if (options.set_section_alignment) |set_align| { + if (std.mem.eql(u8, set_align.section_name, add_section.section_name)) break :align_bytes set_align.alignment; + } + break :align_bytes 4; + }; dest_sections[dest_section_idx] = Elf_Shdr{ .sh_name = user_section_name, @@ -1218,6 +1278,16 @@ fn ElfFile(comptime is_64: bool) type { eof_offset += @as(Elf_OffSize, @intCast(payload.len)); } + // overwrite section alignment + { + // TODO: 19009: NYI + } + + // overwrite section flags + { + // TODO: 19009: NYI + } + assert(dest_section_idx == new_shnum); break :blk dest_sections; }; @@ -1462,9 +1532,9 @@ fn splitOption(option: []const u8) ?SplitResult { test "Split option" { { - const split = splitOption("a=123"); + const split = splitOption(".abc=123"); try std.testing.expect(split != null); - try std.testing.expectEqualStrings("a", split.?.first); + try std.testing.expectEqualStrings(".abc", split.?.first); try std.testing.expectEqualStrings("123", split.?.second); } From caa699fc68bcdc3c5e80eb089d8af62000e06757 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:34 +0200 Subject: [PATCH 3/9] 19009: zig objcopy: implement --set-section-alignment --- lib/compiler/objcopy.zig | 51 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 51bdab5b17..afa714d8db 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -1252,13 +1252,8 @@ fn ElfFile(comptime is_64: bool) type { const max_size = std.math.maxInt(usize); const payload = try section_file.readToEndAlloc(arena.allocator(), max_size); - const flags = 0; // TODO: 19009: support --set-section-flags - const alignment = align_bytes: { - if (options.set_section_alignment) |set_align| { - if (std.mem.eql(u8, set_align.section_name, add_section.section_name)) break :align_bytes set_align.alignment; - } - break :align_bytes 4; - }; + const flags = 0; + const alignment = 4; dest_sections[dest_section_idx] = Elf_Shdr{ .sh_name = user_section_name, @@ -1278,20 +1273,42 @@ fn ElfFile(comptime is_64: bool) type { eof_offset += @as(Elf_OffSize, @intCast(payload.len)); } - // overwrite section alignment - { - // TODO: 19009: NYI - } - - // overwrite section flags - { - // TODO: 19009: NYI - } - assert(dest_section_idx == new_shnum); break :blk dest_sections; }; + // --set-section-alignment: overwrite alignment + if (options.set_section_alignment) |set_align| { + if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF) + fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed? + + const strtab = §ions_update[self.raw_elf_header.e_shstrndx]; + for (updated_section_header) |*section| { + const section_name = std.mem.span(@as([*:0]const u8, @ptrCast(&strtab.payload.?[section.sh_name]))); + if (std.mem.eql(u8, section_name, set_align.section_name)) { + section.sh_addralign = set_align.alignment; + break; + } + } else std.log.warn("Skipping --set-section-alignment. Section '{s}' not found", .{set_align.section_name}); + } + + // --set-section-flags: overwrite flags + if (options.set_section_flags) |set_flags| { + if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF) + fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed? + + const strtab = §ions_update[self.raw_elf_header.e_shstrndx]; + for (updated_section_header) |*section| { + const section_name = std.mem.span(@as([*:0]const u8, @ptrCast(&strtab.payload.?[section.sh_name]))); + if (std.mem.eql(u8, section_name, set_flags.section_name)) { + // TODO: 19009: map flags string to bitfield. + // section.sh_flags = set_flags.flags; + section.sh_flags = 0; + break; + } + } else std.log.warn("Skipping --set-section-flags. Section '{s}' not found", .{set_flags.section_name}); + } + // write the section header at the tail { const offset = std.mem.alignForward(Elf_OffSize, eof_offset, @alignOf(Elf_Shdr)); From 8cd7a9e5fc0be2328388acb138ccce77a45603bc Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:34 +0200 Subject: [PATCH 4/9] 19009: zig objcopy: parse section flags --- lib/compiler/objcopy.zig | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index afa714d8db..0822e6fa99 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -125,6 +125,8 @@ fn cmdObjCopy( if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg}); if (splitOption(args[i])) |split| { + const flags = parseSectionFlags(split.second); + _ = flags; // TODO: 19009: integrate set_section_flags = .{ .section_name = split.first, .flags = split.second }; } else { fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); @@ -1533,6 +1535,100 @@ const ElfFileHelper = struct { } }; +/// Supporting a subset of GNU and LLVM objcopy for ELF only +/// GNU: +/// alloc: add SHF_ALLOC +/// contents: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing +/// load: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing +/// noload: not supported +/// readonly: clear default SHF_WRITE flag +/// code: add SHF_EXECINSTR +/// data: not supported +/// rom: not supported +/// exclude: add SHF_EXCLUDE +/// share: not supported +/// debug: not supported +/// large: add SHF_X86_64_LARGE. Fatal error if target is not x86_64 +/// +/// LLVM: +/// merge: add SHF_MERGE +/// strings: add SHF_STRINGS +const SectionFlags = packed struct { + alloc: bool = false, + contents: bool = false, + load: bool = false, + noload: bool = false, + readonly: bool = false, + code: bool = false, + data: bool = false, + rom: bool = false, + exclude: bool = false, + shared: bool = false, + debug: bool = false, + large: bool = false, + merge: bool = false, + strings: bool = false, +}; + +fn parseSectionFlags(comma_separated_flags: []const u8) SectionFlags { + const P = struct { + fn parse(flags: *SectionFlags, string: []const u8) void { + if (string.len == 0) return; + + if (std.mem.eql(u8, string, "alloc")) { + flags.alloc = true; + } else if (std.mem.eql(u8, string, "contents")) { + flags.contents = true; + } else if (std.mem.eql(u8, string, "load")) { + flags.load = true; + } else if (std.mem.eql(u8, string, "noload")) { + flags.noload = true; + } else if (std.mem.eql(u8, string, "readonly")) { + flags.readonly = true; + } else if (std.mem.eql(u8, string, "code")) { + flags.code = true; + } else if (std.mem.eql(u8, string, "data")) { + flags.data = true; + } else if (std.mem.eql(u8, string, "rom")) { + flags.rom = true; + } else if (std.mem.eql(u8, string, "exclude")) { + flags.exclude = true; + } else if (std.mem.eql(u8, string, "shared")) { + flags.shared = true; + } else if (std.mem.eql(u8, string, "debug")) { + flags.debug = true; + } else if (std.mem.eql(u8, string, "large")) { + flags.large = true; + } else if (std.mem.eql(u8, string, "merge")) { + flags.merge = true; + } else if (std.mem.eql(u8, string, "strings")) { + flags.strings = true; + } else { + std.log.err("Skipping unrecognized section flag '{s}'", .{string}); + } + } + }; + + var flags = SectionFlags{}; + var start: usize = 0; + for (comma_separated_flags, 0..) |c, i| { + if (c == ',') { + defer start = i + 1; + const string = comma_separated_flags[start..i]; + P.parse(&flags, string); + } + } + P.parse(&flags, comma_separated_flags[start..]); + return flags; +} + +test "Parse section flags" { + const F = SectionFlags; + try std.testing.expectEqual(F{}, parseSectionFlags("")); + try std.testing.expectEqual(F{ .alloc = true }, parseSectionFlags("alloc")); + try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code")); +} + const SplitResult = struct { first: []const u8, second: []const u8 }; fn splitOption(option: []const u8) ?SplitResult { From 8f55efc1af9de1ae00aa07bfa6fbbcfeeca02eb4 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:34 +0200 Subject: [PATCH 5/9] 19009: zig objcopy: integrate section flags for --set-section-flags command --- lib/compiler/objcopy.zig | 99 +++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 0822e6fa99..ab0c340ac3 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -41,8 +41,8 @@ fn cmdObjCopy( var compress_debug_sections: bool = false; var listen = false; var add_section: ?AddSectionOptions = null; - var set_section_alignment: ?SectionAlignmentOptions = null; - var set_section_flags: ?SectionFlagsOptions = null; + var set_section_alignment: ?SetSectionAlignmentOptions = null; + var set_section_flags: ?SetSectionFlagsOptions = null; while (i < args.len) : (i += 1) { const arg = args[i]; if (!mem.startsWith(u8, arg, "-")) { @@ -125,9 +125,7 @@ fn cmdObjCopy( if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg}); if (splitOption(args[i])) |split| { - const flags = parseSectionFlags(split.second); - _ = flags; // TODO: 19009: integrate - set_section_flags = .{ .section_name = split.first, .flags = split.second }; + set_section_flags = .{ .section_name = split.first, .flags = parseSectionFlags(split.second) }; } else { fatal("unrecognized argument: '{s}', expecting =", .{args[i]}); } @@ -282,8 +280,8 @@ pub const EmitRawElfOptions = struct { only_section: ?[]const u8 = null, pad_to: ?u64 = null, add_section: ?AddSectionOptions, - set_section_alignment: ?SectionAlignmentOptions, - set_section_flags: ?SectionFlagsOptions, + set_section_alignment: ?SetSectionAlignmentOptions, + set_section_flags: ?SetSectionFlagsOptions, }; const AddSectionOptions = struct { @@ -292,15 +290,14 @@ const AddSectionOptions = struct { file_path: []const u8, }; -const SectionAlignmentOptions = struct { +const SetSectionAlignmentOptions = struct { section_name: []const u8, alignment: u32, }; -const SectionFlagsOptions = struct { +const SetSectionFlagsOptions = struct { section_name: []const u8, - // comma separated string representation of the SHF_x flags for Shdr.sh_flags - flags: []const u8, + flags: SectionFlags, }; fn emitElf( @@ -744,8 +741,8 @@ const StripElfOptions = struct { only_keep_debug: bool = false, compress_debug: bool = false, add_section: ?AddSectionOptions, - set_section_alignment: ?SectionAlignmentOptions, - set_section_flags: ?SectionFlagsOptions, + set_section_alignment: ?SetSectionAlignmentOptions, + set_section_flags: ?SetSectionFlagsOptions, }; fn stripElf( @@ -980,8 +977,8 @@ fn ElfFile(comptime is_64: bool) type { debuglink: ?DebugLink = null, compress_debug: bool = false, add_section: ?AddSectionOptions = null, - set_section_alignment: ?SectionAlignmentOptions = null, - set_section_flags: ?SectionFlagsOptions = null, + set_section_alignment: ?SetSectionAlignmentOptions = null, + set_section_flags: ?SetSectionFlagsOptions = null, }; fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void { var arena = std.heap.ArenaAllocator.init(gpa); @@ -1246,7 +1243,7 @@ fn ElfFile(comptime is_64: bool) type { eof_offset += @as(Elf_OffSize, @intCast(payload.len)); } - // add user section + // --add-section if (options.add_section) |add_section| { var section_file = fs.cwd().openFile(add_section.file_path, .{}) catch |err| fatal("unable to open '{s}': {s}", .{ add_section.file_path, @errorName(err) }); @@ -1303,9 +1300,41 @@ fn ElfFile(comptime is_64: bool) type { for (updated_section_header) |*section| { const section_name = std.mem.span(@as([*:0]const u8, @ptrCast(&strtab.payload.?[section.sh_name]))); if (std.mem.eql(u8, section_name, set_flags.section_name)) { - // TODO: 19009: map flags string to bitfield. - // section.sh_flags = set_flags.flags; - section.sh_flags = 0; + section.sh_flags = std.elf.SHF_WRITE; // default is writable cleared by "readonly" + const f = set_flags.flags; + + // Supporting a subset of GNU and LLVM objcopy for ELF only + // GNU: + // alloc: add SHF_ALLOC + // contents: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing + // load: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents) + // noload: not ELF relevant + // readonly: clear default SHF_WRITE flag + // code: add SHF_EXECINSTR + // data: not ELF relevant + // rom: ignored + // exclude: add SHF_EXCLUDE + // share: not ELF relevant + // debug: not ELF relevant + // large: add SHF_X86_64_LARGE. Fatal error if target is not x86_64 + if (f.alloc) section.sh_flags |= std.elf.SHF_ALLOC; + if (f.contents or f.load) { + if (section.sh_type == std.elf.SHT_NOBITS) section.sh_type = std.elf.SHT_PROGBITS; + } + if (f.readonly) section.sh_flags &= ~@as(@TypeOf(section.sh_type), std.elf.SHF_WRITE); + if (f.code) section.sh_flags |= std.elf.SHF_EXECINSTR; + if (f.exclude) section.sh_flags |= std.elf.SHF_EXCLUDE; + if (f.large) { + if (updated_elf_header.e_machine != std.elf.EM.X86_64) + fatal("zig objcopy: 'large' section flag is only supported on x86_64 targets", .{}); + section.sh_flags |= std.elf.SHF_X86_64_LARGE; + } + + // LLVM: + // merge: add SHF_MERGE + // strings: add SHF_STRINGS + if (f.merge) section.sh_flags |= std.elf.SHF_MERGE; + if (f.strings) section.sh_flags |= std.elf.SHF_STRINGS; break; } } else std.log.warn("Skipping --set-section-flags. Section '{s}' not found", .{set_flags.section_name}); @@ -1535,24 +1564,6 @@ const ElfFileHelper = struct { } }; -/// Supporting a subset of GNU and LLVM objcopy for ELF only -/// GNU: -/// alloc: add SHF_ALLOC -/// contents: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing -/// load: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing -/// noload: not supported -/// readonly: clear default SHF_WRITE flag -/// code: add SHF_EXECINSTR -/// data: not supported -/// rom: not supported -/// exclude: add SHF_EXCLUDE -/// share: not supported -/// debug: not supported -/// large: add SHF_X86_64_LARGE. Fatal error if target is not x86_64 -/// -/// LLVM: -/// merge: add SHF_MERGE -/// strings: add SHF_STRINGS const SectionFlags = packed struct { alloc: bool = false, contents: bool = false, @@ -1604,29 +1615,33 @@ fn parseSectionFlags(comma_separated_flags: []const u8) SectionFlags { } else if (std.mem.eql(u8, string, "strings")) { flags.strings = true; } else { - std.log.err("Skipping unrecognized section flag '{s}'", .{string}); + std.log.warn("Skipping unrecognized section flag '{s}'", .{string}); } } }; var flags = SectionFlags{}; - var start: usize = 0; + var offset: usize = 0; for (comma_separated_flags, 0..) |c, i| { if (c == ',') { - defer start = i + 1; - const string = comma_separated_flags[start..i]; + defer offset = i + 1; + const string = comma_separated_flags[offset..i]; P.parse(&flags, string); } } - P.parse(&flags, comma_separated_flags[start..]); + P.parse(&flags, comma_separated_flags[offset..]); return flags; } test "Parse section flags" { const F = SectionFlags; try std.testing.expectEqual(F{}, parseSectionFlags("")); + try std.testing.expectEqual(F{}, parseSectionFlags(",")); + try std.testing.expectEqual(F{}, parseSectionFlags("abc")); try std.testing.expectEqual(F{ .alloc = true }, parseSectionFlags("alloc")); + try std.testing.expectEqual(F{ .data = true }, parseSectionFlags("data,")); try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code")); + try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code,not_supported")); } const SplitResult = struct { first: []const u8, second: []const u8 }; From e1d54b6d1adb4b8e851bd949ade8cdc5fb840a9b Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:35 +0200 Subject: [PATCH 6/9] 19009: zig objcopy: integrate --add-section, --set-section-alignment and --set-section-flags into std.Build.Step.ObjCopy --- lib/compiler/objcopy.zig | 31 +++++++------ lib/std/Build/Step/ObjCopy.zig | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index ab0c340ac3..e609e39b9b 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -40,9 +40,9 @@ fn cmdObjCopy( var only_keep_debug: bool = false; var compress_debug_sections: bool = false; var listen = false; - var add_section: ?AddSectionOptions = null; - var set_section_alignment: ?SetSectionAlignmentOptions = null; - var set_section_flags: ?SetSectionFlagsOptions = null; + var add_section: ?AddSection = null; + var set_section_alignment: ?SetSectionAlignment = null; + var set_section_flags: ?SetSectionFlags = null; while (i < args.len) : (i += 1) { const arg = args[i]; if (!mem.startsWith(u8, arg, "-")) { @@ -279,23 +279,22 @@ pub const EmitRawElfOptions = struct { ofmt: std.Target.ObjectFormat, only_section: ?[]const u8 = null, pad_to: ?u64 = null, - add_section: ?AddSectionOptions, - set_section_alignment: ?SetSectionAlignmentOptions, - set_section_flags: ?SetSectionFlagsOptions, + add_section: ?AddSection, + set_section_alignment: ?SetSectionAlignment, + set_section_flags: ?SetSectionFlags, }; -const AddSectionOptions = struct { +const AddSection = struct { section_name: []const u8, - // file to store in new section file_path: []const u8, }; -const SetSectionAlignmentOptions = struct { +const SetSectionAlignment = struct { section_name: []const u8, alignment: u32, }; -const SetSectionFlagsOptions = struct { +const SetSectionFlags = struct { section_name: []const u8, flags: SectionFlags, }; @@ -740,9 +739,9 @@ const StripElfOptions = struct { strip_debug: bool = false, only_keep_debug: bool = false, compress_debug: bool = false, - add_section: ?AddSectionOptions, - set_section_alignment: ?SetSectionAlignmentOptions, - set_section_flags: ?SetSectionFlagsOptions, + add_section: ?AddSection, + set_section_alignment: ?SetSectionAlignment, + set_section_flags: ?SetSectionFlags, }; fn stripElf( @@ -976,9 +975,9 @@ fn ElfFile(comptime is_64: bool) type { section_filter: Filter = .all, debuglink: ?DebugLink = null, compress_debug: bool = false, - add_section: ?AddSectionOptions = null, - set_section_alignment: ?SetSectionAlignmentOptions = null, - set_section_flags: ?SetSectionFlagsOptions = null, + add_section: ?AddSection = null, + set_section_alignment: ?SetSectionAlignment = null, + set_section_flags: ?SetSectionFlags = null, }; fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void { var arena = std.heap.ArenaAllocator.init(gpa); diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig index 6ed23d3fb4..8f0f62e222 100644 --- a/lib/std/Build/Step/ObjCopy.zig +++ b/lib/std/Build/Step/ObjCopy.zig @@ -26,6 +26,50 @@ pub const Strip = enum { debug_and_symbols, }; +pub const SectionFlags = packed struct { + /// add SHF_ALLOC + alloc: bool = false, + + /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing + contents: bool = false, + + /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents) + load: bool = false, + + /// readonly: clear default SHF_WRITE flag + readonly: bool = false, + + /// add SHF_EXECINSTR + code: bool = false, + + /// add SHF_EXCLUDE + exclude: bool = false, + + /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64 + large: bool = false, + + /// add SHF_MERGE + merge: bool = false, + + /// add SHF_STRINGS + strings: bool = false, +}; + +pub const AddSection = struct { + section_name: []const u8, + file_path: std.Build.LazyPath, +}; + +pub const SetSectionAlignment = struct { + section_name: []const u8, + alignment: u32, +}; + +pub const SetSectionFlags = struct { + section_name: []const u8, + flags: SectionFlags, +}; + step: Step, input_file: std.Build.LazyPath, basename: []const u8, @@ -38,6 +82,10 @@ pad_to: ?u64, strip: Strip, compress_debug: bool, +add_section: ?AddSection, +set_section_alignment: ?SetSectionAlignment, +set_section_flags: ?SetSectionFlags, + pub const Options = struct { basename: ?[]const u8 = null, format: ?RawFormat = null, @@ -51,6 +99,10 @@ pub const Options = struct { /// note: the `basename` is baked into the elf file to specify the link to the separate debug file. /// see https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html extract_to_separate_file: bool = false, + + add_section: ?AddSection = null, + set_section_alignment: ?SetSectionAlignment = null, + set_section_flags: ?SetSectionFlags = null, }; pub fn create( @@ -75,6 +127,9 @@ pub fn create( .pad_to = options.pad_to, .strip = options.strip, .compress_debug = options.compress_debug, + .add_section = options.add_section, + .set_section_alignment = options.set_section_alignment, + .set_section_flags = options.set_section_flags, }; input_file.addStepDependencies(&objcopy.step); return objcopy; @@ -155,6 +210,31 @@ fn make(step: *Step, options: Step.MakeOptions) !void { if (objcopy.output_file_debug != null) { try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})}); } + if (objcopy.add_section) |section| { + try argv.append("--add-section"); + try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath(b) })}); + } + if (objcopy.set_section_alignment) |set_align| { + try argv.append("--set-section-alignment"); + try argv.appendSlice(&.{b.fmt("{s}={d}", .{ set_align.section_name, set_align.alignment })}); + } + if (objcopy.set_section_flags) |set_flags| { + const f = set_flags.flags; + // trailing comma is allowed + try argv.append("--set-section-flags"); + try argv.appendSlice(&.{b.fmt("{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{ + set_flags.section_name, + if (f.alloc) "alloc," else "", + if (f.contents) "contents," else "", + if (f.load) "load," else "", + if (f.readonly) "readonly," else "", + if (f.code) "code," else "", + if (f.exclude) "exclude," else "", + if (f.large) "large," else "", + if (f.merge) "merge," else "", + if (f.strings) "strings," else "", + })}); + } try argv.appendSlice(&.{ full_src_path, full_dest_path }); From 182c12de6989a7dd9419917a6cd0e6e804bc9d00 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:35 +0200 Subject: [PATCH 7/9] 19009: zig objcopy: fix typo in abort messages --- lib/compiler/objcopy.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index e609e39b9b..8bd2729702 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -865,7 +865,7 @@ fn ElfFile(comptime is_64: bool) type { // program header: list of segments const program_segments = blk: { if (@sizeOf(Elf_Phdr) != header.phentsize) - fatal("zig objcopy: unsuported ELF file, unexpected phentsize ({d})", .{header.phentsize}); + fatal("zig objcopy: unsupported ELF file, unexpected phentsize ({d})", .{header.phentsize}); const program_header = try allocator.alloc(Elf_Phdr, header.phnum); const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(program_header), header.phoff); @@ -877,7 +877,7 @@ fn ElfFile(comptime is_64: bool) type { // section header const sections = blk: { if (@sizeOf(Elf_Shdr) != header.shentsize) - fatal("zig objcopy: unsuported ELF file, unexpected shentsize ({d})", .{header.shentsize}); + fatal("zig objcopy: unsupported ELF file, unexpected shentsize ({d})", .{header.shentsize}); const section_header = try allocator.alloc(Section, header.shnum); @@ -1126,7 +1126,7 @@ fn ElfFile(comptime is_64: bool) type { if (section.section.sh_type == elf.SHT_NOBITS) continue; if (section.section.sh_offset < offset) { - fatal("zig objcopy: unsuported ELF file", .{}); + fatal("zig objcopy: unsupported ELF file", .{}); } offset = section.section.sh_offset; } From 19a0864e4f96d5c20026dae87c0f9a784ae54272 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:38:35 +0200 Subject: [PATCH 8/9] 19009: zig objcopy: allow --add-section, --set-section-alignment and --set-section-flags only if the target is an ELF file --- lib/compiler/objcopy.zig | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 8bd2729702..fe3d2fbc57 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -185,14 +185,17 @@ fn cmdObjCopy( fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{}); if (opt_extract != null) fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{}); + if (add_section != null) + fatal("zig objcopy: ELF to RAW or HEX copying does not support --add-section", .{}); + if (set_section_alignment != null) + fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_alignment", .{}); + if (set_section_flags != null) + fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_flags", .{}); try emitElf(arena, in_file, out_file, elf_hdr, .{ .ofmt = out_fmt, .only_section = only_section, .pad_to = pad_to, - .add_section = add_section, - .set_section_alignment = set_section_alignment, - .set_section_flags = set_section_flags, }); }, .elf => { @@ -279,9 +282,9 @@ pub const EmitRawElfOptions = struct { ofmt: std.Target.ObjectFormat, only_section: ?[]const u8 = null, pad_to: ?u64 = null, - add_section: ?AddSection, - set_section_alignment: ?SetSectionAlignment, - set_section_flags: ?SetSectionFlags, + add_section: ?AddSection = null, + set_section_alignment: ?SetSectionAlignment = null, + set_section_flags: ?SetSectionFlags = null, }; const AddSection = struct { From c3f1ff8b0d2aa6fa94854775b80d57c251ce1438 Mon Sep 17 00:00:00 2001 From: Patrick Wickenhaeuser <35903594+patrickwick@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:49:18 +0200 Subject: [PATCH 9/9] 19009: zig objcopy: minor cleanup --- lib/compiler/objcopy.zig | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index fe3d2fbc57..47f395745f 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -274,7 +274,7 @@ const usage = \\ --compress-debug-sections Compress DWARF debug sections with zlib \\ --set-section-alignment = Set alignment of section to bytes. Must be a power of two. \\ --set-section-flags = Set flags of section to represented as a comma separated set of flags. - \\ --add-section = Add file content from with the section name . + \\ --add-section = Add file content from with the a new section named . \\ ; @@ -1251,21 +1251,18 @@ fn ElfFile(comptime is_64: bool) type { fatal("unable to open '{s}': {s}", .{ add_section.file_path, @errorName(err) }); defer section_file.close(); - const max_size = std.math.maxInt(usize); - const payload = try section_file.readToEndAlloc(arena.allocator(), max_size); - const flags = 0; - const alignment = 4; + const payload = try section_file.readToEndAlloc(arena.allocator(), std.math.maxInt(usize)); dest_sections[dest_section_idx] = Elf_Shdr{ .sh_name = user_section_name, .sh_type = elf.SHT_PROGBITS, - .sh_flags = flags, + .sh_flags = 0, .sh_addr = 0, .sh_offset = eof_offset, .sh_size = @intCast(payload.len), .sh_link = elf.SHN_UNDEF, .sh_info = elf.SHN_UNDEF, - .sh_addralign = alignment, + .sh_addralign = 4, .sh_entsize = 0, }; dest_section_idx += 1;