authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-19 13:15:40-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-19 13:15:40-07:00
log7d6fcf083122bfe049f5314b3ec8ddcbfb27569d
treefe6ea4c053b07f993a3d9f8a2d65db7690cef582
parent8fcc28d30275bf76313765616a2d2bdcaeb4faf2
parentc39191a0869cb6ca614c51fd6e3930908dcfd8df
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15012 from xxxbxxx/objcpy-elf-compress

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

2 files changed, 98 insertions(+), 33 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+84-30
...@@ -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;
...@@ -817,7 +824,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -817,7 +824,7 @@ fn ElfFile(comptime is_64: bool) type {
817 // fill-in sections info:824 // fill-in sections info:
818 // resolve the name825 // resolve the name
819 // find if a program segment uses the section826 // find if a program segment uses the section
820 // categorise sections usage (used by program segments, debug datadase, common metadata, symbol table)827 // categorize sections usage (used by program segments, debug datadase, common metadata, symbol table)
821 for (sections) |*section| {828 for (sections) |*section| {
822 section.segment = for (program_segments) |*seg| {829 section.segment = for (program_segments) |*seg| {
823 if (sectionWithinSegment(section.section, seg.*)) break seg;830 if (sectionWithinSegment(section.section, seg.*)) break seg;
...@@ -836,8 +843,8 @@ fn ElfFile(comptime is_64: bool) type {...@@ -836,8 +843,8 @@ fn ElfFile(comptime is_64: bool) type {
836 if (std.mem.eql(u8, section.name, ".gnu_debuglink")) break :cat .none;843 if (std.mem.eql(u8, section.name, ".gnu_debuglink")) break :cat .none;
837 break :cat category_from_program;844 break :cat category_from_program;
838 },845 },
839 elf.SHT_LOPROC...elf.SHT_HIPROC => .common, // don't strip unkonwn sections846 elf.SHT_LOPROC...elf.SHT_HIPROC => .common, // don't strip unknown sections
840 elf.SHT_LOUSER...elf.SHT_HIUSER => .common, // don't strip unkonwn sections847 elf.SHT_LOUSER...elf.SHT_HIUSER => .common, // don't strip unknown sections
841 else => category_from_program,848 else => category_from_program,
842 };849 };
843 }850 }
...@@ -846,7 +853,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -846,7 +853,7 @@ fn ElfFile(comptime is_64: bool) type {
846 if (header.shstrndx != elf.SHN_UNDEF)853 if (header.shstrndx != elf.SHN_UNDEF)
847 sections[header.shstrndx].category = .common; // string table for the headers854 sections[header.shstrndx].category = .common; // string table for the headers
848855
849 // recursive dependencies856 // recursively propagate section categories to their linked sections, so that they are kept together
850 var dirty: u1 = 1;857 var dirty: u1 = 1;
851 while (dirty != 0) {858 while (dirty != 0) {
852 dirty = 0;859 dirty = 0;
...@@ -856,29 +863,6 @@ fn ElfFile(comptime is_64: bool) type {...@@ -856,29 +863,6 @@ fn ElfFile(comptime is_64: bool) type {
856 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_link].category, section.category);863 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_link].category, section.category);
857 if ((section.section.sh_flags & elf.SHF_INFO_LINK) != 0 and section.section.sh_info != elf.SHN_UNDEF)864 if ((section.section.sh_flags & elf.SHF_INFO_LINK) != 0 and section.section.sh_info != elf.SHN_UNDEF)
858 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_info].category, section.category);865 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_info].category, section.category);
859
860 if (section.payload) |data| {
861 switch (section.section.sh_type) {
862 elf.DT_VERSYM => {
863 assert(section.section.sh_entsize == @sizeOf(Elf_Verdef));
864 const defs = @ptrCast([*]const Elf_Verdef, data)[0 .. @intCast(usize, section.section.sh_size) / @sizeOf(Elf_Verdef)];
865 for (defs) |def| {
866 if (def.vd_ndx != elf.SHN_UNDEF)
867 dirty |= ElfFileHelper.propagateCategory(&sections[def.vd_ndx].category, section.category);
868 }
869 },
870 elf.SHT_SYMTAB, elf.SHT_DYNSYM => {
871 assert(section.section.sh_entsize == @sizeOf(Elf_Sym));
872 const syms = @ptrCast([*]const Elf_Sym, data)[0 .. @intCast(usize, section.section.sh_size) / @sizeOf(Elf_Sym)];
873
874 for (syms) |sym| {
875 if (sym.st_shndx != elf.SHN_UNDEF and sym.st_shndx < elf.SHN_LORESERVE)
876 dirty |= ElfFileHelper.propagateCategory(&sections[sym.st_shndx].category, section.category);
877 }
878 },
879 else => {},
880 }
881 }
882 }866 }
883 }867 }
884868
...@@ -899,6 +883,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -899,6 +883,7 @@ fn ElfFile(comptime is_64: bool) type {
899 const EmitElfOptions = struct {883 const EmitElfOptions = struct {
900 section_filter: Filter = .all,884 section_filter: Filter = .all,
901 debuglink: ?DebugLink = null,885 debuglink: ?DebugLink = null,
886 compress_debug: bool = false,
902 };887 };
903 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 {
904 var arena = std.heap.ArenaAllocator.init(gpa);889 var arena = std.heap.ArenaAllocator.init(gpa);
...@@ -962,6 +947,30 @@ fn ElfFile(comptime is_64: bool) type {...@@ -962,6 +947,30 @@ fn ElfFile(comptime is_64: bool) type {
962 break :blk new_offset;947 break :blk new_offset;
963 };948 };
964949
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
965 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);974 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);
966 defer cmdbuf.deinit();975 defer cmdbuf.deinit();
967 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);976 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);
...@@ -994,6 +1003,8 @@ fn ElfFile(comptime is_64: bool) type {...@@ -994,6 +1003,8 @@ fn ElfFile(comptime is_64: bool) type {
994 // this code only supports when they are in increasing file order.1003 // this code only supports when they are in increasing file order.
995 var offset: u64 = eof_offset;1004 var offset: u64 = eof_offset;
996 for (self.sections[1..]) |section| {1005 for (self.sections[1..]) |section| {
1006 if (section.section.sh_type == elf.SHT_NOBITS)
1007 continue;
997 if (section.section.sh_offset < offset) {1008 if (section.section.sh_offset < offset) {
998 fatal("zig objcopy: unsuported ELF file", .{});1009 fatal("zig objcopy: unsuported ELF file", .{});
999 }1010 }
...@@ -1025,7 +1036,7 @@ fn ElfFile(comptime is_64: bool) type {...@@ -1025,7 +1036,7 @@ fn ElfFile(comptime is_64: bool) type {
10251036
1026 const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign;1037 const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign;
1027 dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign);1038 dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign);
1028 if (src.sh_offset != dest.sh_offset and section.segment != null and update.action != .empty and dest.sh_type != elf.SHT_NOTE) {1039 if (src.sh_offset != dest.sh_offset and section.segment != null and update.action != .empty and dest.sh_type != elf.SHT_NOTE and dest.sh_type != elf.SHT_NOBITS) {
1029 if (src.sh_offset > dest.sh_offset) {1040 if (src.sh_offset > dest.sh_offset) {
1030 dest.sh_offset = src.sh_offset; // add padding to avoid modifing the program segments1041 dest.sh_offset = src.sh_offset; // add padding to avoid modifing the program segments
1031 } else {1042 } else {
...@@ -1268,6 +1279,49 @@ const ElfFileHelper = struct {...@@ -1268,6 +1279,49 @@ const ElfFileHelper = struct {
1268 }1279 }
1269 }1280 }
12701281
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
1271 fn createDebugLink(path: []const u8) DebugLink {1325 fn createDebugLink(path: []const u8) DebugLink {
1272 const file = std.fs.cwd().openFile(path, .{}) catch |err| {1326 const file = std.fs.cwd().openFile(path, .{}) catch |err| {
1273 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) });