Merge pull request #16788 from xxxbxxx/objcopy-step

build/ObjCopy: strip debug info to a separate elf file
This commit is contained in:
Andrew Kelley 2023-08-12 10:30:32 -07:00
parent 2d4e24dd6b
commit 1b88ce3b0c
2 changed files with 78 additions and 25 deletions

View file

@ -17,22 +17,40 @@ pub const base_id: Step.Id = .objcopy;
pub const RawFormat = enum { pub const RawFormat = enum {
bin, bin,
hex, hex,
elf,
};
pub const Strip = enum {
none,
debug,
debug_and_symbols,
}; };
step: Step, step: Step,
input_file: std.Build.LazyPath, input_file: std.Build.LazyPath,
basename: []const u8, basename: []const u8,
output_file: std.Build.GeneratedFile, output_file: std.Build.GeneratedFile,
output_file_debug: ?std.Build.GeneratedFile,
format: ?RawFormat, format: ?RawFormat,
only_section: ?[]const u8, only_section: ?[]const u8,
pad_to: ?u64, pad_to: ?u64,
strip: Strip,
compress_debug: bool,
pub const Options = struct { pub const Options = struct {
basename: ?[]const u8 = null, basename: ?[]const u8 = null,
format: ?RawFormat = null, format: ?RawFormat = null,
only_section: ?[]const u8 = null, only_section: ?[]const u8 = null,
pad_to: ?u64 = null, pad_to: ?u64 = null,
compress_debug: bool = false,
strip: Strip = .none,
/// Put the stripped out debug sections in a separate file.
/// 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,
}; };
pub fn create( pub fn create(
@ -51,10 +69,12 @@ pub fn create(
.input_file = input_file, .input_file = input_file,
.basename = options.basename orelse input_file.getDisplayName(), .basename = options.basename orelse input_file.getDisplayName(),
.output_file = std.Build.GeneratedFile{ .step = &self.step }, .output_file = std.Build.GeneratedFile{ .step = &self.step },
.output_file_debug = if (options.strip != .none and options.extract_to_separate_file) std.Build.GeneratedFile{ .step = &self.step } else null,
.format = options.format, .format = options.format,
.only_section = options.only_section, .only_section = options.only_section,
.pad_to = options.pad_to, .pad_to = options.pad_to,
.strip = options.strip,
.compress_debug = options.compress_debug,
}; };
input_file.addStepDependencies(&self.step); input_file.addStepDependencies(&self.step);
return self; return self;
@ -66,6 +86,9 @@ pub const getOutputSource = getOutput;
pub fn getOutput(self: *const ObjCopy) std.Build.LazyPath { pub fn getOutput(self: *const ObjCopy) std.Build.LazyPath {
return .{ .generated = &self.output_file }; return .{ .generated = &self.output_file };
} }
pub fn getOutputSeparatedDebug(self: *const ObjCopy) ?std.Build.LazyPath {
return if (self.output_file_debug) |*file| .{ .generated = file } else null;
}
fn make(step: *Step, prog_node: *std.Progress.Node) !void { fn make(step: *Step, prog_node: *std.Progress.Node) !void {
const b = step.owner; const b = step.owner;
@ -83,6 +106,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
man.hash.addOptionalBytes(self.only_section); man.hash.addOptionalBytes(self.only_section);
man.hash.addOptional(self.pad_to); man.hash.addOptional(self.pad_to);
man.hash.addOptional(self.format); man.hash.addOptional(self.format);
man.hash.add(self.compress_debug);
man.hash.add(self.strip);
man.hash.add(self.output_file_debug != null);
if (try step.cacheHit(&man)) { if (try step.cacheHit(&man)) {
// Cache hit, skip subprocess execution. // Cache hit, skip subprocess execution.
@ -90,12 +116,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
self.output_file.path = try b.cache_root.join(b.allocator, &.{ self.output_file.path = try b.cache_root.join(b.allocator, &.{
"o", &digest, self.basename, "o", &digest, self.basename,
}); });
if (self.output_file_debug) |*file| {
file.path = try b.cache_root.join(b.allocator, &.{
"o", &digest, b.fmt("{s}.debug", .{self.basename}),
});
}
return; return;
} }
const digest = man.final(); const digest = man.final();
const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
const cache_path = "o" ++ fs.path.sep_str ++ digest; const cache_path = "o" ++ fs.path.sep_str ++ digest;
const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, self.basename });
const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{self.basename}) });
b.cache_root.handle.makePath(cache_path) catch |err| { b.cache_root.handle.makePath(cache_path) catch |err| {
return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
}; };
@ -106,13 +138,25 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
if (self.only_section) |only_section| { if (self.only_section) |only_section| {
try argv.appendSlice(&.{ "-j", only_section }); try argv.appendSlice(&.{ "-j", only_section });
} }
switch (self.strip) {
.none => {},
.debug => try argv.appendSlice(&.{"--strip-debug"}),
.debug_and_symbols => try argv.appendSlice(&.{"--strip-all"}),
}
if (self.pad_to) |pad_to| { if (self.pad_to) |pad_to| {
try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) });
} }
if (self.format) |format| switch (format) { if (self.format) |format| switch (format) {
.bin => try argv.appendSlice(&.{ "-O", "binary" }), .bin => try argv.appendSlice(&.{ "-O", "binary" }),
.hex => try argv.appendSlice(&.{ "-O", "hex" }), .hex => try argv.appendSlice(&.{ "-O", "hex" }),
.elf => try argv.appendSlice(&.{ "-O", "elf" }),
}; };
if (self.compress_debug) {
try argv.appendSlice(&.{"--compress-debug-sections"});
}
if (self.output_file_debug != null) {
try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})});
}
try argv.appendSlice(&.{ full_src_path, full_dest_path }); try argv.appendSlice(&.{ full_src_path, full_dest_path });
@ -120,5 +164,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
_ = try step.evalZigProcess(argv.items, prog_node); _ = try step.evalZigProcess(argv.items, prog_node);
self.output_file.path = full_dest_path; self.output_file.path = full_dest_path;
if (self.output_file_debug) |*file| file.path = full_dest_path_debug;
try man.writeManifest(); try man.writeManifest();
} }

