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}