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,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
1const std = @import("std");4const std = @import("std");
5const InstallRawStep = @This();
26
3const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
4const ArenaAllocator = std.heap.ArenaAllocator;8const ArenaAllocator = std.heap.ArenaAllocator;
...@@ -13,406 +17,6 @@ const fs = std.fs;...@@ -13,406 +17,6 @@ const fs = std.fs;
13const io = std.io;17const io = std.io;
14const sort = std.sort;18const 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
416pub const base_id = .install_raw;20pub const base_id = .install_raw;
41721
418pub const RawFormat = enum {22pub const RawFormat = enum {
...@@ -428,18 +32,11 @@ dest_filename: []const u8,...@@ -428,18 +32,11 @@ dest_filename: []const u8,
428options: CreateOptions,32options: CreateOptions,
429output_file: std.build.GeneratedFile,33output_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
438pub const CreateOptions = struct {35pub const CreateOptions = struct {
439 format: ?RawFormat = null,36 format: ?RawFormat = null,
440 dest_dir: ?InstallDir = null,37 dest_dir: ?InstallDir = null,
441 only_section_name: ?[]const u8 = null,38 only_section: ?[]const u8 = null,
442 pad_to_size: ?usize = null,39 pad_to: ?u64 = null,
443};40};
44441
445pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8, options: CreateOptions) *InstallRawStep {42pub 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 {...@@ -470,60 +67,40 @@ pub fn getOutputSource(self: *const InstallRawStep) std.build.FileSource {
47067
471fn make(step: *Step) !void {68fn make(step: *Step) !void {
472 const self = @fieldParentPtr(InstallRawStep, "step", step);69 const self = @fieldParentPtr(InstallRawStep, "step", step);
473 const builder = self.builder;70 const b = self.builder;
47471
475 if (self.artifact.target.getObjectFormat() != .elf) {72 if (self.artifact.target.getObjectFormat() != .elf) {
476 std.debug.print("InstallRawStep only works with ELF format.\n", .{});73 std.debug.print("InstallRawStep only works with ELF format.\n", .{});
477 return error.InvalidObjectFormat;74 return error.InvalidObjectFormat;
478 }75 }
47976
480 const full_src_path = self.artifact.getOutputSource().getPath(builder);77 const full_src_path = self.artifact.getOutputSource().getPath(b);
481 const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename);78 const full_dest_path = b.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);
485 self.output_file.path = full_dest_path;79 self.output_file.path = full_dest_path;
486}
48780
488test {81 fs.cwd().makePath(b.getInstallPath(self.dest_dir, "")) catch unreachable;
489 std.testing.refAllDecls(InstallRawStep);
490}
49182
492test "Detect format from filename" {83 var argv_list = std.ArrayList([]const u8).init(b.allocator);
493 try std.testing.expectEqual(RawFormat.hex, detectFormat("foo.hex"));84 try argv_list.appendSlice(&.{ b.zig_exe, "objcopy" });
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}
49985
500test "containsValidAddressRange" {86 if (self.options.only_section) |only_section| {
501 var segment = BinaryElfSegment{87 try argv_list.appendSlice(&.{ "-j", only_section });
502 .physicalAddress = 0,88 }
503 .virtualAddress = 0,89 if (self.options.pad_to) |pad_to| {
504 .elfOffset = 0,90 try argv_list.appendSlice(&.{
505 .binaryOffset = 0,91 "--pad-to",
506 .fileSize = 0,92 b.fmt("{d}", .{pad_to}),
507 .firstSection = null,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" }),
508 };98 };
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 big100 try argv_list.appendSlice(&.{ full_src_path, full_dest_path });
521 segment.physicalAddress = std.math.maxInt(u32) - 1;101 _ = try self.builder.execFromStep(argv_list.items, &self.step);
522 segment.fileSize = 2;102}
523 try std.testing.expect(!containsValidAddressRange(&buf));
524103
525 // is ok104test {
526 segment.physicalAddress = std.math.maxInt(u32) - 1;105 std.testing.refAllDecls(InstallRawStep);
527 segment.fileSize = 1;
528 try std.testing.expect(containsValidAddressRange(&buf));
529}106}
src/main.zig+3
...@@ -85,6 +85,7 @@ const normal_usage =...@@ -85,6 +85,7 @@ const normal_usage =
85 \\ dlltool Use Zig as a drop-in dlltool.exe85 \\ dlltool Use Zig as a drop-in dlltool.exe
86 \\ lib Use Zig as a drop-in lib.exe86 \\ lib Use Zig as a drop-in lib.exe
87 \\ ranlib Use Zig as a drop-in ranlib87 \\ ranlib Use Zig as a drop-in ranlib
88 \\ objcopy Use Zig as a drop-in objcopy
88 \\89 \\
89 \\ env Print lib path, std path, cache directory, and version90 \\ env Print lib path, std path, cache directory, and version
90 \\ help Print this help and exit91 \\ help Print this help and exit
...@@ -286,6 +287,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -286,6 +287,8 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
286 return cmdBuild(gpa, arena, cmd_args);287 return cmdBuild(gpa, arena, cmd_args);
287 } else if (mem.eql(u8, cmd, "fmt")) {288 } else if (mem.eql(u8, cmd, "fmt")) {
288 return cmdFmt(gpa, arena, cmd_args);289 return cmdFmt(gpa, arena, cmd_args);
290 } else if (mem.eql(u8, cmd, "objcopy")) {
291 return @import("objcopy.zig").cmdObjCopy(gpa, arena, cmd_args);
289 } else if (mem.eql(u8, cmd, "libc")) {292 } else if (mem.eql(u8, cmd, "libc")) {
290 return cmdLibC(gpa, cmd_args);293 return cmdLibC(gpa, cmd_args);
291 } else if (mem.eql(u8, cmd, "init-exe")) {294 } 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;...@@ -3,7 +3,7 @@ const fs = std.fs;
3const io = std.io;3const io = std.io;
4const mem = std.mem;4const mem = std.mem;
5const meta = std.meta;5const meta = std.meta;
6const Allocator = mem.Allocator;6const Allocator = std.mem.Allocator;
7const Target = std.Target;7const Target = std.Target;
8const target = @import("target.zig");8const target = @import("target.zig");
9const assert = std.debug.assert;9const assert = std.debug.assert;