authorgravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-03-12 11:07:25+01:00
committergravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-03-20 08:39:23+01:00
log7ab48163eee3288444f664fd709d74e4148832a3
tree120e5684e5b597e8879a19ef0962bdb3e3aca5a2
parent60d033d1f3e052cf0b469b6739ee0e12f9409c26

objcopy: add support for `--add-gnu-debuglink` and `--only-keep-debug`

as documented at https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html It is now equivalent to do ``` zig objcopy --only-keep-debug bar foo.debug zig objcopy -g bar foo.tmp zig objcopy --add-gnu-debuglink=foo.debug foo.tmp foo rm foo.tmp ``` or ``` zig objcopy --only-keep-debug bar foo foo.debug zig objcopy -g --add-gnu-debuglink=foo.debug bar foo ``` or ``` zig objcopy -g --extract-to=foo.debug bar foo ```

1 files changed, 72 insertions(+), 24 deletions(-)

src/objcopy.zig+72-24
...@@ -21,10 +21,12 @@ pub fn cmdObjCopy(...@@ -21,10 +21,12 @@ pub fn cmdObjCopy(
21 var opt_input: ?[]const u8 = null;21 var opt_input: ?[]const u8 = null;
22 var opt_output: ?[]const u8 = null;22 var opt_output: ?[]const u8 = null;
23 var opt_extract: ?[]const u8 = null;23 var opt_extract: ?[]const u8 = null;
24 var opt_add_debuglink: ?[]const u8 = null;
24 var only_section: ?[]const u8 = null;25 var only_section: ?[]const u8 = null;
25 var pad_to: ?u64 = null;26 var pad_to: ?u64 = null;
26 var strip_all: bool = false;27 var strip_all: bool = false;
27 var strip_only_debug: bool = false;28 var strip_debug: bool = false;
29 var only_keep_debug: bool = false;
28 var listen = false;30 var listen = false;
29 while (i < args.len) : (i += 1) {31 while (i < args.len) : (i += 1) {
30 const arg = args[i];32 const arg = args[i];
...@@ -71,10 +73,19 @@ pub fn cmdObjCopy(...@@ -71,10 +73,19 @@ pub fn cmdObjCopy(
71 fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });73 fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });
72 };74 };
73 } else if (mem.eql(u8, arg, "-g") or mem.eql(u8, arg, "--strip-debug")) {75 } else if (mem.eql(u8, arg, "-g") or mem.eql(u8, arg, "--strip-debug")) {
74 strip_only_debug = true;76 strip_debug = true;
75 } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-all")) {77 } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-all")) {
76 strip_only_debug = true;
77 strip_all = true;78 strip_all = true;
79 } else if (mem.eql(u8, arg, "--only-keep-debug")) {
80 only_keep_debug = true;
81 } else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {
82 opt_add_debuglink = arg["--add-gnu-debuglink=".len..];
83 } else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {
84 i += 1;
85 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
86 opt_add_debuglink = args[i];
87 } else if (mem.startsWith(u8, arg, "--extract-to=")) {
88 opt_extract = arg["--extract-to=".len..];
78 } else if (mem.eql(u8, arg, "--extract-to")) {89 } else if (mem.eql(u8, arg, "--extract-to")) {
79 i += 1;90 i += 1;
80 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});91 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
...@@ -114,10 +125,14 @@ pub fn cmdObjCopy(...@@ -114,10 +125,14 @@ pub fn cmdObjCopy(
114125
115 switch (out_fmt) {126 switch (out_fmt) {
116 .hex, .raw => {127 .hex, .raw => {
117 if (strip_only_debug or strip_all)128 if (strip_debug or strip_all or only_keep_debug)
118 fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{});129 fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{});
119 if (opt_extract != null)130 if (opt_extract != null)
120 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});131 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
132 if (opt_extract != null)
133 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
134 if (opt_extract != null)
135 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
121136
122 try emitElf(arena, in_file, out_file, elf_hdr, .{137 try emitElf(arena, in_file, out_file, elf_hdr, .{
123 .ofmt = out_fmt,138 .ofmt = out_fmt,
...@@ -136,12 +151,12 @@ pub fn cmdObjCopy(...@@ -136,12 +151,12 @@ pub fn cmdObjCopy(
136 fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});151 fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});
137 if (pad_to) |_|152 if (pad_to) |_|
138 fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});153 fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});
139 if (!strip_only_debug and !strip_all)
140 fatal("zig objcopy: ELF to ELF copying only supports --strip", .{});
141154
142 try stripElf(arena, in_file, out_file, elf_hdr, .{155 try stripElf(arena, in_file, out_file, elf_hdr, .{
143 .strip_only_debug = strip_only_debug,156 .strip_debug = strip_debug,
144 .strip_all = strip_all,157 .strip_all = strip_all,
158 .only_keep_debug = only_keep_debug,
159 .add_debuglink = opt_add_debuglink,
145 .extract_to = opt_extract,160 .extract_to = opt_extract,
146 });161 });
147 return std.process.cleanExit();162 return std.process.cleanExit();
...@@ -190,15 +205,18 @@ const usage =...@@ -190,15 +205,18 @@ const usage =
190 \\Usage: zig objcopy [options] input output205 \\Usage: zig objcopy [options] input output
191 \\206 \\
192 \\Options:207 \\Options:
193 \\ -h, --help Print this help and exit208 \\ -h, --help Print this help and exit
194 \\ --output-target=<value> Format of the output file209 \\ --output-target=<value> Format of the output file
195 \\ -O <value> Alias for --output-target210 \\ -O <value> Alias for --output-target
196 \\ --only-section=<section> Remove all but <section>211 \\ --only-section=<section> Remove all but <section>
197 \\ -j <value> Alias for --only-section212 \\ -j <value> Alias for --only-section
198 \\ --pad-to <addr> Pad the last section up to address <addr>213 \\ --pad-to <addr> Pad the last section up to address <addr>
199 \\ --strip-debug, -g Remove all debug sections from the output.¶214 \\ --strip-debug, -g Remove all debug sections from the output.
200 \\ --strip-all, -S Remove all debug sections and symbol table from the output.215 \\ --strip-all, -S Remove all debug sections and symbol table from the output.
201 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section216 \\ --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.
217 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
218 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
219 \\
202;220;
203221
204pub const EmitRawElfOptions = struct {222pub const EmitRawElfOptions = struct {
...@@ -644,8 +662,10 @@ test "containsValidAddressRange" {...@@ -644,8 +662,10 @@ test "containsValidAddressRange" {
644662
645pub const StripElfOptions = struct {663pub const StripElfOptions = struct {
646 extract_to: ?[]const u8 = null,664 extract_to: ?[]const u8 = null,
665 add_debuglink: ?[]const u8 = null,
647 strip_all: bool = false,666 strip_all: bool = false,
648 strip_only_debug: bool = false,667 strip_debug: bool = false,
668 only_keep_debug: bool = false,
649};669};
650670
651fn stripElf(671fn stripElf(
...@@ -655,7 +675,12 @@ fn stripElf(...@@ -655,7 +675,12 @@ fn stripElf(
655 elf_hdr: elf.Header,675 elf_hdr: elf.Header,
656 options: StripElfOptions,676 options: StripElfOptions,
657) !void {677) !void {
658 std.debug.assert(options.strip_only_debug or options.strip_all);678 const filter: ElfContents.Filter = filter: {
679 if (options.only_keep_debug) break :filter .debug;
680 if (options.strip_all) break :filter .program;
681 if (options.strip_debug) break :filter .program_and_symbols;
682 break :filter .all;
683 };
659684
660 var elf_contents = try ElfContents.parse(allocator, in_file, elf_hdr);685 var elf_contents = try ElfContents.parse(allocator, in_file, elf_hdr);
661 defer elf_contents.deinit();686 defer elf_contents.deinit();
...@@ -665,23 +690,40 @@ fn stripElf(...@@ -665,23 +690,40 @@ fn stripElf(
665 fatal("zig objcopy: unable to create '{s}': {s}", .{ filename, @errorName(err) });690 fatal("zig objcopy: unable to create '{s}': {s}", .{ filename, @errorName(err) });
666 };691 };
667 defer dbg_file.close();692 defer dbg_file.close();
668 try elf_contents.emit(allocator, dbg_file, in_file, if (options.strip_only_debug) .debug else .debug_and_symbols, null);693
694 const filter_complement: ElfContents.Filter = switch (filter) {
695 .program => .debug_and_symbols,
696 .debug => .program_and_symbols,
697 .program_and_symbols => .debug,
698 .debug_and_symbols => .program,
699 .all => fatal("zig objcopy: nothing to extract", .{}),
700 };
701
702 try elf_contents.emit(allocator, dbg_file, in_file, filter_complement, null);
669 }703 }
670704
671 const debuglink: ?ElfContents.DebugLink = blk: {705 const debuglink: ?ElfContents.DebugLink = blk: {
672 if (options.extract_to) |filename| {706 const debuglink_filename = name: {
707 if (options.add_debuglink) |filename| break :name filename;
708 if (options.extract_to) |filename| break :name filename;
709 break :name null;
710 };
711 if (debuglink_filename) |filename| {
673 const dbg_file = std.fs.cwd().openFile(filename, .{}) catch |err| {712 const dbg_file = std.fs.cwd().openFile(filename, .{}) catch |err| {
674 fatal("zig objcopy: could not read `{s}`: {s}\n", .{ filename, @errorName(err) });713 fatal("zig objcopy: could not read `{s}`: {s}\n", .{ filename, @errorName(err) });
675 };714 };
676 defer dbg_file.close();715 defer dbg_file.close();
677716
678 break :blk .{ .name = std.fs.path.basename(filename), .crc32 = try computeFileCrc(dbg_file) };717 break :blk .{
718 .name = std.fs.path.basename(filename),
719 .crc32 = try computeFileCrc(dbg_file),
720 };
679 } else {721 } else {
680 break :blk null;722 break :blk null;
681 }723 }
682 };724 };
683725
684 try elf_contents.emit(allocator, out_file, in_file, if (options.strip_only_debug) .program_and_symbols else .program, debuglink);726 try elf_contents.emit(allocator, out_file, in_file, filter, debuglink);
685}727}
686728
687// note: this is "a minimal effort implementation"729// note: this is "a minimal effort implementation"
...@@ -872,7 +914,7 @@ const ElfContents = struct {...@@ -872,7 +914,7 @@ const ElfContents = struct {
872 }914 }
873915
874 const DebugLink = struct { name: []const u8, crc32: u32 };916 const DebugLink = struct { name: []const u8, crc32: u32 };
875 const Filter = enum { program, debug, program_and_symbols, debug_and_symbols };917 const Filter = enum { all, program, debug, program_and_symbols, debug_and_symbols };
876 fn emit(self: *const Self, gpa: Allocator, output: File, source: File, filter: Filter, debuglink: ?DebugLink) !void {918 fn emit(self: *const Self, gpa: Allocator, output: File, source: File, filter: Filter, debuglink: ?DebugLink) !void {
877 var arena = std.heap.ArenaAllocator.init(gpa);919 var arena = std.heap.ArenaAllocator.init(gpa);
878 defer arena.deinit();920 defer arena.deinit();
...@@ -900,6 +942,10 @@ const ElfContents = struct {...@@ -900,6 +942,10 @@ const ElfContents = struct {
900 update.action = action: {942 update.action = action: {
901 if (section.usage == .none) break :action .strip;943 if (section.usage == .none) break :action .strip;
902 break :action switch (filter) {944 break :action switch (filter) {
945 .all => switch (section.usage) {
946 .none => .strip,
947 else => .keep,
948 },
903 .program => switch (section.usage) {949 .program => switch (section.usage) {
904 .common, .exe => .keep,950 .common, .exe => .keep,
905 else => .strip,951 else => .strip,
...@@ -910,10 +956,12 @@ const ElfContents = struct {...@@ -910,10 +956,12 @@ const ElfContents = struct {
910 },956 },
911 .debug => switch (section.usage) {957 .debug => switch (section.usage) {
912 .exe, .symbols => .empty,958 .exe, .symbols => .empty,
959 .none => .strip,
913 else => .keep,960 else => .keep,
914 },961 },
915 .debug_and_symbols => switch (section.usage) {962 .debug_and_symbols => switch (section.usage) {
916 .exe => .empty,963 .exe => .empty,
964 .none => .strip,
917 else => .keep,965 else => .keep,
918 },966 },
919 };967 };
...@@ -1117,7 +1165,7 @@ const ElfContents = struct {...@@ -1117,7 +1165,7 @@ const ElfContents = struct {
1117 }1165 }
11181166
1119 // write the target files1167 // write the target files
1120 // TODO: pack together contiguous copies (cmdbuf if ordered by construction)1168 // TODO: pack together contiguous copies (cmdbuf is ordered, by construction)
1121 // TODO: fill the paddings with zero or copy from source file1169 // TODO: fill the paddings with zero or copy from source file
1122 for (cmdbuf.items) |cmd| {1170 for (cmdbuf.items) |cmd| {
1123 switch (cmd) {1171 switch (cmd) {