master
  1const std = @import("std");
  2const ObjCopy = @This();
  3
  4const Allocator = std.mem.Allocator;
  5const ArenaAllocator = std.heap.ArenaAllocator;
  6const File = std.fs.File;
  7const InstallDir = std.Build.InstallDir;
  8const Step = std.Build.Step;
  9const elf = std.elf;
 10const fs = std.fs;
 11const sort = std.sort;
 12
 13pub const base_id: Step.Id = .objcopy;
 14
 15pub const RawFormat = enum {
 16    bin,
 17    hex,
 18    elf,
 19};
 20
 21pub const Strip = enum {
 22    none,
 23    debug,
 24    debug_and_symbols,
 25};
 26
 27pub const SectionFlags = packed struct {
 28    /// add SHF_ALLOC
 29    alloc: bool = false,
 30
 31    /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing
 32    contents: bool = false,
 33
 34    /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents)
 35    load: bool = false,
 36
 37    /// readonly: clear default SHF_WRITE flag
 38    readonly: bool = false,
 39
 40    /// add SHF_EXECINSTR
 41    code: bool = false,
 42
 43    /// add SHF_EXCLUDE
 44    exclude: bool = false,
 45
 46    /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64
 47    large: bool = false,
 48
 49    /// add SHF_MERGE
 50    merge: bool = false,
 51
 52    /// add SHF_STRINGS
 53    strings: bool = false,
 54};
 55
 56pub const AddSection = struct {
 57    section_name: []const u8,
 58    file_path: std.Build.LazyPath,
 59};
 60
 61pub const SetSectionAlignment = struct {
 62    section_name: []const u8,
 63    alignment: u32,
 64};
 65
 66pub const SetSectionFlags = struct {
 67    section_name: []const u8,
 68    flags: SectionFlags,
 69};
 70
 71step: Step,
 72input_file: std.Build.LazyPath,
 73basename: []const u8,
 74output_file: std.Build.GeneratedFile,
 75output_file_debug: ?std.Build.GeneratedFile,
 76
 77format: ?RawFormat,
 78only_section: ?[]const u8,
 79pad_to: ?u64,
 80strip: Strip,
 81compress_debug: bool,
 82
 83add_section: ?AddSection,
 84set_section_alignment: ?SetSectionAlignment,
 85set_section_flags: ?SetSectionFlags,
 86
 87pub const Options = struct {
 88    basename: ?[]const u8 = null,
 89    format: ?RawFormat = null,
 90    only_section: ?[]const u8 = null,
 91    pad_to: ?u64 = null,
 92
 93    compress_debug: bool = false,
 94    strip: Strip = .none,
 95
 96    /// Put the stripped out debug sections in a separate file.
 97    /// note: the `basename` is baked into the elf file to specify the link to the separate debug file.
 98    /// see https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
 99    extract_to_separate_file: bool = false,
100
101    add_section: ?AddSection = null,
102    set_section_alignment: ?SetSectionAlignment = null,
103    set_section_flags: ?SetSectionFlags = null,
104};
105
106pub fn create(
107    owner: *std.Build,
108    input_file: std.Build.LazyPath,
109    options: Options,
110) *ObjCopy {
111    const objcopy = owner.allocator.create(ObjCopy) catch @panic("OOM");
112    objcopy.* = ObjCopy{
113        .step = Step.init(.{
114            .id = base_id,
115            .name = owner.fmt("objcopy {s}", .{input_file.getDisplayName()}),
116            .owner = owner,
117            .makeFn = make,
118        }),
119        .input_file = input_file,
120        .basename = options.basename orelse input_file.getDisplayName(),
121        .output_file = std.Build.GeneratedFile{ .step = &objcopy.step },
122        .output_file_debug = if (options.strip != .none and options.extract_to_separate_file) std.Build.GeneratedFile{ .step = &objcopy.step } else null,
123        .format = options.format,
124        .only_section = options.only_section,
125        .pad_to = options.pad_to,
126        .strip = options.strip,
127        .compress_debug = options.compress_debug,
128        .add_section = options.add_section,
129        .set_section_alignment = options.set_section_alignment,
130        .set_section_flags = options.set_section_flags,
131    };
132    input_file.addStepDependencies(&objcopy.step);
133    return objcopy;
134}
135
136pub fn getOutput(objcopy: *const ObjCopy) std.Build.LazyPath {
137    return .{ .generated = .{ .file = &objcopy.output_file } };
138}
139pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath {
140    return if (objcopy.output_file_debug) |*file| .{ .generated = .{ .file = file } } else null;
141}
142
143fn make(step: *Step, options: Step.MakeOptions) !void {
144    const prog_node = options.progress_node;
145    const b = step.owner;
146    const objcopy: *ObjCopy = @fieldParentPtr("step", step);
147    try step.singleUnchangingWatchInput(objcopy.input_file);
148
149    var man = b.graph.cache.obtain();
150    defer man.deinit();
151
152    const full_src_path = objcopy.input_file.getPath2(b, step);
153    _ = try man.addFile(full_src_path, null);
154    man.hash.addOptionalBytes(objcopy.only_section);
155    man.hash.addOptional(objcopy.pad_to);
156    man.hash.addOptional(objcopy.format);
157    man.hash.add(objcopy.compress_debug);
158    man.hash.add(objcopy.strip);
159    man.hash.add(objcopy.output_file_debug != null);
160
161    if (try step.cacheHit(&man)) {
162        // Cache hit, skip subprocess execution.
163        const digest = man.final();
164        objcopy.output_file.path = try b.cache_root.join(b.allocator, &.{
165            "o", &digest, objcopy.basename,
166        });
167        if (objcopy.output_file_debug) |*file| {
168            file.path = try b.cache_root.join(b.allocator, &.{
169                "o", &digest, b.fmt("{s}.debug", .{objcopy.basename}),
170            });
171        }
172        return;
173    }
174
175    const digest = man.final();
176    const cache_path = "o" ++ fs.path.sep_str ++ digest;
177    const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename });
178    const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) });
179    b.cache_root.handle.makePath(cache_path) catch |err| {
180        return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
181    };
182
183    var argv = std.array_list.Managed([]const u8).init(b.allocator);
184    try argv.appendSlice(&.{ b.graph.zig_exe, "objcopy" });
185
186    if (objcopy.only_section) |only_section| {
187        try argv.appendSlice(&.{ "-j", only_section });
188    }
189    switch (objcopy.strip) {
190        .none => {},
191        .debug => try argv.appendSlice(&.{"--strip-debug"}),
192        .debug_and_symbols => try argv.appendSlice(&.{"--strip-all"}),
193    }
194    if (objcopy.pad_to) |pad_to| {
195        try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) });
196    }
197    if (objcopy.format) |format| switch (format) {
198        .bin => try argv.appendSlice(&.{ "-O", "binary" }),
199        .hex => try argv.appendSlice(&.{ "-O", "hex" }),
200        .elf => try argv.appendSlice(&.{ "-O", "elf" }),
201    };
202    if (objcopy.compress_debug) {
203        try argv.appendSlice(&.{"--compress-debug-sections"});
204    }
205    if (objcopy.output_file_debug != null) {
206        try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})});
207    }
208    if (objcopy.add_section) |section| {
209        try argv.append("--add-section");
210        try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath2(b, step) })});
211    }
212    if (objcopy.set_section_alignment) |set_align| {
213        try argv.append("--set-section-alignment");
214        try argv.appendSlice(&.{b.fmt("{s}={d}", .{ set_align.section_name, set_align.alignment })});
215    }
216    if (objcopy.set_section_flags) |set_flags| {
217        const f = set_flags.flags;
218        // trailing comma is allowed
219        try argv.append("--set-section-flags");
220        try argv.appendSlice(&.{b.fmt("{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{
221            set_flags.section_name,
222            if (f.alloc) "alloc," else "",
223            if (f.contents) "contents," else "",
224            if (f.load) "load," else "",
225            if (f.readonly) "readonly," else "",
226            if (f.code) "code," else "",
227            if (f.exclude) "exclude," else "",
228            if (f.large) "large," else "",
229            if (f.merge) "merge," else "",
230            if (f.strings) "strings," else "",
231        })});
232    }
233
234    try argv.appendSlice(&.{ full_src_path, full_dest_path });
235
236    try argv.append("--listen=-");
237    _ = try step.evalZigProcess(argv.items, prog_node, false, options.web_server, options.gpa);
238
239    objcopy.output_file.path = full_dest_path;
240    if (objcopy.output_file_debug) |*file| file.path = full_dest_path_debug;
241    try man.writeManifest();
242}