View file

@ -104,9 +104,6 @@ pub fn cmdObjCopy(
fatal("unable to open '{s}': {s}", .{ input, @errorName(err) }); fatal("unable to open '{s}': {s}", .{ input, @errorName(err) });
defer in_file.close(); defer in_file.close();
var out_file = try fs.cwd().createFile(output, .{});
defer out_file.close();
const elf_hdr = std.elf.Header.read(in_file) catch |err| switch (err) { const elf_hdr = std.elf.Header.read(in_file) catch |err| switch (err) {
error.InvalidElfMagic => fatal("not an ELF file: '{s}'", .{input}), error.InvalidElfMagic => fatal("not an ELF file: '{s}'", .{input}),
else => fatal("unable to read '{s}': {s}", .{ input, @errorName(err) }), else => fatal("unable to read '{s}': {s}", .{ input, @errorName(err) }),
@ -126,6 +123,17 @@ pub fn cmdObjCopy(
} }
}; };
const mode = mode: {
if (out_fmt != .elf or !only_keep_debug)
break :mode fs.File.default_mode;
if (in_file.stat()) |stat|
break :mode stat.mode
else |_|
break :mode fs.File.default_mode;
};
var out_file = try fs.cwd().createFile(output, .{ .mode = mode });
defer out_file.close();
switch (out_fmt) { switch (out_fmt) {
.hex, .raw => { .hex, .raw => {
if (strip_debug or strip_all or only_keep_debug) if (strip_debug or strip_all or only_keep_debug)
@ -345,7 +353,7 @@ const BinaryElfOutput = struct {
const shstrtab_shdr = (try section_headers.next()).?; const shstrtab_shdr = (try section_headers.next()).?;
const buffer = try allocator.alloc(u8, @as(usize, @intCast(shstrtab_shdr.sh_size))); const buffer = try allocator.alloc(u8, @intCast(shstrtab_shdr.sh_size));
errdefer allocator.free(buffer); errdefer allocator.free(buffer);
const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset); const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset);
@ -363,7 +371,7 @@ const BinaryElfOutput = struct {
newSection.binaryOffset = 0; newSection.binaryOffset = 0;
newSection.elfOffset = section.sh_offset; newSection.elfOffset = section.sh_offset;
newSection.fileSize = @as(usize, @intCast(section.sh_size)); newSection.fileSize = @intCast(section.sh_size);
newSection.segment = null; newSection.segment = null;
newSection.name = if (self.shstrtab) |shstrtab| newSection.name = if (self.shstrtab) |shstrtab|
@ -382,7 +390,7 @@ const BinaryElfOutput = struct {
newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr; newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr;
newSegment.virtualAddress = phdr.p_vaddr; newSegment.virtualAddress = phdr.p_vaddr;
newSegment.fileSize = @as(usize, @intCast(phdr.p_filesz)); newSegment.fileSize = @intCast(phdr.p_filesz);
newSegment.elfOffset = phdr.p_offset; newSegment.elfOffset = phdr.p_offset;
newSegment.binaryOffset = 0; newSegment.binaryOffset = 0;
newSegment.firstSection = null; newSegment.firstSection = null;
@ -478,8 +486,8 @@ const HexWriter = struct {
const MAX_PAYLOAD_LEN: u8 = 16; const MAX_PAYLOAD_LEN: u8 = 16;
fn addressParts(address: u16) [2]u8 { fn addressParts(address: u16) [2]u8 {
const msb = @as(u8, @truncate(address >> 8)); const msb: u8 = @truncate(address >> 8);
const lsb = @as(u8, @truncate(address)); const lsb: u8 = @truncate(address);
return [2]u8{ msb, lsb }; return [2]u8{ msb, lsb };
} }
@ -508,14 +516,14 @@ const HexWriter = struct {
fn Data(address: u32, data: []const u8) Record { fn Data(address: u32, data: []const u8) Record {
return Record{ return Record{
.address = @as(u16, @intCast(address % 0x10000)), .address = @intCast(address % 0x10000),
.payload = .{ .Data = data }, .payload = .{ .Data = data },
}; };
} }
fn Address(address: u32) Record { fn Address(address: u32) Record {
assert(address > 0xFFFF); assert(address > 0xFFFF);
const segment = @as(u16, @intCast(address / 0x10000)); const segment: u16 = @intCast(address / 0x10000);
if (address > 0xFFFFF) { if (address > 0xFFFFF) {
return Record{ return Record{
.address = 0, .address = 0,
@ -540,7 +548,7 @@ const HexWriter = struct {
fn checksum(self: Record) u8 { fn checksum(self: Record) u8 {
const payload_bytes = self.getPayloadBytes(); const payload_bytes = self.getPayloadBytes();
var sum: u8 = @as(u8, @intCast(payload_bytes.len)); var sum: u8 = @intCast(payload_bytes.len);
const parts = addressParts(self.address); const parts = addressParts(self.address);
sum +%= parts[0]; sum +%= parts[0];
sum +%= parts[1]; sum +%= parts[1];
@ -574,10 +582,10 @@ const HexWriter = struct {
var buf: [MAX_PAYLOAD_LEN]u8 = undefined; var buf: [MAX_PAYLOAD_LEN]u8 = undefined;
var bytes_read: usize = 0; var bytes_read: usize = 0;
while (bytes_read < segment.fileSize) { while (bytes_read < segment.fileSize) {
const row_address = @as(u32, @intCast(segment.physicalAddress + bytes_read)); const row_address: u32 = @intCast(segment.physicalAddress + bytes_read);
const remaining = segment.fileSize - bytes_read; const remaining = segment.fileSize - bytes_read;
const to_read = @as(usize, @intCast(@min(remaining, MAX_PAYLOAD_LEN))); const to_read: usize = @intCast(@min(remaining, MAX_PAYLOAD_LEN));
const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read); const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read);
if (did_read < to_read) return error.UnexpectedEOF; if (did_read < to_read) return error.UnexpectedEOF;
@ -593,7 +601,7 @@ const HexWriter = struct {
try Record.Address(address).write(self.out_file); try Record.Address(address).write(self.out_file);
} }
try record.write(self.out_file); try record.write(self.out_file);
self.prev_addr = @as(u32, @intCast(record.address + data.len)); self.prev_addr = @intCast(record.address + data.len);
} }
fn writeEOF(self: HexWriter) File.WriteError!void { fn writeEOF(self: HexWriter) File.WriteError!void {
@ -814,7 +822,7 @@ fn ElfFile(comptime is_64: bool) type {
const need_strings = (idx == header.shstrndx); const need_strings = (idx == header.shstrndx);
if (need_data or need_strings) { if (need_data or need_strings) {
const buffer = try allocator.alignedAlloc(u8, section_memory_align, @as(usize, @intCast(section.section.sh_size))); const buffer = try allocator.alignedAlloc(u8, section_memory_align, @intCast(section.section.sh_size));
const bytes_read = try in_file.preadAll(buffer, section.section.sh_offset); const bytes_read = try in_file.preadAll(buffer, section.section.sh_offset);
if (bytes_read != section.section.sh_size) return error.TRUNCATED_ELF; if (bytes_read != section.section.sh_size) return error.TRUNCATED_ELF;
section.payload = buffer; section.payload = buffer;
@ -935,7 +943,7 @@ fn ElfFile(comptime is_64: bool) type {
const update = &sections_update[self.raw_elf_header.e_shstrndx]; const update = &sections_update[self.raw_elf_header.e_shstrndx];
const name: []const u8 = ".gnu_debuglink"; const name: []const u8 = ".gnu_debuglink";
const new_offset = @as(u32, @intCast(strtab.payload.?.len)); const new_offset: u32 = @intCast(strtab.payload.?.len);
const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1); const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1);
@memcpy(buf[0..new_offset], strtab.payload.?); @memcpy(buf[0..new_offset], strtab.payload.?);
@memcpy(buf[new_offset..][0..name.len], name); @memcpy(buf[new_offset..][0..name.len], name);
@ -965,7 +973,7 @@ fn ElfFile(comptime is_64: bool) type {
update.payload = payload; update.payload = payload;
update.section = section.section; update.section = section.section;
update.section.?.sh_addralign = @alignOf(Elf_Chdr); update.section.?.sh_addralign = @alignOf(Elf_Chdr);
update.section.?.sh_size = @as(Elf_OffSize, @intCast(payload.len)); update.section.?.sh_size = @intCast(payload.len);
update.section.?.sh_flags |= elf.SHF_COMPRESSED; update.section.?.sh_flags |= elf.SHF_COMPRESSED;
} }
} }
@ -1032,7 +1040,7 @@ fn ElfFile(comptime is_64: bool) type {
dest.sh_info = sections_update[src.sh_info].remap_idx; dest.sh_info = sections_update[src.sh_info].remap_idx;
if (payload) |data| if (payload) |data|
dest.sh_size = @as(Elf_OffSize, @intCast(data.len)); dest.sh_size = @intCast(data.len);
const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign; const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign;
dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign); dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign);
@ -1110,7 +1118,7 @@ fn ElfFile(comptime is_64: bool) type {
.sh_flags = 0, .sh_flags = 0,
.sh_addr = 0, .sh_addr = 0,
.sh_offset = eof_offset, .sh_offset = eof_offset,
.sh_size = @as(Elf_OffSize, @intCast(payload.len)), .sh_size = @intCast(payload.len),
.sh_link = elf.SHN_UNDEF, .sh_link = elf.SHN_UNDEF,
.sh_info = elf.SHN_UNDEF, .sh_info = elf.SHN_UNDEF,
.sh_addralign = 4, .sh_addralign = 4,
@ -1232,7 +1240,7 @@ const ElfFileHelper = struct {
fused_cmd = null; fused_cmd = null;
} }
if (data.out_offset > offset) { if (data.out_offset > offset) {
consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@as(usize, @intCast(data.out_offset - offset))], .out_offset = offset } }); consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(data.out_offset - offset)], .out_offset = offset } });
} }
consolidated.appendAssumeCapacity(cmd); consolidated.appendAssumeCapacity(cmd);
offset = data.out_offset + data.data.len; offset = data.out_offset + data.data.len;
@ -1249,7 +1257,7 @@ const ElfFileHelper = struct {
} else { } else {
consolidated.appendAssumeCapacity(prev); consolidated.appendAssumeCapacity(prev);
if (range.out_offset > offset) { if (range.out_offset > offset) {
consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@as(usize, @intCast(range.out_offset - offset))], .out_offset = offset } }); consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(range.out_offset - offset)], .out_offset = offset } });
} }
fused_cmd = cmd; fused_cmd = cmd;
} }
@ -1286,7 +1294,7 @@ const ElfFileHelper = struct {
var section_reader = std.io.limitedReader(in_file.reader(), size); var section_reader = std.io.limitedReader(in_file.reader(), size);
// allocate as large as decompressed data. if the compression doesn't fit, keep the data uncompressed. // allocate as large as decompressed data. if the compression doesn't fit, keep the data uncompressed.
const compressed_data = try allocator.alignedAlloc(u8, 8, @as(usize, @intCast(size))); const compressed_data = try allocator.alignedAlloc(u8, 8, @intCast(size));
var compressed_stream = std.io.fixedBufferStream(compressed_data); var compressed_stream = std.io.fixedBufferStream(compressed_data);
try compressed_stream.writer().writeAll(prefix); try compressed_stream.writer().writeAll(prefix);
@ -1317,7 +1325,7 @@ const ElfFileHelper = struct {
}; };
} }
const compressed_len = @as(usize, @intCast(compressed_stream.getPos() catch unreachable)); const compressed_len: usize = @intCast(compressed_stream.getPos() catch unreachable);
const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data; const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data;
return data[0..compressed_len]; return data[0..compressed_len];
} }