Merge pull request #21591 from patrickwick/issue-19009

zig objcopy: support --add-section
This commit is contained in:
Andrew Kelley 2024-10-04 19:45:39 -07:00 committed by GitHub
commit cfd3bcffec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 405 additions and 16 deletions

View File

@ -40,6 +40,9 @@ fn cmdObjCopy(
var only_keep_debug: bool = false;
var compress_debug_sections: bool = false;
var listen = false;
var add_section: ?AddSection = null;
var set_section_alignment: ?SetSectionAlignment = null;
var set_section_flags: ?SetSectionFlags = null;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (!mem.startsWith(u8, arg, "-")) {
@ -104,6 +107,37 @@ fn cmdObjCopy(
i += 1;
if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
opt_extract = args[i];
} else if (mem.eql(u8, arg, "--set-section-alignment")) {
i += 1;
if (i >= args.len) fatal("expected section name and alignment arguments after '{s}'", .{arg});
if (splitOption(args[i])) |split| {
const alignment = std.fmt.parseInt(u32, split.second, 10) catch |err| {
fatal("unable to parse alignment number: '{s}': {s}", .{ split.second, @errorName(err) });
};
if (!std.math.isPowerOfTwo(alignment)) fatal("alignment must be a power of two", .{});
set_section_alignment = .{ .section_name = split.first, .alignment = alignment };
} else {
fatal("unrecognized argument: '{s}', expecting <name>=<alignment>", .{args[i]});
}
} else if (mem.eql(u8, arg, "--set-section-flags")) {
i += 1;
if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg});
if (splitOption(args[i])) |split| {
set_section_flags = .{ .section_name = split.first, .flags = parseSectionFlags(split.second) };
} else {
fatal("unrecognized argument: '{s}', expecting <name>=<flags>", .{args[i]});
}
} else if (mem.eql(u8, arg, "--add-section")) {
i += 1;
if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg});
if (splitOption(args[i])) |split| {
add_section = .{ .section_name = split.first, .file_path = split.second };
} else {
fatal("unrecognized argument: '{s}', expecting <name>=<file>", .{args[i]});
}
} else {
fatal("unrecognized argument: '{s}'", .{arg});
}
@ -151,6 +185,12 @@ fn cmdObjCopy(
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 (add_section != null)
fatal("zig objcopy: ELF to RAW or HEX copying does not support --add-section", .{});
if (set_section_alignment != null)
fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_alignment", .{});
if (set_section_flags != null)
fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_flags", .{});
try emitElf(arena, in_file, out_file, elf_hdr, .{
.ofmt = out_fmt,
@ -175,6 +215,9 @@ fn cmdObjCopy(
.add_debuglink = opt_add_debuglink,
.extract_to = opt_extract,
.compress_debug = compress_debug_sections,
.add_section = add_section,
.set_section_alignment = set_section_alignment,
.set_section_flags = set_section_flags,
});
return std.process.cleanExit();
},
@ -217,18 +260,21 @@ 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.
\\ --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.
\\ --compress-debug-sections Compress DWARF debug sections with zlib
\\ -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.
\\ --compress-debug-sections Compress DWARF debug sections with zlib
\\ --set-section-alignment <name>=<align> Set alignment of section <name> to <align> bytes. Must be a power of two.
\\ --set-section-flags <name>=<file> Set flags of section <name> to <flags> represented as a comma separated set of flags.
\\ --add-section <name>=<file> Add file content from <file> with the a new section named <name>.
\\
;
@ -236,6 +282,24 @@ pub const EmitRawElfOptions = struct {
ofmt: std.Target.ObjectFormat,
only_section: ?[]const u8 = null,
pad_to: ?u64 = null,
add_section: ?AddSection = null,
set_section_alignment: ?SetSectionAlignment = null,
set_section_flags: ?SetSectionFlags = null,
};
const AddSection = struct {
section_name: []const u8,
file_path: []const u8,
};
const SetSectionAlignment = struct {
section_name: []const u8,
alignment: u32,
};
const SetSectionFlags = struct {
section_name: []const u8,
flags: SectionFlags,
};
fn emitElf(
@ -678,6 +742,9 @@ const StripElfOptions = struct {
strip_debug: bool = false,
only_keep_debug: bool = false,
compress_debug: bool = false,
add_section: ?AddSection,
set_section_alignment: ?SetSectionAlignment,
set_section_flags: ?SetSectionFlags,
};
fn stripElf(
@ -721,6 +788,14 @@ fn stripElf(
var elf_file = try ElfFile(is_64).parse(allocator, in_file, elf_hdr);
defer elf_file.deinit();
if (options.add_section) |user_section| {
for (elf_file.sections) |section| {
if (std.mem.eql(u8, section.name, user_section.section_name)) {
fatal("zig objcopy: unable to add section '{s}'. Section already exists in input", .{user_section.section_name});
}
}
}
if (filter_complement) |flt| {
// write the .dbg file and close it, so it can be read back to compute the debuglink checksum.
const path = options.extract_to.?;
@ -733,7 +808,14 @@ fn stripElf(
}
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, .compress_debug = options.compress_debug });
try elf_file.emit(allocator, out_file, in_file, .{
.section_filter = filter,
.debuglink = debuglink,
.compress_debug = options.compress_debug,
.add_section = options.add_section,
.set_section_alignment = options.set_section_alignment,
.set_section_flags = options.set_section_flags,
});
},
}
}
@ -786,7 +868,7 @@ fn ElfFile(comptime is_64: bool) type {
// program header: list of segments
const program_segments = blk: {
if (@sizeOf(Elf_Phdr) != header.phentsize)
fatal("zig objcopy: unsuported ELF file, unexpected phentsize ({d})", .{header.phentsize});
fatal("zig objcopy: unsupported ELF file, unexpected phentsize ({d})", .{header.phentsize});
const program_header = try allocator.alloc(Elf_Phdr, header.phnum);
const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(program_header), header.phoff);
@ -798,7 +880,7 @@ fn ElfFile(comptime is_64: bool) type {
// section header
const sections = blk: {
if (@sizeOf(Elf_Shdr) != header.shentsize)
fatal("zig objcopy: unsuported ELF file, unexpected shentsize ({d})", .{header.shentsize});
fatal("zig objcopy: unsupported ELF file, unexpected shentsize ({d})", .{header.shentsize});
const section_header = try allocator.alloc(Section, header.shnum);
@ -896,6 +978,9 @@ fn ElfFile(comptime is_64: bool) type {
section_filter: Filter = .all,
debuglink: ?DebugLink = null,
compress_debug: bool = false,
add_section: ?AddSection = null,
set_section_alignment: ?SetSectionAlignment = null,
set_section_flags: ?SetSectionFlags = null,
};
fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void {
var arena = std.heap.ArenaAllocator.init(gpa);
@ -934,6 +1019,10 @@ fn ElfFile(comptime is_64: bool) type {
if (options.debuglink != null)
next_idx += 1;
if (options.add_section != null) {
next_idx += 1;
}
break :blk next_idx;
};
@ -959,6 +1048,28 @@ fn ElfFile(comptime is_64: bool) type {
break :blk new_offset;
};
// add user section to the string table if needed
const user_section_name: u32 = blk: {
if (options.add_section == null) break :blk elf.SHN_UNDEF;
if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF)
fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed?
const strtab = &self.sections[self.raw_elf_header.e_shstrndx];
const update = &sections_update[self.raw_elf_header.e_shstrndx];
const name = options.add_section.?.section_name;
const new_offset: u32 = @intCast(strtab.payload.?.len);
const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1);
@memcpy(buf[0..new_offset], strtab.payload.?);
@memcpy(buf[new_offset..][0..name.len], name);
buf[new_offset + name.len] = 0;
assert(update.action == .keep);
update.payload = buf;
break :blk new_offset;
};
// maybe compress .debug sections
if (options.compress_debug) {
for (self.sections[1..], sections_update[1..]) |section, *update| {
@ -1018,7 +1129,7 @@ fn ElfFile(comptime is_64: bool) type {
if (section.section.sh_type == elf.SHT_NOBITS)
continue;
if (section.section.sh_offset < offset) {
fatal("zig objcopy: unsuported ELF file", .{});
fatal("zig objcopy: unsupported ELF file", .{});
}
offset = section.section.sh_offset;
}
@ -1134,10 +1245,100 @@ fn ElfFile(comptime is_64: bool) type {
eof_offset += @as(Elf_OffSize, @intCast(payload.len));
}
// --add-section
if (options.add_section) |add_section| {
var section_file = fs.cwd().openFile(add_section.file_path, .{}) catch |err|
fatal("unable to open '{s}': {s}", .{ add_section.file_path, @errorName(err) });
defer section_file.close();
const payload = try section_file.readToEndAlloc(arena.allocator(), std.math.maxInt(usize));
dest_sections[dest_section_idx] = Elf_Shdr{
.sh_name = user_section_name,
.sh_type = elf.SHT_PROGBITS,
.sh_flags = 0,
.sh_addr = 0,
.sh_offset = eof_offset,
.sh_size = @intCast(payload.len),
.sh_link = elf.SHN_UNDEF,
.sh_info = elf.SHN_UNDEF,
.sh_addralign = 4,
.sh_entsize = 0,
};
dest_section_idx += 1;
cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = payload, .out_offset = eof_offset } });
eof_offset += @as(Elf_OffSize, @intCast(payload.len));
}
assert(dest_section_idx == new_shnum);
break :blk dest_sections;
};
// --set-section-alignment: overwrite alignment
if (options.set_section_alignment) |set_align| {
if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF)
fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed?
const strtab = &sections_update[self.raw_elf_header.e_shstrndx];
for (updated_section_header) |*section| {
const section_name = std.mem.span(@as([*:0]const u8, @ptrCast(&strtab.payload.?[section.sh_name])));
if (std.mem.eql(u8, section_name, set_align.section_name)) {
section.sh_addralign = set_align.alignment;
break;
}
} else std.log.warn("Skipping --set-section-alignment. Section '{s}' not found", .{set_align.section_name});
}
// --set-section-flags: overwrite flags
if (options.set_section_flags) |set_flags| {
if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF)
fatal("zig objcopy: no strtab, cannot add the user section", .{}); // TODO add the section if needed?
const strtab = &sections_update[self.raw_elf_header.e_shstrndx];
for (updated_section_header) |*section| {
const section_name = std.mem.span(@as([*:0]const u8, @ptrCast(&strtab.payload.?[section.sh_name])));
if (std.mem.eql(u8, section_name, set_flags.section_name)) {
section.sh_flags = std.elf.SHF_WRITE; // default is writable cleared by "readonly"
const f = set_flags.flags;
// Supporting a subset of GNU and LLVM objcopy for ELF only
// GNU:
// alloc: add SHF_ALLOC
// contents: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
// load: if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
// noload: not ELF relevant
// readonly: clear default SHF_WRITE flag
// code: add SHF_EXECINSTR
// data: not ELF relevant
// rom: ignored
// exclude: add SHF_EXCLUDE
// share: not ELF relevant
// debug: not ELF relevant
// large: add SHF_X86_64_LARGE. Fatal error if target is not x86_64
if (f.alloc) section.sh_flags |= std.elf.SHF_ALLOC;
if (f.contents or f.load) {
if (section.sh_type == std.elf.SHT_NOBITS) section.sh_type = std.elf.SHT_PROGBITS;
}
if (f.readonly) section.sh_flags &= ~@as(@TypeOf(section.sh_type), std.elf.SHF_WRITE);
if (f.code) section.sh_flags |= std.elf.SHF_EXECINSTR;
if (f.exclude) section.sh_flags |= std.elf.SHF_EXCLUDE;
if (f.large) {
if (updated_elf_header.e_machine != std.elf.EM.X86_64)
fatal("zig objcopy: 'large' section flag is only supported on x86_64 targets", .{});
section.sh_flags |= std.elf.SHF_X86_64_LARGE;
}
// LLVM:
// merge: add SHF_MERGE
// strings: add SHF_STRINGS
if (f.merge) section.sh_flags |= std.elf.SHF_MERGE;
if (f.strings) section.sh_flags |= std.elf.SHF_STRINGS;
break;
}
} else std.log.warn("Skipping --set-section-flags. Section '{s}' not found", .{set_flags.section_name});
}
// write the section header at the tail
{
const offset = std.mem.alignForward(Elf_OffSize, eof_offset, @alignOf(Elf_Shdr));
@ -1361,3 +1562,111 @@ const ElfFileHelper = struct {
return hasher.final();
}
};
const SectionFlags = packed struct {
alloc: bool = false,
contents: bool = false,
load: bool = false,
noload: bool = false,
readonly: bool = false,
code: bool = false,
data: bool = false,
rom: bool = false,
exclude: bool = false,
shared: bool = false,
debug: bool = false,
large: bool = false,
merge: bool = false,
strings: bool = false,
};
fn parseSectionFlags(comma_separated_flags: []const u8) SectionFlags {
const P = struct {
fn parse(flags: *SectionFlags, string: []const u8) void {
if (string.len == 0) return;
if (std.mem.eql(u8, string, "alloc")) {
flags.alloc = true;
} else if (std.mem.eql(u8, string, "contents")) {
flags.contents = true;
} else if (std.mem.eql(u8, string, "load")) {
flags.load = true;
} else if (std.mem.eql(u8, string, "noload")) {
flags.noload = true;
} else if (std.mem.eql(u8, string, "readonly")) {
flags.readonly = true;
} else if (std.mem.eql(u8, string, "code")) {
flags.code = true;
} else if (std.mem.eql(u8, string, "data")) {
flags.data = true;
} else if (std.mem.eql(u8, string, "rom")) {
flags.rom = true;
} else if (std.mem.eql(u8, string, "exclude")) {
flags.exclude = true;
} else if (std.mem.eql(u8, string, "shared")) {
flags.shared = true;
} else if (std.mem.eql(u8, string, "debug")) {
flags.debug = true;
} else if (std.mem.eql(u8, string, "large")) {
flags.large = true;
} else if (std.mem.eql(u8, string, "merge")) {
flags.merge = true;
} else if (std.mem.eql(u8, string, "strings")) {
flags.strings = true;
} else {
std.log.warn("Skipping unrecognized section flag '{s}'", .{string});
}
}
};
var flags = SectionFlags{};
var offset: usize = 0;
for (comma_separated_flags, 0..) |c, i| {
if (c == ',') {
defer offset = i + 1;
const string = comma_separated_flags[offset..i];
P.parse(&flags, string);
}
}
P.parse(&flags, comma_separated_flags[offset..]);
return flags;
}
test "Parse section flags" {
const F = SectionFlags;
try std.testing.expectEqual(F{}, parseSectionFlags(""));
try std.testing.expectEqual(F{}, parseSectionFlags(","));
try std.testing.expectEqual(F{}, parseSectionFlags("abc"));
try std.testing.expectEqual(F{ .alloc = true }, parseSectionFlags("alloc"));
try std.testing.expectEqual(F{ .data = true }, parseSectionFlags("data,"));
try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code"));
try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code,not_supported"));
}
const SplitResult = struct { first: []const u8, second: []const u8 };
fn splitOption(option: []const u8) ?SplitResult {
const separator = '=';
if (option.len < 3) return null; // minimum "a=b"
for (1..option.len - 1) |i| {
if (option[i] == separator) return .{
.first = option[0..i],
.second = option[i + 1 ..],
};
}
return null;
}
test "Split option" {
{
const split = splitOption(".abc=123");
try std.testing.expect(split != null);
try std.testing.expectEqualStrings(".abc", split.?.first);
try std.testing.expectEqualStrings("123", split.?.second);
}
try std.testing.expectEqual(null, splitOption(""));
try std.testing.expectEqual(null, splitOption("=abc"));
try std.testing.expectEqual(null, splitOption("abc="));
try std.testing.expectEqual(null, splitOption("abc"));
}

