diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 751f82a9ea..9f30a36148 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -706,13 +706,13 @@ pub const Elf64_Shdr = extern struct { sh_entsize: Elf64_Xword, }; pub const Elf32_Chdr = extern struct { - ch_type: Elf32_Word, + ch_type: COMPRESS, ch_size: Elf32_Word, ch_addralign: Elf32_Word, }; pub const Elf64_Chdr = extern struct { - ch_type: Elf64_Word, - ch_reserved: Elf64_Word, + ch_type: COMPRESS, + ch_reserved: Elf64_Word = 0, ch_size: Elf64_Xword, ch_addralign: Elf64_Xword, }; @@ -1730,6 +1730,17 @@ pub const SHN_COMMON = 0xfff2; /// End of reserved indices pub const SHN_HIRESERVE = 0xffff; +// Legal values for ch_type (compression algorithm). +pub const COMPRESS = enum(u32) { + ZLIB = 1, + ZSTD = 2, + LOOS = 0x60000000, + HIOS = 0x6fffffff, + LOPROC = 0x70000000, + HIPROC = 0x7fffffff, + _, +}; + /// AMD x86-64 relocations. /// No reloc pub const R_X86_64_NONE = 0; diff --git a/src/objcopy.zig b/src/objcopy.zig index 4555ee3f18..eb87c3ca23 100644 --- a/src/objcopy.zig +++ b/src/objcopy.zig @@ -27,6 +27,7 @@ pub fn cmdObjCopy( var strip_all: bool = false; var strip_debug: bool = false; var only_keep_debug: bool = false; + var compress_debug_sections: bool = false; var listen = false; while (i < args.len) : (i += 1) { const arg = args[i]; @@ -78,6 +79,8 @@ pub fn cmdObjCopy( strip_all = true; } else if (mem.eql(u8, arg, "--only-keep-debug")) { only_keep_debug = true; + } else if (mem.eql(u8, arg, "--compress-debug-sections")) { + compress_debug_sections = 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")) { @@ -152,6 +155,7 @@ pub fn cmdObjCopy( .only_keep_debug = only_keep_debug, .add_debuglink = opt_add_debuglink, .extract_to = opt_extract, + .compress_debug = compress_debug_sections, }); return std.process.cleanExit(); }, @@ -210,6 +214,7 @@ const usage = \\ --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= Creates a .gnu_debuglink section which contains a reference to and adds it to the output file. \\ --extract-to Extract the removed sections into , and add a .gnu-debuglink section. + \\ --compress-debug-sections Compress DWARF debug sections with zlib \\ ; @@ -660,6 +665,7 @@ const StripElfOptions = struct { strip_all: bool = false, strip_debug: bool = false, only_keep_debug: bool = false, + compress_debug: bool = false, }; fn stripElf( @@ -711,11 +717,11 @@ fn stripElf( }; defer dbg_file.close(); - try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt }); + try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt, .compress_debug = options.compress_debug }); } const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null; - try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink }); + try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug }); }, } } @@ -730,6 +736,7 @@ fn ElfFile(comptime is_64: bool) type { const Elf_Ehdr = if (is_64) elf.Elf64_Ehdr else elf.Elf32_Ehdr; const Elf_Phdr = if (is_64) elf.Elf64_Phdr else elf.Elf32_Phdr; const Elf_Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr; + const Elf_Chdr = if (is_64) elf.Elf64_Chdr else elf.Elf32_Chdr; const Elf_Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym; const Elf_Verdef = if (is_64) elf.Elf64_Verdef else elf.Elf32_Verdef; const Elf_OffSize = if (is_64) elf.Elf64_Off else elf.Elf32_Off; @@ -876,6 +883,7 @@ fn ElfFile(comptime is_64: bool) type { const EmitElfOptions = struct { section_filter: Filter = .all, debuglink: ?DebugLink = null, + compress_debug: bool = false, }; fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void { var arena = std.heap.ArenaAllocator.init(gpa); @@ -939,6 +947,30 @@ fn ElfFile(comptime is_64: bool) type { break :blk new_offset; }; + // maybe compress .debug sections + if (options.compress_debug) { + for (self.sections[1..], sections_update[1..]) |section, *update| { + if (update.action != .keep) continue; + if (!std.mem.startsWith(u8, section.name, ".debug_")) continue; + if ((section.section.sh_flags & elf.SHF_COMPRESSED) != 0) continue; // already compressed + + const chdr = Elf_Chdr{ + .ch_type = elf.COMPRESS.ZLIB, + .ch_size = section.section.sh_size, + .ch_addralign = section.section.sh_addralign, + }; + + const compressed_payload = try ElfFileHelper.tryCompressSection(allocator, in_file, section.section.sh_offset, section.section.sh_size, std.mem.asBytes(&chdr)); + if (compressed_payload) |payload| { + update.payload = payload; + update.section = section.section; + update.section.?.sh_addralign = @alignOf(Elf_Chdr); + update.section.?.sh_size = @intCast(Elf_OffSize, payload.len); + update.section.?.sh_flags |= elf.SHF_COMPRESSED; + } + } + } + var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator); defer cmdbuf.deinit(); try cmdbuf.ensureUnusedCapacity(3 + new_shnum); @@ -1247,6 +1279,49 @@ const ElfFileHelper = struct { } } + fn tryCompressSection(allocator: Allocator, in_file: File, offset: u64, size: u64, prefix: []const u8) !?[]align(8) const u8 { + if (size < prefix.len) return null; + + try in_file.seekTo(offset); + 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. + const compressed_data = try allocator.alignedAlloc(u8, 8, @intCast(usize, size)); + var compressed_stream = std.io.fixedBufferStream(compressed_data); + + try compressed_stream.writer().writeAll(prefix); + + { + var compressor = try std.compress.zlib.compressStream(allocator, compressed_stream.writer(), .{}); + defer compressor.deinit(); + + var buf: [8000]u8 = undefined; + while (true) { + const bytes_read = try section_reader.read(&buf); + if (bytes_read == 0) break; + const bytes_written = compressor.write(buf[0..bytes_read]) catch |err| switch (err) { + error.NoSpaceLeft => { + allocator.free(compressed_data); + return null; + }, + else => return err, + }; + std.debug.assert(bytes_written == bytes_read); + } + compressor.finish() catch |err| switch (err) { + error.NoSpaceLeft => { + allocator.free(compressed_data); + return null; + }, + else => return err, + }; + } + + const compressed_len = @intCast(usize, compressed_stream.getPos() catch unreachable); + const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data; + return data[0..compressed_len]; + } + fn createDebugLink(path: []const u8) DebugLink { const file = std.fs.cwd().openFile(path, .{}) catch |err| { fatal("zig objcopy: could not open `{s}`: {s}\n", .{ path, @errorName(err) });