authorgravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-03-19 21:40:57+01:00
committergravatar for xavierb@gmail.comXavier Bouchoux <xavierb@gmail.com> 2023-06-19 08:51:03+02:00
logc39191a0869cb6ca614c51fd6e3930908dcfd8df
treee2d8dff8f8aae9404126e15c198433a7ad625d36
parent5eacddce99c36c6f6ab62e7e7e5cc427c3e2f807

objcopy: add support for `--compress-debug-sections`


2 files changed, 91 insertions(+), 5 deletions(-)

lib/std/elf.zig+14-3
...@@ -706,13 +706,13 @@ pub const Elf64_Shdr = extern struct {...@@ -706,13 +706,13 @@ pub const Elf64_Shdr = extern struct {
706 sh_entsize: Elf64_Xword,706 sh_entsize: Elf64_Xword,
707};707};
708pub const Elf32_Chdr = extern struct {708pub const Elf32_Chdr = extern struct {
709 ch_type: Elf32_Word,709 ch_type: COMPRESS,
710 ch_size: Elf32_Word,710 ch_size: Elf32_Word,
711 ch_addralign: Elf32_Word,711 ch_addralign: Elf32_Word,
712};712};
713pub const Elf64_Chdr = extern struct {713pub const Elf64_Chdr = extern struct {
714 ch_type: Elf64_Word,714 ch_type: COMPRESS,
715 ch_reserved: Elf64_Word,715 ch_reserved: Elf64_Word = 0,
716 ch_size: Elf64_Xword,716 ch_size: Elf64_Xword,
717 ch_addralign: Elf64_Xword,717 ch_addralign: Elf64_Xword,
718};718};
...@@ -1730,6 +1730,17 @@ pub const SHN_COMMON = 0xfff2;...@@ -1730,6 +1730,17 @@ pub const SHN_COMMON = 0xfff2;
1730/// End of reserved indices1730/// End of reserved indices
1731pub const SHN_HIRESERVE = 0xffff;1731pub const SHN_HIRESERVE = 0xffff;
17321732
1733// Legal values for ch_type (compression algorithm).
1734pub const COMPRESS = enum(u32) {
1735 ZLIB = 1,
1736 ZSTD = 2,
1737 LOOS = 0x60000000,
1738 HIOS = 0x6fffffff,
1739 LOPROC = 0x70000000,
1740 HIPROC = 0x7fffffff,
1741 _,
1742};
1743
1733/// AMD x86-64 relocations.1744/// AMD x86-64 relocations.
1734/// No reloc1745/// No reloc
1735pub const R_X86_64_NONE = 0;1746pub const R_X86_64_NONE = 0;
src/objcopy.zig+77-2
...@@ -27,6 +27,7 @@ pub fn cmdObjCopy(...@@ -27,6 +27,7 @@ pub fn cmdObjCopy(
27 var strip_all: bool = false;27 var strip_all: bool = false;
28 var strip_debug: bool = false;28 var strip_debug: bool = false;
29 var only_keep_debug: bool = false;29 var only_keep_debug: bool = false;
30 var compress_debug_sections: bool = false;
30 var listen = false;31 var listen = false;
31 while (i < args.len) : (i += 1) {32 while (i < args.len) : (i += 1) {
32 const arg = args[i];33 const arg = args[i];
...@@ -78,6 +79,8 @@ pub fn cmdObjCopy(...@@ -78,6 +79,8 @@ pub fn cmdObjCopy(
78 strip_all = true;79 strip_all = true;
79 } else if (mem.eql(u8, arg, "--only-keep-debug")) {80 } else if (mem.eql(u8, arg, "--only-keep-debug")) {
80 only_keep_debug = true;81 only_keep_debug = true;
82 } else if (mem.eql(u8, arg, "--compress-debug-sections")) {
83 compress_debug_sections = true;
81 } else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {84 } else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {
82 opt_add_debuglink = arg["--add-gnu-debuglink=".len..];85 opt_add_debuglink = arg["--add-gnu-debuglink=".len..];
83 } else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {86 } else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {
...@@ -152,6 +155,7 @@ pub fn cmdObjCopy(...@@ -152,6 +155,7 @@ pub fn cmdObjCopy(
152 .only_keep_debug = only_keep_debug,155 .only_keep_debug = only_keep_debug,
153 .add_debuglink = opt_add_debuglink,156 .add_debuglink = opt_add_debuglink,
154 .extract_to = opt_extract,157 .extract_to = opt_extract,
158 .compress_debug = compress_debug_sections,
155 });159 });
156 return std.process.cleanExit();160 return std.process.cleanExit();
157 },161 },
...@@ -210,6 +214,7 @@ const usage =...@@ -210,6 +214,7 @@ const usage =
210 \\ --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.214 \\ --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.
211 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.215 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
212 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.216 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
217 \\ --compress-debug-sections Compress DWARF debug sections with zlib
213 \\218 \\
214;219;
215220
...@@ -660,6 +665,7 @@ const StripElfOptions = struct {...@@ -660,6 +665,7 @@ const StripElfOptions = struct {
660 strip_all: bool = false,665 strip_all: bool = false,
661 strip_debug: bool = false,666 strip_debug: bool = false,
662 only_keep_debug: bool = false,667 only_keep_debug: bool = false,
668 compress_debug: bool = false,
663};669};
664670
665fn stripElf(671fn stripElf(
...@@ -711,11 +717,11 @@ fn stripElf(...@@ -711,11 +717,11 @@ fn stripElf(
711 };717 };
712 defer dbg_file.close();718 defer dbg_file.close();
713719
714 try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt });720 try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt, .compress_debug = options.compress_debug });
715 }721 }
716722
717 const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null;723 const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null;
718 try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink });724 try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug });
719 },725 },
720 }726 }
721}727}
...@@ -730,6 +736,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -730,6 +736,7 @@ fn ElfFile(comptime is_64: bool) type {
730 const Elf_Ehdr = if (is_64) elf.Elf64_Ehdr else elf.Elf32_Ehdr;736 const Elf_Ehdr = if (is_64) elf.Elf64_Ehdr else elf.Elf32_Ehdr;
731 const Elf_Phdr = if (is_64) elf.Elf64_Phdr else elf.Elf32_Phdr;737 const Elf_Phdr = if (is_64) elf.Elf64_Phdr else elf.Elf32_Phdr;
732 const Elf_Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr;738 const Elf_Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr;
739 const Elf_Chdr = if (is_64) elf.Elf64_Chdr else elf.Elf32_Chdr;
733 const Elf_Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;740 const Elf_Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;
734 const Elf_Verdef = if (is_64) elf.Elf64_Verdef else elf.Elf32_Verdef;741 const Elf_Verdef = if (is_64) elf.Elf64_Verdef else elf.Elf32_Verdef;
735 const Elf_OffSize = if (is_64) elf.Elf64_Off else elf.Elf32_Off;742 const Elf_OffSize = if (is_64) elf.Elf64_Off else elf.Elf32_Off;
...@@ -876,6 +883,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -876,6 +883,7 @@ fn ElfFile(comptime is_64: bool) type {
876 const EmitElfOptions = struct {883 const EmitElfOptions = struct {
877 section_filter: Filter = .all,884 section_filter: Filter = .all,
878 debuglink: ?DebugLink = null,885 debuglink: ?DebugLink = null,
886 compress_debug: bool = false,
879 };887 };
880 fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void {888 fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void {
881 var arena = std.heap.ArenaAllocator.init(gpa);889 var arena = std.heap.ArenaAllocator.init(gpa);
...@@ -939,6 +947,30 @@ fn ElfFile(comptime is_64: bool) type {...@@ -939,6 +947,30 @@ fn ElfFile(comptime is_64: bool) type {
939 break :blk new_offset;947 break :blk new_offset;
940 };948 };
941949
950 // maybe compress .debug sections
951 if (options.compress_debug) {
952 for (self.sections[1..], sections_update[1..]) |section, *update| {
953 if (update.action != .keep) continue;
954 if (!std.mem.startsWith(u8, section.name, ".debug_")) continue;
955 if ((section.section.sh_flags & elf.SHF_COMPRESSED) != 0) continue; // already compressed
956
957 const chdr = Elf_Chdr{
958 .ch_type = elf.COMPRESS.ZLIB,
959 .ch_size = section.section.sh_size,
960 .ch_addralign = section.section.sh_addralign,
961 };
962
963 const compressed_payload = try ElfFileHelper.tryCompressSection(allocator, in_file, section.section.sh_offset, section.section.sh_size, std.mem.asBytes(&chdr));
964 if (compressed_payload) |payload| {
965 update.payload = payload;
966 update.section = section.section;
967 update.section.?.sh_addralign = @alignOf(Elf_Chdr);
968 update.section.?.sh_size = @intCast(Elf_OffSize, payload.len);
969 update.section.?.sh_flags |= elf.SHF_COMPRESSED;
970 }
971 }
972 }
973
942 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);974 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);
943 defer cmdbuf.deinit();975 defer cmdbuf.deinit();
944 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);976 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);
...@@ -1247,6 +1279,49 @@ const ElfFileHelper = struct {...@@ -1247,6 +1279,49 @@ const ElfFileHelper = struct {
1247 }1279 }
1248 }1280 }
12491281
1282 fn tryCompressSection(allocator: Allocator, in_file: File, offset: u64, size: u64, prefix: []const u8) !?[]align(8) const u8 {
1283 if (size < prefix.len) return null;
1284
1285 try in_file.seekTo(offset);
1286 var section_reader = std.io.limitedReader(in_file.reader(), size);
1287
1288 // allocate as large as decompressed data. if the compression doesn't fit, keep the data uncompressed.
1289 const compressed_data = try allocator.alignedAlloc(u8, 8, @intCast(usize, size));
1290 var compressed_stream = std.io.fixedBufferStream(compressed_data);
1291
1292 try compressed_stream.writer().writeAll(prefix);
1293
1294 {
1295 var compressor = try std.compress.zlib.compressStream(allocator, compressed_stream.writer(), .{});
1296 defer compressor.deinit();
1297
1298 var buf: [8000]u8 = undefined;
1299 while (true) {
1300 const bytes_read = try section_reader.read(&buf);
1301 if (bytes_read == 0) break;
1302 const bytes_written = compressor.write(buf[0..bytes_read]) catch |err| switch (err) {
1303 error.NoSpaceLeft => {
1304 allocator.free(compressed_data);
1305 return null;
1306 },
1307 else => return err,
1308 };
1309 std.debug.assert(bytes_written == bytes_read);
1310 }
1311 compressor.finish() catch |err| switch (err) {
1312 error.NoSpaceLeft => {
1313 allocator.free(compressed_data);
1314 return null;
1315 },
1316 else => return err,
1317 };
1318 }
1319
1320 const compressed_len = @intCast(usize, compressed_stream.getPos() catch unreachable);
1321 const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data;
1322 return data[0..compressed_len];
1323 }
1324
1250 fn createDebugLink(path: []const u8) DebugLink {1325 fn createDebugLink(path: []const u8) DebugLink {
1251 const file = std.fs.cwd().openFile(path, .{}) catch |err| {1326 const file = std.fs.cwd().openFile(path, .{}) catch |err| {
1252 fatal("zig objcopy: could not open `{s}`: {s}\n", .{ path, @errorName(err) });1327 fatal("zig objcopy: could not open `{s}`: {s}\n", .{ path, @errorName(err) });