View File

@ -26,6 +26,50 @@ pub const Strip = enum {
debug_and_symbols,
};
pub const SectionFlags = packed struct {
/// add SHF_ALLOC
alloc: bool = false,
/// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
contents: bool = false,
/// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
load: bool = false,
/// readonly: clear default SHF_WRITE flag
readonly: bool = false,
/// add SHF_EXECINSTR
code: bool = false,
/// add SHF_EXCLUDE
exclude: bool = false,
/// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
large: bool = false,
/// add SHF_MERGE
merge: bool = false,
/// add SHF_STRINGS
strings: bool = false,
};
pub const AddSection = struct {
section_name: []const u8,
file_path: std.Build.LazyPath,
};
pub const SetSectionAlignment = struct {
section_name: []const u8,
alignment: u32,
};
pub const SetSectionFlags = struct {
section_name: []const u8,
flags: SectionFlags,
};
step: Step,
input_file: std.Build.LazyPath,
basename: []const u8,
@ -38,6 +82,10 @@ pad_to: ?u64,
strip: Strip,
compress_debug: bool,
add_section: ?AddSection,
set_section_alignment: ?SetSectionAlignment,
set_section_flags: ?SetSectionFlags,
pub const Options = struct {
basename: ?[]const u8 = null,
format: ?RawFormat = null,
@ -51,6 +99,10 @@ pub const Options = struct {
/// 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,
add_section: ?AddSection = null,
set_section_alignment: ?SetSectionAlignment = null,
set_section_flags: ?SetSectionFlags = null,
};
pub fn create(
@ -75,6 +127,9 @@ pub fn create(
.pad_to = options.pad_to,
.strip = options.strip,
.compress_debug = options.compress_debug,
.add_section = options.add_section,
.set_section_alignment = options.set_section_alignment,
.set_section_flags = options.set_section_flags,
};
input_file.addStepDependencies(&objcopy.step);
return objcopy;
@ -155,6 +210,31 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
if (objcopy.output_file_debug != null) {
try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})});
}
if (objcopy.add_section) |section| {
try argv.append("--add-section");
try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath(b) })});
}
if (objcopy.set_section_alignment) |set_align| {
try argv.append("--set-section-alignment");
try argv.appendSlice(&.{b.fmt("{s}={d}", .{ set_align.section_name, set_align.alignment })});
}
if (objcopy.set_section_flags) |set_flags| {
const f = set_flags.flags;
// trailing comma is allowed
try argv.append("--set-section-flags");
try argv.appendSlice(&.{b.fmt("{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
set_flags.section_name,
if (f.alloc) "alloc," else "",
if (f.contents) "contents," else "",
if (f.load) "load," else "",
if (f.readonly) "readonly," else "",
if (f.code) "code," else "",
if (f.exclude) "exclude," else "",
if (f.large) "large," else "",
if (f.merge) "merge," else "",
if (f.strings) "strings," else "",
})});
}
try argv.appendSlice(&.{ full_src_path, full_dest_path });