authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-12 18:20:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-13 15:37:52-05:00
log9be5323e93123a3979037997fa40a95b4c985b85
tree8e6af2db5efc74d21ac7faad5b77c726a7276583
parent5d3adc568c7f0d4720bbd283337404c3cad86479

add `zig objcopy` subcommand

This commit moves the logic from `std.build.InstallRawStep` into `zig objcopy`. The options here are limited, but we can add features as needed. closes #9261 New issues can be opened for specific objcopy flag support.

4 files changed, 583 insertions(+), 453 deletions(-)

lib/std/build/InstallRawStep.zig+29-452
......@@ -1,4 +1,8 @@
1//! TODO: Rename this to ObjCopyStep now that it invokes the `zig objcopy`
2//! subcommand rather than containing an implementation directly.
3
14const std = @import("std");
5const InstallRawStep = @This();
26
37const Allocator = std.mem.Allocator;
48const ArenaAllocator = std.heap.ArenaAllocator;
......@@ -13,406 +17,6 @@ const fs = std.fs;
1317const io = std.io;
1418const sort = std.sort;
1519
16const BinaryElfSection = struct {
17 elfOffset: u64,
18 binaryOffset: u64,
19 fileSize: usize,
20 name: ?[]const u8,
21 segment: ?*BinaryElfSegment,
22};
23
24const BinaryElfSegment = struct {
25 physicalAddress: u64,
26 virtualAddress: u64,
27 elfOffset: u64,
28 binaryOffset: u64,
29 fileSize: u64,
30 firstSection: ?*BinaryElfSection,
31};
32
33const BinaryElfOutput = struct {
34 segments: ArrayListUnmanaged(*BinaryElfSegment),
35 sections: ArrayListUnmanaged(*BinaryElfSection),
36 allocator: Allocator,
37 shstrtab: ?[]const u8,
38
39 const Self = @This();
40
41 pub fn deinit(self: *Self) void {
42 if (self.shstrtab) |shstrtab|
43 self.allocator.free(shstrtab);
44 self.sections.deinit(self.allocator);
45 self.segments.deinit(self.allocator);
46 }
47
48 pub fn parse(allocator: Allocator, elf_file: File) !Self {
49 var self: Self = .{
50 .segments = .{},
51 .sections = .{},
52 .allocator = allocator,
53 .shstrtab = null,
54 };
55 errdefer self.sections.deinit(allocator);
56 errdefer self.segments.deinit(allocator);
57
58 const elf_hdr = try std.elf.Header.read(&elf_file);
59
60 self.shstrtab = blk: {
61 if (elf_hdr.shstrndx >= elf_hdr.shnum) break :blk null;
62
63 var section_headers = elf_hdr.section_header_iterator(&elf_file);
64
65 var section_counter: usize = 0;
66 while (section_counter < elf_hdr.shstrndx) : (section_counter += 1) {
67 _ = (try section_headers.next()).?;
68 }
69
70 const shstrtab_shdr = (try section_headers.next()).?;
71
72 const buffer = try allocator.alloc(u8, @intCast(usize, shstrtab_shdr.sh_size));
73 errdefer allocator.free(buffer);
74
75 const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset);
76 if (num_read != buffer.len) return error.EndOfStream;
77
78 break :blk buffer;
79 };
80
81 errdefer if (self.shstrtab) |shstrtab| allocator.free(shstrtab);
82
83 var section_headers = elf_hdr.section_header_iterator(&elf_file);
84 while (try section_headers.next()) |section| {
85 if (sectionValidForOutput(section)) {
86 const newSection = try allocator.create(BinaryElfSection);
87
88 newSection.binaryOffset = 0;
89 newSection.elfOffset = section.sh_offset;
90 newSection.fileSize = @intCast(usize, section.sh_size);
91 newSection.segment = null;
92
93 newSection.name = if (self.shstrtab) |shstrtab|
94 std.mem.span(@ptrCast([*:0]const u8, &shstrtab[section.sh_name]))
95 else
96 null;
97
98 try self.sections.append(allocator, newSection);
99 }
100 }
101
102 var program_headers = elf_hdr.program_header_iterator(&elf_file);
103 while (try program_headers.next()) |phdr| {
104 if (phdr.p_type == elf.PT_LOAD) {
105 const newSegment = try allocator.create(BinaryElfSegment);
106
107 newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr;
108 newSegment.virtualAddress = phdr.p_vaddr;
109 newSegment.fileSize = @intCast(usize, phdr.p_filesz);
110 newSegment.elfOffset = phdr.p_offset;
111 newSegment.binaryOffset = 0;
112 newSegment.firstSection = null;
113
114 for (self.sections.items) |section| {
115 if (sectionWithinSegment(section, phdr)) {
116 if (section.segment) |sectionSegment| {
117 if (sectionSegment.elfOffset > newSegment.elfOffset) {
118 section.segment = newSegment;
119 }
120 } else {
121 section.segment = newSegment;
122 }
123
124 if (newSegment.firstSection == null) {
125 newSegment.firstSection = section;
126 }
127 }
128 }
129
130 try self.segments.append(allocator, newSegment);
131 }
132 }
133
134 sort.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
135
136 for (self.segments.items) |firstSegment, i| {
137 if (firstSegment.firstSection) |firstSection| {
138 const diff = firstSection.elfOffset - firstSegment.elfOffset;
139
140 firstSegment.elfOffset += diff;
141 firstSegment.fileSize += diff;
142 firstSegment.physicalAddress += diff;
143
144 const basePhysicalAddress = firstSegment.physicalAddress;
145
146 for (self.segments.items[i + 1 ..]) |segment| {
147 segment.binaryOffset = segment.physicalAddress - basePhysicalAddress;
148 }
149 break;
150 }
151 }
152
153 for (self.sections.items) |section| {
154 if (section.segment) |segment| {
155 section.binaryOffset = segment.binaryOffset + (section.elfOffset - segment.elfOffset);
156 }
157 }
158
159 sort.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
160
161 return self;
162 }
163
164 fn sectionWithinSegment(section: *BinaryElfSection, segment: elf.Elf64_Phdr) bool {
165 return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize);
166 }
167
168 fn sectionValidForOutput(shdr: anytype) bool {
169 return shdr.sh_size > 0 and shdr.sh_type != elf.SHT_NOBITS and
170 ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC);
171 }
172
173 fn segmentSortCompare(context: void, left: *BinaryElfSegment, right: *BinaryElfSegment) bool {
174 _ = context;
175 if (left.physicalAddress < right.physicalAddress) {
176 return true;
177 }
178 if (left.physicalAddress > right.physicalAddress) {
179 return false;
180 }
181 return false;
182 }
183
184 fn sectionSortCompare(context: void, left: *BinaryElfSection, right: *BinaryElfSection) bool {
185 _ = context;
186 return left.binaryOffset < right.binaryOffset;
187 }
188};
189
190fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSection) !void {
191 try out_file.writeFileAll(elf_file, .{
192 .in_offset = section.elfOffset,
193 .in_len = section.fileSize,
194 });
195}
196
197const HexWriter = struct {
198 prev_addr: ?u32 = null,
199 out_file: File,
200
201 /// Max data bytes per line of output
202 const MAX_PAYLOAD_LEN: u8 = 16;
203
204 fn addressParts(address: u16) [2]u8 {
205 const msb = @truncate(u8, address >> 8);
206 const lsb = @truncate(u8, address);
207 return [2]u8{ msb, lsb };
208 }
209
210 const Record = struct {
211 const Type = enum(u8) {
212 Data = 0,
213 EOF = 1,
214 ExtendedSegmentAddress = 2,
215 ExtendedLinearAddress = 4,
216 };
217
218 address: u16,
219 payload: union(Type) {
220 Data: []const u8,
221 EOF: void,
222 ExtendedSegmentAddress: [2]u8,
223 ExtendedLinearAddress: [2]u8,
224 },
225
226 fn EOF() Record {
227 return Record{
228 .address = 0,
229 .payload = .EOF,
230 };
231 }
232
233 fn Data(address: u32, data: []const u8) Record {
234 return Record{
235 .address = @intCast(u16, address % 0x10000),
236 .payload = .{ .Data = data },
237 };
238 }
239
240 fn Address(address: u32) Record {
241 std.debug.assert(address > 0xFFFF);
242 const segment = @intCast(u16, address / 0x10000);
243 if (address > 0xFFFFF) {
244 return Record{
245 .address = 0,
246 .payload = .{ .ExtendedLinearAddress = addressParts(segment) },
247 };
248 } else {
249 return Record{
250 .address = 0,
251 .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) },
252 };
253 }
254 }
255
256 fn getPayloadBytes(self: Record) []const u8 {
257 return switch (self.payload) {
258 .Data => |d| d,
259 .EOF => @as([]const u8, &.{}),
260 .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg,
261 };
262 }
263
264 fn checksum(self: Record) u8 {
265 const payload_bytes = self.getPayloadBytes();
266
267 var sum: u8 = @intCast(u8, payload_bytes.len);
268 const parts = addressParts(self.address);
269 sum +%= parts[0];
270 sum +%= parts[1];
271 sum +%= @enumToInt(self.payload);
272 for (payload_bytes) |byte| {
273 sum +%= byte;
274 }
275 return (sum ^ 0xFF) +% 1;
276 }
277
278 fn write(self: Record, file: File) File.WriteError!void {
279 const linesep = "\r\n";
280 // colon, (length, address, type, payload, checksum) as hex, CRLF
281 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
282 var outbuf: [BUFSIZE]u8 = undefined;
283 const payload_bytes = self.getPayloadBytes();
284 std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
285
286 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
287 @intCast(u8, payload_bytes.len),
288 self.address,
289 @enumToInt(self.payload),
290 std.fmt.fmtSliceHexUpper(payload_bytes),
291 self.checksum(),
292 });
293 try file.writeAll(line);
294 }
295 };
296
297 pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, elf_file: File) !void {
298 var buf: [MAX_PAYLOAD_LEN]u8 = undefined;
299 var bytes_read: usize = 0;
300 while (bytes_read < segment.fileSize) {
301 const row_address = @intCast(u32, segment.physicalAddress + bytes_read);
302
303 const remaining = segment.fileSize - bytes_read;
304 const to_read = @intCast(usize, @min(remaining, MAX_PAYLOAD_LEN));
305 const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read);
306 if (did_read < to_read) return error.UnexpectedEOF;
307
308 try self.writeDataRow(row_address, buf[0..did_read]);
309
310 bytes_read += did_read;
311 }
312 }
313
314 fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) File.WriteError!void {
315 const record = Record.Data(address, data);
316 if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) {
317 try Record.Address(address).write(self.out_file);
318 }
319 try record.write(self.out_file);
320 self.prev_addr = @intCast(u32, record.address + data.len);
321 }
322
323 fn writeEOF(self: HexWriter) File.WriteError!void {
324 try Record.EOF().write(self.out_file);
325 }
326};
327
328fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
329 const max_address = std.math.maxInt(u32);
330 for (segments) |segment| {
331 if (segment.fileSize > max_address or
332 segment.physicalAddress > max_address - segment.fileSize) return false;
333 }
334 return true;
335}
336
337fn padFile(f: fs.File, size: ?usize) !void {
338 if (size) |pad_size| {
339 const current_size = try f.getEndPos();
340 if (current_size < pad_size) {
341 try f.seekTo(pad_size - 1);
342 try f.writer().writeByte(0);
343 }
344 if (current_size > pad_size) {
345 return error.FileTooLarge; // Maybe this shouldn't be an error?
346 }
347 }
348}
349
350fn emitRaw(allocator: Allocator, elf_path: []const u8, raw_path: []const u8, options: CreateOptions) !void {
351 var elf_file = try fs.cwd().openFile(elf_path, .{});
352 defer elf_file.close();
353
354 var out_file = try fs.cwd().createFile(raw_path, .{});
355 defer out_file.close();
356
357 var binary_elf_output = try BinaryElfOutput.parse(allocator, elf_file);
358 defer binary_elf_output.deinit();
359
360 const effective_format = options.format orelse detectFormat(raw_path);
361
362 if (options.only_section_name) |target_name| {
363 switch (effective_format) {
364 // Hex format can only write segments/phdrs, sections not supported yet
365 .hex => return error.NotYetImplemented,
366 .bin => {
367 for (binary_elf_output.sections.items) |section| {
368 if (section.name) |curr_name| {
369 if (!std.mem.eql(u8, curr_name, target_name))
370 continue;
371 } else {
372 continue;
373 }
374
375 try writeBinaryElfSection(elf_file, out_file, section);
376 try padFile(out_file, options.pad_to_size);
377 return;
378 }
379 },
380 }
381
382 return error.SectionNotFound;
383 }
384
385 switch (effective_format) {
386 .bin => {
387 for (binary_elf_output.sections.items) |section| {
388 try out_file.seekTo(section.binaryOffset);
389 try writeBinaryElfSection(elf_file, out_file, section);
390 }
391 try padFile(out_file, options.pad_to_size);
392 },
393 .hex => {
394 if (binary_elf_output.segments.items.len == 0) return;
395 if (!containsValidAddressRange(binary_elf_output.segments.items)) {
396 return error.InvalidHexfileAddressRange;
397 }
398
399 var hex_writer = HexWriter{ .out_file = out_file };
400 for (binary_elf_output.sections.items) |section| {
401 if (section.segment) |segment| {
402 try hex_writer.writeSegment(segment, elf_file);
403 }
404 }
405 if (options.pad_to_size) |_| {
406 // Padding to a size in hex files isn't applicable
407 return error.InvalidArgument;
408 }
409 try hex_writer.writeEOF();
410 },
411 }
412}
413
414const InstallRawStep = @This();
415
41620pub const base_id = .install_raw;
41721
41822pub const RawFormat = enum {
......@@ -428,18 +32,11 @@ dest_filename: []const u8,
42832options: CreateOptions,
42933output_file: std.build.GeneratedFile,
43034
431fn detectFormat(filename: []const u8) RawFormat {
432 if (std.mem.endsWith(u8, filename, ".hex") or std.mem.endsWith(u8, filename, ".ihex")) {
433 return .hex;
434 }
435 return .bin;
436}
437
43835pub const CreateOptions = struct {
43936 format: ?RawFormat = null,
44037 dest_dir: ?InstallDir = null,
441 only_section_name: ?[]const u8 = null,
442 pad_to_size: ?usize = null,
38 only_section: ?[]const u8 = null,
39 pad_to: ?u64 = null,
44340};
44441
44542pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: CreateOptions) *InstallRawStep {
......@@ -470,60 +67,40 @@ pub fn getOutputSource(self: *const InstallRawStep) std.build.FileSource {
47067
47168fn make(step: *Step) !void {
47269 const self = @fieldParentPtr(InstallRawStep, "step", step);
473 const builder = self.builder;
70 const b = self.builder;
47471
47572 if (self.artifact.target.getObjectFormat() != .elf) {
47673 std.debug.print("InstallRawStep only works with ELF format.\n", .{});
47774 return error.InvalidObjectFormat;
47875 }
47976
480 const full_src_path = self.artifact.getOutputSource().getPath(builder);
481 const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename);
482
483 fs.cwd().makePath(builder.getInstallPath(self.dest_dir, "")) catch unreachable;
484 try emitRaw(builder.allocator, full_src_path, full_dest_path, self.options);
77 const full_src_path = self.artifact.getOutputSource().getPath(b);
78 const full_dest_path = b.getInstallPath(self.dest_dir, self.dest_filename);
48579 self.output_file.path = full_dest_path;
486}
48780
488test {
489 std.testing.refAllDecls(InstallRawStep);
490}
81 fs.cwd().makePath(b.getInstallPath(self.dest_dir, "")) catch unreachable;
49182
492test "Detect format from filename" {
493 try std.testing.expectEqual(RawFormat.hex, detectFormat("foo.hex"));
494 try std.testing.expectEqual(RawFormat.hex, detectFormat("foo.ihex"));
495 try std.testing.expectEqual(RawFormat.bin, detectFormat("foo.bin"));
496 try std.testing.expectEqual(RawFormat.bin, detectFormat("foo.bar"));
497 try std.testing.expectEqual(RawFormat.bin, detectFormat("a"));
498}
83 var argv_list = std.ArrayList([]const u8).init(b.allocator);
84 try argv_list.appendSlice(&.{ b.zig_exe, "objcopy" });
49985
500test "containsValidAddressRange" {
501 var segment = BinaryElfSegment{
502 .physicalAddress = 0,
503 .virtualAddress = 0,
504 .elfOffset = 0,
505 .binaryOffset = 0,
506 .fileSize = 0,
507 .firstSection = null,
86 if (self.options.only_section) |only_section| {
87 try argv_list.appendSlice(&.{ "-j", only_section });
88 }
89 if (self.options.pad_to) |pad_to| {
90 try argv_list.appendSlice(&.{
91 "--pad-to",
92 b.fmt("{d}", .{pad_to}),
93 });
94 }
95 if (self.options.format) |format| switch (format) {
96 .bin => try argv_list.appendSlice(&.{ "-O", "binary" }),
97 .hex => try argv_list.appendSlice(&.{ "-O", "hex" }),
50898 };
509 var buf: [1]*BinaryElfSegment = .{&segment};
510
511 // segment too big
512 segment.fileSize = std.math.maxInt(u32) + 1;
513 try std.testing.expect(!containsValidAddressRange(&buf));
514
515 // start address too big
516 segment.physicalAddress = std.math.maxInt(u32) + 1;
517 segment.fileSize = 2;
518 try std.testing.expect(!containsValidAddressRange(&buf));
51999
520 // max address too big
521 segment.physicalAddress = std.math.maxInt(u32) - 1;
522 segment.fileSize = 2;
523 try std.testing.expect(!containsValidAddressRange(&buf));
100 try argv_list.appendSlice(&.{ full_src_path, full_dest_path });
101 _ = try self.builder.execFromStep(argv_list.items, &self.step);
102}
524103
525 // is ok
526 segment.physicalAddress = std.math.maxInt(u32) - 1;
527 segment.fileSize = 1;
528 try std.testing.expect(containsValidAddressRange(&buf));
104test {
105 std.testing.refAllDecls(InstallRawStep);
529106}
src/main.zig+3
......@@ -85,6 +85,7 @@ const normal_usage =
8585 \\ dlltool Use Zig as a drop-in dlltool.exe
8686 \\ lib Use Zig as a drop-in lib.exe
8787 \\ ranlib Use Zig as a drop-in ranlib
88 \\ objcopy Use Zig as a drop-in objcopy
8889 \\
8990 \\ env Print lib path, std path, cache directory, and version
9091 \\ help Print this help and exit
......@@ -286,6 +287,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
286287 return cmdBuild(gpa, arena, cmd_args);
287288 } else if (mem.eql(u8, cmd, "fmt")) {
288289 return cmdFmt(gpa, arena, cmd_args);
290 } else if (mem.eql(u8, cmd, "objcopy")) {
291 return @import("objcopy.zig").cmdObjCopy(gpa, arena, cmd_args);
289292 } else if (mem.eql(u8, cmd, "libc")) {
290293 return cmdLibC(gpa, cmd_args);
291294 } else if (mem.eql(u8, cmd, "init-exe")) {
src/objcopy.zig created+550
......@@ -0,0 +1,550 @@
1const std = @import("std");
2const mem = std.mem;
3const fs = std.fs;
4const elf = std.elf;
5const Allocator = std.mem.Allocator;
6const File = std.fs.File;
7const main = @import("main.zig");
8const fatal = main.fatal;
9const cleanExit = main.cleanExit;
10
11pub fn cmdObjCopy(
12 gpa: Allocator,
13 arena: Allocator,
14 args: []const []const u8,
15) !void {
16 _ = gpa;
17 var i: usize = 0;
18 var opt_out_fmt: ?std.Target.ObjectFormat = null;
19 var opt_input: ?[]const u8 = null;
20 var opt_output: ?[]const u8 = null;
21 var only_section: ?[]const u8 = null;
22 var pad_to: ?u64 = null;
23 while (i < args.len) : (i += 1) {
24 const arg = args[i];
25 if (!mem.startsWith(u8, arg, "-")) {
26 if (opt_input == null) {
27 opt_input = arg;
28 } else if (opt_output == null) {
29 opt_output = arg;
30 } else {
31 fatal("unexpected positional argument: '{s}'", .{arg});
32 }
33 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
34 return std.io.getStdOut().writeAll(usage);
35 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {
36 i += 1;
37 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
38 const next_arg = args[i];
39 if (mem.eql(u8, next_arg, "binary")) {
40 opt_out_fmt = .raw;
41 } else {
42 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
43 fatal("invalid output format: '{s}'", .{next_arg});
44 }
45 } else if (mem.startsWith(u8, arg, "--output-target=")) {
46 const next_arg = arg["--output-target=".len..];
47 if (mem.eql(u8, next_arg, "binary")) {
48 opt_out_fmt = .raw;
49 } else {
50 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
51 fatal("invalid output format: '{s}'", .{next_arg});
52 }
53 } else if (mem.eql(u8, arg, "-j") or mem.eql(u8, arg, "--only-section")) {
54 i += 1;
55 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
56 only_section = args[i];
57 } else if (mem.startsWith(u8, arg, "--only-section=")) {
58 only_section = arg["--output-target=".len..];
59 } else if (mem.eql(u8, arg, "--pad-to")) {
60 i += 1;
61 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
62 pad_to = std.fmt.parseInt(u64, args[i], 0) catch |err| {
63 fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });
64 };
65 } else {
66 fatal("unrecognized argument: '{s}'", .{arg});
67 }
68 }
69 const input = opt_input orelse fatal("expected input parameter", .{});
70 const output = opt_output orelse fatal("expected output parameter", .{});
71
72 var in_file = fs.cwd().openFile(input, .{}) catch |err|
73 fatal("unable to open '{s}': {s}", .{ input, @errorName(err) });
74 defer in_file.close();
75
76 var out_file = try fs.cwd().createFile(output, .{});
77 defer out_file.close();
78
79 const elf_hdr = std.elf.Header.read(in_file) catch |err| switch (err) {
80 error.InvalidElfMagic => fatal("not an ELF file: '{s}'", .{input}),
81 else => fatal("unable to read '{s}': {s}", .{ input, @errorName(err) }),
82 };
83
84 const in_ofmt = .elf;
85
86 const out_fmt: std.Target.ObjectFormat = opt_out_fmt orelse ofmt: {
87 if (mem.endsWith(u8, output, ".hex") or std.mem.endsWith(u8, output, ".ihex")) {
88 break :ofmt .hex;
89 } else if (mem.endsWith(u8, output, ".bin")) {
90 break :ofmt .raw;
91 } else if (mem.endsWith(u8, output, ".elf")) {
92 break :ofmt .elf;
93 } else {
94 break :ofmt in_ofmt;
95 }
96 };
97
98 switch (out_fmt) {
99 .hex, .raw, .elf => {
100 try emitElf(arena, in_file, out_file, elf_hdr, .{
101 .ofmt = out_fmt,
102 .only_section = only_section,
103 .pad_to = pad_to,
104 });
105 return cleanExit();
106 },
107 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
108 }
109}
110
111const usage =
112 \\Usage: zig objcopy [options] input output
113 \\
114 \\Options:
115 \\ -h, --help Print this help and exit
116 \\ --output-target=<value> Format of the output file
117 \\ -O <value> Alias for --output-target
118 \\ --only-section=<section> Remove all but <section>
119 \\ -j <value> Alias for --only-section
120 \\ --pad-to <addr> Pad the last section up to address <addr>
121 \\
122;
123
124pub const EmitRawElfOptions = struct {
125 ofmt: std.Target.ObjectFormat,
126 only_section: ?[]const u8 = null,
127 pad_to: ?u64 = null,
128};
129
130fn emitElf(
131 arena: Allocator,
132 in_file: File,
133 out_file: File,
134 elf_hdr: elf.Header,
135 options: EmitRawElfOptions,
136) !void {
137 var binary_elf_output = try BinaryElfOutput.parse(arena, in_file, elf_hdr);
138 defer binary_elf_output.deinit();
139
140 if (options.ofmt == .elf) {
141 fatal("zig objcopy: ELF to ELF copying is not implemented yet", .{});
142 }
143
144 if (options.only_section) |target_name| {
145 switch (options.ofmt) {
146 .hex => fatal("zig objcopy: hex format with sections is not implemented yet", .{}),
147 .raw => {
148 for (binary_elf_output.sections.items) |section| {
149 if (section.name) |curr_name| {
150 if (!std.mem.eql(u8, curr_name, target_name))
151 continue;
152 } else {
153 continue;
154 }
155
156 try writeBinaryElfSection(in_file, out_file, section);
157 try padFile(out_file, options.pad_to);
158 return;
159 }
160 },
161 else => unreachable,
162 }
163
164 return error.SectionNotFound;
165 }
166
167 switch (options.ofmt) {
168 .raw => {
169 for (binary_elf_output.sections.items) |section| {
170 try out_file.seekTo(section.binaryOffset);
171 try writeBinaryElfSection(in_file, out_file, section);
172 }
173 try padFile(out_file, options.pad_to);
174 },
175 .hex => {
176 if (binary_elf_output.segments.items.len == 0) return;
177 if (!containsValidAddressRange(binary_elf_output.segments.items)) {
178 return error.InvalidHexfileAddressRange;
179 }
180
181 var hex_writer = HexWriter{ .out_file = out_file };
182 for (binary_elf_output.sections.items) |section| {
183 if (section.segment) |segment| {
184 try hex_writer.writeSegment(segment, in_file);
185 }
186 }
187 if (options.pad_to) |_| {
188 // Padding to a size in hex files isn't applicable
189 return error.InvalidArgument;
190 }
191 try hex_writer.writeEOF();
192 },
193 else => unreachable,
194 }
195}
196
197const BinaryElfSection = struct {
198 elfOffset: u64,
199 binaryOffset: u64,
200 fileSize: usize,
201 name: ?[]const u8,
202 segment: ?*BinaryElfSegment,
203};
204
205const BinaryElfSegment = struct {
206 physicalAddress: u64,
207 virtualAddress: u64,
208 elfOffset: u64,
209 binaryOffset: u64,
210 fileSize: u64,
211 firstSection: ?*BinaryElfSection,
212};
213
214const BinaryElfOutput = struct {
215 segments: std.ArrayListUnmanaged(*BinaryElfSegment),
216 sections: std.ArrayListUnmanaged(*BinaryElfSection),
217 allocator: Allocator,
218 shstrtab: ?[]const u8,
219
220 const Self = @This();
221
222 pub fn deinit(self: *Self) void {
223 if (self.shstrtab) |shstrtab|
224 self.allocator.free(shstrtab);
225 self.sections.deinit(self.allocator);
226 self.segments.deinit(self.allocator);
227 }
228
229 pub fn parse(allocator: Allocator, elf_file: File, elf_hdr: elf.Header) !Self {
230 var self: Self = .{
231 .segments = .{},
232 .sections = .{},
233 .allocator = allocator,
234 .shstrtab = null,
235 };
236 errdefer self.sections.deinit(allocator);
237 errdefer self.segments.deinit(allocator);
238
239 self.shstrtab = blk: {
240 if (elf_hdr.shstrndx >= elf_hdr.shnum) break :blk null;
241
242 var section_headers = elf_hdr.section_header_iterator(&elf_file);
243
244 var section_counter: usize = 0;
245 while (section_counter < elf_hdr.shstrndx) : (section_counter += 1) {
246 _ = (try section_headers.next()).?;
247 }
248
249 const shstrtab_shdr = (try section_headers.next()).?;
250
251 const buffer = try allocator.alloc(u8, @intCast(usize, shstrtab_shdr.sh_size));
252 errdefer allocator.free(buffer);
253
254 const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset);
255 if (num_read != buffer.len) return error.EndOfStream;
256
257 break :blk buffer;
258 };
259
260 errdefer if (self.shstrtab) |shstrtab| allocator.free(shstrtab);
261
262 var section_headers = elf_hdr.section_header_iterator(&elf_file);
263 while (try section_headers.next()) |section| {
264 if (sectionValidForOutput(section)) {
265 const newSection = try allocator.create(BinaryElfSection);
266
267 newSection.binaryOffset = 0;
268 newSection.elfOffset = section.sh_offset;
269 newSection.fileSize = @intCast(usize, section.sh_size);
270 newSection.segment = null;
271
272 newSection.name = if (self.shstrtab) |shstrtab|
273 std.mem.span(@ptrCast([*:0]const u8, &shstrtab[section.sh_name]))
274 else
275 null;
276
277 try self.sections.append(allocator, newSection);
278 }
279 }
280
281 var program_headers = elf_hdr.program_header_iterator(&elf_file);
282 while (try program_headers.next()) |phdr| {
283 if (phdr.p_type == elf.PT_LOAD) {
284 const newSegment = try allocator.create(BinaryElfSegment);
285
286 newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr;
287 newSegment.virtualAddress = phdr.p_vaddr;
288 newSegment.fileSize = @intCast(usize, phdr.p_filesz);
289 newSegment.elfOffset = phdr.p_offset;
290 newSegment.binaryOffset = 0;
291 newSegment.firstSection = null;
292
293 for (self.sections.items) |section| {
294 if (sectionWithinSegment(section, phdr)) {
295 if (section.segment) |sectionSegment| {
296 if (sectionSegment.elfOffset > newSegment.elfOffset) {
297 section.segment = newSegment;
298 }
299 } else {
300 section.segment = newSegment;
301 }
302
303 if (newSegment.firstSection == null) {
304 newSegment.firstSection = section;
305 }
306 }
307 }
308
309 try self.segments.append(allocator, newSegment);
310 }
311 }
312
313 std.sort.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
314
315 for (self.segments.items) |firstSegment, i| {
316 if (firstSegment.firstSection) |firstSection| {
317 const diff = firstSection.elfOffset - firstSegment.elfOffset;
318
319 firstSegment.elfOffset += diff;
320 firstSegment.fileSize += diff;
321 firstSegment.physicalAddress += diff;
322
323 const basePhysicalAddress = firstSegment.physicalAddress;
324
325 for (self.segments.items[i + 1 ..]) |segment| {
326 segment.binaryOffset = segment.physicalAddress - basePhysicalAddress;
327 }
328 break;
329 }
330 }
331
332 for (self.sections.items) |section| {
333 if (section.segment) |segment| {
334 section.binaryOffset = segment.binaryOffset + (section.elfOffset - segment.elfOffset);
335 }
336 }
337
338 std.sort.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
339
340 return self;
341 }
342
343 fn sectionWithinSegment(section: *BinaryElfSection, segment: elf.Elf64_Phdr) bool {
344 return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize);
345 }
346
347 fn sectionValidForOutput(shdr: anytype) bool {
348 return shdr.sh_size > 0 and shdr.sh_type != elf.SHT_NOBITS and
349 ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC);
350 }
351
352 fn segmentSortCompare(context: void, left: *BinaryElfSegment, right: *BinaryElfSegment) bool {
353 _ = context;
354 if (left.physicalAddress < right.physicalAddress) {
355 return true;
356 }
357 if (left.physicalAddress > right.physicalAddress) {
358 return false;
359 }
360 return false;
361 }
362
363 fn sectionSortCompare(context: void, left: *BinaryElfSection, right: *BinaryElfSection) bool {
364 _ = context;
365 return left.binaryOffset < right.binaryOffset;
366 }
367};
368
369fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSection) !void {
370 try out_file.writeFileAll(elf_file, .{
371 .in_offset = section.elfOffset,
372 .in_len = section.fileSize,
373 });
374}
375
376const HexWriter = struct {
377 prev_addr: ?u32 = null,
378 out_file: File,
379
380 /// Max data bytes per line of output
381 const MAX_PAYLOAD_LEN: u8 = 16;
382
383 fn addressParts(address: u16) [2]u8 {
384 const msb = @truncate(u8, address >> 8);
385 const lsb = @truncate(u8, address);
386 return [2]u8{ msb, lsb };
387 }
388
389 const Record = struct {
390 const Type = enum(u8) {
391 Data = 0,
392 EOF = 1,
393 ExtendedSegmentAddress = 2,
394 ExtendedLinearAddress = 4,
395 };
396
397 address: u16,
398 payload: union(Type) {
399 Data: []const u8,
400 EOF: void,
401 ExtendedSegmentAddress: [2]u8,
402 ExtendedLinearAddress: [2]u8,
403 },
404
405 fn EOF() Record {
406 return Record{
407 .address = 0,
408 .payload = .EOF,
409 };
410 }
411
412 fn Data(address: u32, data: []const u8) Record {
413 return Record{
414 .address = @intCast(u16, address % 0x10000),
415 .payload = .{ .Data = data },
416 };
417 }
418
419 fn Address(address: u32) Record {
420 std.debug.assert(address > 0xFFFF);
421 const segment = @intCast(u16, address / 0x10000);
422 if (address > 0xFFFFF) {
423 return Record{
424 .address = 0,
425 .payload = .{ .ExtendedLinearAddress = addressParts(segment) },
426 };
427 } else {
428 return Record{
429 .address = 0,
430 .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) },
431 };
432 }
433 }
434
435 fn getPayloadBytes(self: Record) []const u8 {
436 return switch (self.payload) {
437 .Data => |d| d,
438 .EOF => @as([]const u8, &.{}),
439 .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg,
440 };
441 }
442
443 fn checksum(self: Record) u8 {
444 const payload_bytes = self.getPayloadBytes();
445
446 var sum: u8 = @intCast(u8, payload_bytes.len);
447 const parts = addressParts(self.address);
448 sum +%= parts[0];
449 sum +%= parts[1];
450 sum +%= @enumToInt(self.payload);
451 for (payload_bytes) |byte| {
452 sum +%= byte;
453 }
454 return (sum ^ 0xFF) +% 1;
455 }
456
457 fn write(self: Record, file: File) File.WriteError!void {
458 const linesep = "\r\n";
459 // colon, (length, address, type, payload, checksum) as hex, CRLF
460 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
461 var outbuf: [BUFSIZE]u8 = undefined;
462 const payload_bytes = self.getPayloadBytes();
463 std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
464
465 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
466 @intCast(u8, payload_bytes.len),
467 self.address,
468 @enumToInt(self.payload),
469 std.fmt.fmtSliceHexUpper(payload_bytes),
470 self.checksum(),
471 });
472 try file.writeAll(line);
473 }
474 };
475
476 pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, elf_file: File) !void {
477 var buf: [MAX_PAYLOAD_LEN]u8 = undefined;
478 var bytes_read: usize = 0;
479 while (bytes_read < segment.fileSize) {
480 const row_address = @intCast(u32, segment.physicalAddress + bytes_read);
481
482 const remaining = segment.fileSize - bytes_read;
483 const to_read = @intCast(usize, @min(remaining, MAX_PAYLOAD_LEN));
484 const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read);
485 if (did_read < to_read) return error.UnexpectedEOF;
486
487 try self.writeDataRow(row_address, buf[0..did_read]);
488
489 bytes_read += did_read;
490 }
491 }
492
493 fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) File.WriteError!void {
494 const record = Record.Data(address, data);
495 if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) {
496 try Record.Address(address).write(self.out_file);
497 }
498 try record.write(self.out_file);
499 self.prev_addr = @intCast(u32, record.address + data.len);
500 }
501
502 fn writeEOF(self: HexWriter) File.WriteError!void {
503 try Record.EOF().write(self.out_file);
504 }
505};
506
507fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
508 const max_address = std.math.maxInt(u32);
509 for (segments) |segment| {
510 if (segment.fileSize > max_address or
511 segment.physicalAddress > max_address - segment.fileSize) return false;
512 }
513 return true;
514}
515
516fn padFile(f: File, opt_size: ?u64) !void {
517 const size = opt_size orelse return;
518 try f.setEndPos(size);
519}
520
521test "containsValidAddressRange" {
522 var segment = BinaryElfSegment{
523 .physicalAddress = 0,
524 .virtualAddress = 0,
525 .elfOffset = 0,
526 .binaryOffset = 0,
527 .fileSize = 0,
528 .firstSection = null,
529 };
530 var buf: [1]*BinaryElfSegment = .{&segment};
531
532 // segment too big
533 segment.fileSize = std.math.maxInt(u32) + 1;
534 try std.testing.expect(!containsValidAddressRange(&buf));
535
536 // start address too big
537 segment.physicalAddress = std.math.maxInt(u32) + 1;
538 segment.fileSize = 2;
539 try std.testing.expect(!containsValidAddressRange(&buf));
540
541 // max address too big
542 segment.physicalAddress = std.math.maxInt(u32) - 1;
543 segment.fileSize = 2;
544 try std.testing.expect(!containsValidAddressRange(&buf));
545
546 // is ok
547 segment.physicalAddress = std.math.maxInt(u32) - 1;
548 segment.fileSize = 1;
549 try std.testing.expect(containsValidAddressRange(&buf));
550}
src/print_targets.zig+1-1
......@@ -3,7 +3,7 @@ const fs = std.fs;
33const io = std.io;
44const mem = std.mem;
55const meta = std.meta;
6const Allocator = mem.Allocator;
6const Allocator = std.mem.Allocator;
77const Target = std.Target;
88const target = @import("target.zig");
99const assert = std.debug.assert;