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
```
This commit is contained in:
Xavier Bouchoux 2023-03-12 11:07:25 +01:00
parent 60d033d1f3
commit 7ab48163ee

View File

@ -21,10 +21,12 @@ pub fn cmdObjCopy(
var opt_input: ?[]const u8 = null;
var opt_output: ?[]const u8 = null;
var opt_extract: ?[]const u8 = null;
var opt_add_debuglink: ?[]const u8 = null;
var only_section: ?[]const u8 = null;
var pad_to: ?u64 = null;
var strip_all: bool = false;
var strip_only_debug: bool = false;
var strip_debug: bool = false;
var only_keep_debug: bool = false;
var listen = false;
while (i < args.len) : (i += 1) {
const arg = args[i];
@ -71,10 +73,19 @@ pub fn cmdObjCopy(
fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });
};
} else if (mem.eql(u8, arg, "-g") or mem.eql(u8, arg, "--strip-debug")) {
strip_only_debug = true;
strip_debug = true;
} else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-all")) {
strip_only_debug = true;
strip_all = true;
} else if (mem.eql(u8, arg, "--only-keep-debug")) {
only_keep_debug = true;
} else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {
opt_add_debuglink = arg["--add-gnu-debuglink=".len..];
} else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {
i += 1;
if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
opt_add_debuglink = args[i];
} else if (mem.startsWith(u8, arg, "--extract-to=")) {
opt_extract = arg["--extract-to=".len..];
} else if (mem.eql(u8, arg, "--extract-to")) {
i += 1;
if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
@ -114,10 +125,14 @@ pub fn cmdObjCopy(
switch (out_fmt) {
.hex, .raw => {
if (strip_only_debug or strip_all)
if (strip_debug or strip_all or only_keep_debug)
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 (opt_extract != null)
fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
if (opt_extract != null)
fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
try emitElf(arena, in_file, out_file, elf_hdr, .{
.ofmt = out_fmt,
@ -136,12 +151,12 @@ pub fn cmdObjCopy(
fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});
if (pad_to) |_|
fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});
if (!strip_only_debug and !strip_all)
fatal("zig objcopy: ELF to ELF copying only supports --strip", .{});
try stripElf(arena, in_file, out_file, elf_hdr, .{
.strip_only_debug = strip_only_debug,
.strip_debug = strip_debug,
.strip_all = strip_all,
.only_keep_debug = only_keep_debug,
.add_debuglink = opt_add_debuglink,
.extract_to = opt_extract,
});
return std.process.cleanExit();
@ -190,15 +205,18 @@ const usage =
\\Usage: zig objcopy [options] input output
\\
\\Options:
\\ -h, --help Print this help and exit
\\ --output-target=<value> Format of the output file
\\ -O <value> Alias for --output-target
\\ --only-section=<section> Remove all but <section>
\\ -j <value> Alias for --only-section
\\ --pad-to <addr> Pad the last section up to address <addr>
\\ --strip-debug, -g Remove all debug sections from the output.¶
\\ --strip-all, -S Remove all debug sections and symbol table from the output.
\\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section
\\ -h, --help Print this help and exit
\\ --output-target=<value> Format of the output file
\\ -O <value> Alias for --output-target
\\ --only-section=<section> Remove all but <section>
\\ -j <value> Alias for --only-section
\\ --pad-to <addr> Pad the last section up to address <addr>
\\ --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=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
\\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
\\
;
pub const EmitRawElfOptions = struct {
@ -644,8 +662,10 @@ test "containsValidAddressRange" {
pub const StripElfOptions = struct {
extract_to: ?[]const u8 = null,
add_debuglink: ?[]const u8 = null,
strip_all: bool = false,
strip_only_debug: bool = false,
strip_debug: bool = false,
only_keep_debug: bool = false,
};
fn stripElf(
@ -655,7 +675,12 @@ fn stripElf(
elf_hdr: elf.Header,
options: StripElfOptions,
) !void {
std.debug.assert(options.strip_only_debug or options.strip_all);
const filter: ElfContents.Filter = filter: {
if (options.only_keep_debug) break :filter .debug;
if (options.strip_all) break :filter .program;
if (options.strip_debug) break :filter .program_and_symbols;
break :filter .all;
};
var elf_contents = try ElfContents.parse(allocator, in_file, elf_hdr);
defer elf_contents.deinit();
@ -665,23 +690,40 @@ fn stripElf(
fatal("zig objcopy: unable to create '{s}': {s}", .{ filename, @errorName(err) });
};
defer dbg_file.close();
try elf_contents.emit(allocator, dbg_file, in_file, if (options.strip_only_debug) .debug else .debug_and_symbols, null);
const filter_complement: ElfContents.Filter = switch (filter) {
.program => .debug_and_symbols,
.debug => .program_and_symbols,
.program_and_symbols => .debug,
.debug_and_symbols => .program,
.all => fatal("zig objcopy: nothing to extract", .{}),
};
try elf_contents.emit(allocator, dbg_file, in_file, filter_complement, null);
}
const debuglink: ?ElfContents.DebugLink = blk: {
if (options.extract_to) |filename| {
const debuglink_filename = name: {
if (options.add_debuglink) |filename| break :name filename;
if (options.extract_to) |filename| break :name filename;
break :name null;
};
if (debuglink_filename) |filename| {
const dbg_file = std.fs.cwd().openFile(filename, .{}) catch |err| {
fatal("zig objcopy: could not read `{s}`: {s}\n", .{ filename, @errorName(err) });
};
defer dbg_file.close();
break :blk .{ .name = std.fs.path.basename(filename), .crc32 = try computeFileCrc(dbg_file) };
break :blk .{
.name = std.fs.path.basename(filename),
.crc32 = try computeFileCrc(dbg_file),
};
} else {
break :blk null;
}
};
try elf_contents.emit(allocator, out_file, in_file, if (options.strip_only_debug) .program_and_symbols else .program, debuglink);
try elf_contents.emit(allocator, out_file, in_file, filter, debuglink);
}
// note: this is "a minimal effort implementation"
@ -872,7 +914,7 @@ const ElfContents = struct {
}
const DebugLink = struct { name: []const u8, crc32: u32 };
const Filter = enum { program, debug, program_and_symbols, debug_and_symbols };
const Filter = enum { all, program, debug, program_and_symbols, debug_and_symbols };
fn emit(self: *const Self, gpa: Allocator, output: File, source: File, filter: Filter, debuglink: ?DebugLink) !void {
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
@ -900,6 +942,10 @@ const ElfContents = struct {
update.action = action: {
if (section.usage == .none) break :action .strip;
break :action switch (filter) {
.all => switch (section.usage) {
.none => .strip,
else => .keep,
},
.program => switch (section.usage) {
.common, .exe => .keep,
else => .strip,
@ -910,10 +956,12 @@ const ElfContents = struct {
},
.debug => switch (section.usage) {
.exe, .symbols => .empty,
.none => .strip,
else => .keep,
},
.debug_and_symbols => switch (section.usage) {
.exe => .empty,
.none => .strip,
else => .keep,
},
};
@ -1117,7 +1165,7 @@ const ElfContents = struct {
}
// write the target files
// TODO: pack together contiguous copies (cmdbuf if ordered by construction)
// TODO: pack together contiguous copies (cmdbuf is ordered, by construction)
// TODO: fill the paddings with zero or copy from source file
for (cmdbuf.items) |cmd| {
switch (cmd) {