master
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5const elf = std.elf;
6const Allocator = std.mem.Allocator;
7const File = std.fs.File;
8const assert = std.debug.assert;
9
10const fatal = std.process.fatal;
11const Server = std.zig.Server;
12
13var stdin_buffer: [1024]u8 = undefined;
14var stdout_buffer: [1024]u8 = undefined;
15
16var input_buffer: [1024]u8 = undefined;
17var output_buffer: [1024]u8 = undefined;
18
19pub fn main() !void {
20 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
21 defer arena_instance.deinit();
22 const arena = arena_instance.allocator();
23
24 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
25 const gpa = general_purpose_allocator.allocator();
26
27 const args = try std.process.argsAlloc(arena);
28 return cmdObjCopy(gpa, arena, args[1..]);
29}
30
31fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
32 var i: usize = 0;
33 var opt_out_fmt: ?std.Target.ObjectFormat = null;
34 var opt_input: ?[]const u8 = null;
35 var opt_output: ?[]const u8 = null;
36 var opt_extract: ?[]const u8 = null;
37 var opt_add_debuglink: ?[]const u8 = null;
38 var only_section: ?[]const u8 = null;
39 var pad_to: ?u64 = null;
40 var strip_all: bool = false;
41 var strip_debug: bool = false;
42 var only_keep_debug: bool = false;
43 var compress_debug_sections: bool = false;
44 var listen = false;
45 var add_section: ?AddSection = null;
46 var set_section_alignment: ?SetSectionAlignment = null;
47 var set_section_flags: ?SetSectionFlags = null;
48 while (i < args.len) : (i += 1) {
49 const arg = args[i];
50 if (!mem.startsWith(u8, arg, "-")) {
51 if (opt_input == null) {
52 opt_input = arg;
53 } else if (opt_output == null) {
54 opt_output = arg;
55 } else {
56 fatal("unexpected positional argument: '{s}'", .{arg});
57 }
58 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
59 return std.fs.File.stdout().writeAll(usage);
60 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {
61 i += 1;
62 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
63 const next_arg = args[i];
64 if (mem.eql(u8, next_arg, "binary")) {
65 opt_out_fmt = .raw;
66 } else {
67 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
68 fatal("invalid output format: '{s}'", .{next_arg});
69 }
70 } else if (mem.startsWith(u8, arg, "--output-target=")) {
71 const next_arg = arg["--output-target=".len..];
72 if (mem.eql(u8, next_arg, "binary")) {
73 opt_out_fmt = .raw;
74 } else {
75 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
76 fatal("invalid output format: '{s}'", .{next_arg});
77 }
78 } else if (mem.eql(u8, arg, "-j") or mem.eql(u8, arg, "--only-section")) {
79 i += 1;
80 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
81 only_section = args[i];
82 } else if (mem.eql(u8, arg, "--listen=-")) {
83 listen = true;
84 } else if (mem.startsWith(u8, arg, "--only-section=")) {
85 only_section = arg["--only-section=".len..];
86 } else if (mem.eql(u8, arg, "--pad-to")) {
87 i += 1;
88 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
89 pad_to = std.fmt.parseInt(u64, args[i], 0) catch |err| {
90 fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });
91 };
92 } else if (mem.eql(u8, arg, "-g") or mem.eql(u8, arg, "--strip-debug")) {
93 strip_debug = true;
94 } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-all")) {
95 strip_all = true;
96 } else if (mem.eql(u8, arg, "--only-keep-debug")) {
97 only_keep_debug = true;
98 } else if (mem.eql(u8, arg, "--compress-debug-sections")) {
99 compress_debug_sections = true;
100 } else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {
101 opt_add_debuglink = arg["--add-gnu-debuglink=".len..];
102 } else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {
103 i += 1;
104 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
105 opt_add_debuglink = args[i];
106 } else if (mem.startsWith(u8, arg, "--extract-to=")) {
107 opt_extract = arg["--extract-to=".len..];
108 } else if (mem.eql(u8, arg, "--extract-to")) {
109 i += 1;
110 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
111 opt_extract = args[i];
112 } else if (mem.eql(u8, arg, "--set-section-alignment")) {
113 i += 1;
114 if (i >= args.len) fatal("expected section name and alignment arguments after '{s}'", .{arg});
115
116 if (splitOption(args[i])) |split| {
117 const alignment = std.fmt.parseInt(u32, split.second, 10) catch |err| {
118 fatal("unable to parse alignment number: '{s}': {s}", .{ split.second, @errorName(err) });
119 };
120 if (!std.math.isPowerOfTwo(alignment)) fatal("alignment must be a power of two", .{});
121 set_section_alignment = .{ .section_name = split.first, .alignment = alignment };
122 } else {
123 fatal("unrecognized argument: '{s}', expecting <name>=<alignment>", .{args[i]});
124 }
125 } else if (mem.eql(u8, arg, "--set-section-flags")) {
126 i += 1;
127 if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg});
128
129 if (splitOption(args[i])) |split| {
130 set_section_flags = .{ .section_name = split.first, .flags = parseSectionFlags(split.second) };
131 } else {
132 fatal("unrecognized argument: '{s}', expecting <name>=<flags>", .{args[i]});
133 }
134 } else if (mem.eql(u8, arg, "--add-section")) {
135 i += 1;
136 if (i >= args.len) fatal("expected section name and filename arguments after '{s}'", .{arg});
137
138 if (splitOption(args[i])) |split| {
139 add_section = .{ .section_name = split.first, .file_path = split.second };
140 } else {
141 fatal("unrecognized argument: '{s}', expecting <name>=<file>", .{args[i]});
142 }
143 } else {
144 fatal("unrecognized argument: '{s}'", .{arg});
145 }
146 }
147 const input = opt_input orelse fatal("expected input parameter", .{});
148 const output = opt_output orelse fatal("expected output parameter", .{});
149
150 var threaded: std.Io.Threaded = .init(gpa);
151 defer threaded.deinit();
152 const io = threaded.io();
153
154 const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
155 defer input_file.close();
156
157 const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err });
158
159 var in: File.Reader = .initSize(input_file.adaptToNewApi(), io, &input_buffer, stat.size);
160
161 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {
162 error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }),
163 else => |e| fatal("invalid elf file: {t}", .{e}),
164 };
165
166 const in_ofmt = .elf;
167
168 const out_fmt: std.Target.ObjectFormat = opt_out_fmt orelse ofmt: {
169 if (mem.endsWith(u8, output, ".hex") or std.mem.endsWith(u8, output, ".ihex")) {
170 break :ofmt .hex;
171 } else if (mem.endsWith(u8, output, ".bin")) {
172 break :ofmt .raw;
173 } else if (mem.endsWith(u8, output, ".elf")) {
174 break :ofmt .elf;
175 } else {
176 break :ofmt in_ofmt;
177 }
178 };
179
180 const mode = if (out_fmt != .elf or only_keep_debug) fs.File.default_mode else stat.mode;
181
182 var output_file = try fs.cwd().createFile(output, .{ .mode = mode });
183 defer output_file.close();
184
185 var out = output_file.writer(&output_buffer);
186
187 switch (out_fmt) {
188 .hex, .raw => {
189 if (strip_debug or strip_all or only_keep_debug)
190 fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{});
191 if (opt_extract != null)
192 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
193 if (add_section != null)
194 fatal("zig objcopy: ELF to RAW or HEX copying does not support --add-section", .{});
195 if (set_section_alignment != null)
196 fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_alignment", .{});
197 if (set_section_flags != null)
198 fatal("zig objcopy: ELF to RAW or HEX copying does not support --set_section_flags", .{});
199
200 try emitElf(arena, &in, &out, elf_hdr, .{
201 .ofmt = out_fmt,
202 .only_section = only_section,
203 .pad_to = pad_to,
204 });
205 },
206 .elf => {
207 if (elf_hdr.endian != builtin.target.cpu.arch.endian())
208 fatal("zig objcopy: ELF to ELF copying only supports native endian", .{});
209 if (elf_hdr.phoff == 0) // no program header
210 fatal("zig objcopy: ELF to ELF copying only supports programs", .{});
211 if (only_section) |_|
212 fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});
213 if (pad_to) |_|
214 fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});
215
216 fatal("unimplemented", .{});
217 },
218 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
219 }
220
221 try out.end();
222
223 if (listen) {
224 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
225 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
226 var server = try Server.init(.{
227 .in = &stdin_reader.interface,
228 .out = &stdout_writer.interface,
229 .zig_version = builtin.zig_version_string,
230 });
231
232 var seen_update = false;
233 while (true) {
234 const hdr = try server.receiveMessage();
235 switch (hdr.tag) {
236 .exit => {
237 return std.process.cleanExit();
238 },
239 .update => {
240 if (seen_update) fatal("zig objcopy only supports 1 update for now", .{});
241 seen_update = true;
242
243 // The build system already knows what the output is at this point, we
244 // only need to communicate that the process has finished.
245 // Use the empty error bundle to indicate that the update is done.
246 try server.serveErrorBundle(std.zig.ErrorBundle.empty);
247 },
248 else => fatal("unsupported message: {s}", .{@tagName(hdr.tag)}),
249 }
250 }
251 }
252 return std.process.cleanExit();
253}
254
255const usage =
256 \\Usage: zig objcopy [options] input output
257 \\
258 \\Options:
259 \\ -h, --help Print this help and exit
260 \\ --output-target=<value> Format of the output file
261 \\ -O <value> Alias for --output-target
262 \\ --only-section=<section> Remove all but <section>
263 \\ -j <value> Alias for --only-section
264 \\ --pad-to <addr> Pad the last section up to address <addr>
265 \\ --strip-debug, -g Remove all debug sections from the output.
266 \\ --strip-all, -S Remove all debug sections and symbol table from the output.
267 \\ --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.
268 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
269 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
270 \\ --compress-debug-sections Compress DWARF debug sections with zlib
271 \\ --set-section-alignment <name>=<align> Set alignment of section <name> to <align> bytes. Must be a power of two.
272 \\ --set-section-flags <name>=<file> Set flags of section <name> to <flags> represented as a comma separated set of flags.
273 \\ --add-section <name>=<file> Add file content from <file> with the a new section named <name>.
274 \\
275;
276
277pub const EmitRawElfOptions = struct {
278 ofmt: std.Target.ObjectFormat,
279 only_section: ?[]const u8 = null,
280 pad_to: ?u64 = null,
281 add_section: ?AddSection = null,
282 set_section_alignment: ?SetSectionAlignment = null,
283 set_section_flags: ?SetSectionFlags = null,
284};
285
286const AddSection = struct {
287 section_name: []const u8,
288 file_path: []const u8,
289};
290
291const SetSectionAlignment = struct {
292 section_name: []const u8,
293 alignment: u32,
294};
295
296const SetSectionFlags = struct {
297 section_name: []const u8,
298 flags: SectionFlags,
299};
300
301fn emitElf(
302 arena: Allocator,
303 in: *File.Reader,
304 out: *File.Writer,
305 elf_hdr: elf.Header,
306 options: EmitRawElfOptions,
307) !void {
308 var binary_elf_output = try BinaryElfOutput.parse(arena, in, elf_hdr);
309 defer binary_elf_output.deinit();
310
311 if (options.ofmt == .elf) {
312 fatal("zig objcopy: ELF to ELF copying is not implemented yet", .{});
313 }
314
315 if (options.only_section) |target_name| {
316 switch (options.ofmt) {
317 .hex => fatal("zig objcopy: hex format with sections is not implemented yet", .{}),
318 .raw => {
319 for (binary_elf_output.sections.items) |section| {
320 if (section.name) |curr_name| {
321 if (!std.mem.eql(u8, curr_name, target_name))
322 continue;
323 } else {
324 continue;
325 }
326
327 try writeBinaryElfSection(in, out, section);
328 try padFile(out, options.pad_to);
329 return;
330 }
331 },
332 else => unreachable,
333 }
334
335 return error.SectionNotFound;
336 }
337
338 switch (options.ofmt) {
339 .raw => {
340 for (binary_elf_output.sections.items) |section| {
341 try out.seekTo(section.binaryOffset);
342 try writeBinaryElfSection(in, out, section);
343 }
344 try padFile(out, options.pad_to);
345 },
346 .hex => {
347 if (binary_elf_output.segments.items.len == 0) return;
348 if (!containsValidAddressRange(binary_elf_output.segments.items)) {
349 return error.InvalidHexfileAddressRange;
350 }
351
352 var hex_writer = HexWriter{ .out = out };
353 for (binary_elf_output.segments.items) |segment| {
354 try hex_writer.writeSegment(segment, in);
355 }
356 if (options.pad_to) |_| {
357 // Padding to a size in hex files isn't applicable
358 return error.InvalidArgument;
359 }
360 try hex_writer.writeEof();
361 },
362 else => unreachable,
363 }
364}
365
366const BinaryElfSection = struct {
367 elfOffset: u64,
368 binaryOffset: u64,
369 fileSize: usize,
370 name: ?[]const u8,
371 segment: ?*BinaryElfSegment,
372};
373
374const BinaryElfSegment = struct {
375 physicalAddress: u64,
376 virtualAddress: u64,
377 elfOffset: u64,
378 binaryOffset: u64,
379 fileSize: u64,
380 firstSection: ?*BinaryElfSection,
381};
382
383const BinaryElfOutput = struct {
384 segments: std.ArrayList(*BinaryElfSegment),
385 sections: std.ArrayList(*BinaryElfSection),
386 allocator: Allocator,
387 shstrtab: ?[]const u8,
388
389 const Self = @This();
390
391 pub fn deinit(self: *Self) void {
392 if (self.shstrtab) |shstrtab|
393 self.allocator.free(shstrtab);
394 self.sections.deinit(self.allocator);
395 self.segments.deinit(self.allocator);
396 }
397
398 pub fn parse(allocator: Allocator, in: *File.Reader, elf_hdr: elf.Header) !Self {
399 var self: Self = .{
400 .segments = .{},
401 .sections = .{},
402 .allocator = allocator,
403 .shstrtab = null,
404 };
405 errdefer self.sections.deinit(allocator);
406 errdefer self.segments.deinit(allocator);
407
408 self.shstrtab = blk: {
409 if (elf_hdr.shstrndx >= elf_hdr.shnum) break :blk null;
410
411 var section_headers = elf_hdr.iterateSectionHeaders(in);
412
413 var section_counter: usize = 0;
414 while (section_counter < elf_hdr.shstrndx) : (section_counter += 1) {
415 _ = (try section_headers.next()).?;
416 }
417
418 const shstrtab_shdr = (try section_headers.next()).?;
419
420 try in.seekTo(shstrtab_shdr.sh_offset);
421 break :blk try in.interface.readAlloc(allocator, shstrtab_shdr.sh_size);
422 };
423
424 errdefer if (self.shstrtab) |shstrtab| allocator.free(shstrtab);
425
426 var section_headers = elf_hdr.iterateSectionHeaders(in);
427 while (try section_headers.next()) |section| {
428 if (sectionValidForOutput(section)) {
429 const newSection = try allocator.create(BinaryElfSection);
430
431 newSection.binaryOffset = 0;
432 newSection.elfOffset = section.sh_offset;
433 newSection.fileSize = @intCast(section.sh_size);
434 newSection.segment = null;
435
436 newSection.name = if (self.shstrtab) |shstrtab|
437 std.mem.span(@as([*:0]const u8, @ptrCast(&shstrtab[section.sh_name])))
438 else
439 null;
440
441 try self.sections.append(allocator, newSection);
442 }
443 }
444
445 var program_headers = elf_hdr.iterateProgramHeaders(in);
446 while (try program_headers.next()) |phdr| {
447 if (phdr.p_type == elf.PT_LOAD) {
448 const newSegment = try allocator.create(BinaryElfSegment);
449
450 newSegment.physicalAddress = phdr.p_paddr;
451 newSegment.virtualAddress = phdr.p_vaddr;
452 newSegment.fileSize = @intCast(phdr.p_filesz);
453 newSegment.elfOffset = phdr.p_offset;
454 newSegment.binaryOffset = 0;
455 newSegment.firstSection = null;
456
457 for (self.sections.items) |section| {
458 if (sectionWithinSegment(section, phdr)) {
459 if (section.segment) |sectionSegment| {
460 if (sectionSegment.elfOffset > newSegment.elfOffset) {
461 section.segment = newSegment;
462 }
463 } else {
464 section.segment = newSegment;
465 }
466
467 if (newSegment.firstSection == null) {
468 newSegment.firstSection = section;
469 }
470 }
471 }
472
473 try self.segments.append(allocator, newSegment);
474 }
475 }
476
477 mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
478
479 for (self.segments.items, 0..) |firstSegment, i| {
480 if (firstSegment.firstSection) |firstSection| {
481 const diff = firstSection.elfOffset - firstSegment.elfOffset;
482
483 firstSegment.elfOffset += diff;
484 firstSegment.fileSize += diff;
485 firstSegment.physicalAddress += diff;
486
487 const basePhysicalAddress = firstSegment.physicalAddress;
488
489 for (self.segments.items[i + 1 ..]) |segment| {
490 segment.binaryOffset = segment.physicalAddress - basePhysicalAddress;
491 }
492 break;
493 }
494 }
495
496 for (self.sections.items) |section| {
497 if (section.segment) |segment| {
498 section.binaryOffset = segment.binaryOffset + (section.elfOffset - segment.elfOffset);
499 }
500 }
501
502 mem.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
503
504 return self;
505 }
506
507 fn sectionWithinSegment(section: *BinaryElfSection, segment: elf.Elf64_Phdr) bool {
508 return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize);
509 }
510
511 fn sectionValidForOutput(shdr: anytype) bool {
512 return shdr.sh_type != elf.SHT_NOBITS and
513 ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC);
514 }
515
516 fn segmentSortCompare(context: void, left: *BinaryElfSegment, right: *BinaryElfSegment) bool {
517 _ = context;
518 if (left.physicalAddress < right.physicalAddress) {
519 return true;
520 }
521 if (left.physicalAddress > right.physicalAddress) {
522 return false;
523 }
524 return false;
525 }
526
527 fn sectionSortCompare(context: void, left: *BinaryElfSection, right: *BinaryElfSection) bool {
528 _ = context;
529 return left.binaryOffset < right.binaryOffset;
530 }
531};
532
533fn writeBinaryElfSection(in: *File.Reader, out: *File.Writer, section: *BinaryElfSection) !void {
534 try in.seekTo(section.elfOffset);
535 _ = try out.interface.sendFileAll(in, .limited(section.fileSize));
536}
537
538const HexWriter = struct {
539 prev_addr: ?u32 = null,
540 out: *File.Writer,
541
542 /// Max data bytes per line of output
543 const max_payload_len: u8 = 16;
544
545 fn addressParts(address: u16) [2]u8 {
546 const msb: u8 = @truncate(address >> 8);
547 const lsb: u8 = @truncate(address);
548 return [2]u8{ msb, lsb };
549 }
550
551 const Record = struct {
552 const Type = enum(u8) {
553 Data = 0,
554 EOF = 1,
555 ExtendedSegmentAddress = 2,
556 ExtendedLinearAddress = 4,
557 };
558
559 address: u16,
560 payload: union(Type) {
561 Data: []const u8,
562 EOF: void,
563 ExtendedSegmentAddress: [2]u8,
564 ExtendedLinearAddress: [2]u8,
565 },
566
567 fn EOF() Record {
568 return Record{
569 .address = 0,
570 .payload = .EOF,
571 };
572 }
573
574 fn Data(address: u32, data: []const u8) Record {
575 return Record{
576 .address = @intCast(address % 0x10000),
577 .payload = .{ .Data = data },
578 };
579 }
580
581 fn Address(address: u32) Record {
582 assert(address > 0xFFFF);
583 const segment: u16 = @intCast(address / 0x10000);
584 if (address > 0xFFFFF) {
585 return Record{
586 .address = 0,
587 .payload = .{ .ExtendedLinearAddress = addressParts(segment) },
588 };
589 } else {
590 return Record{
591 .address = 0,
592 .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) },
593 };
594 }
595 }
596
597 fn getPayloadBytes(self: *const Record) []const u8 {
598 return switch (self.payload) {
599 .Data => |d| d,
600 .EOF => @as([]const u8, &.{}),
601 .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg,
602 };
603 }
604
605 fn checksum(self: Record) u8 {
606 const payload_bytes = self.getPayloadBytes();
607
608 var sum: u8 = @intCast(payload_bytes.len);
609 const parts = addressParts(self.address);
610 sum +%= parts[0];
611 sum +%= parts[1];
612 sum +%= @intFromEnum(self.payload);
613 for (payload_bytes) |byte| {
614 sum +%= byte;
615 }
616 return (sum ^ 0xFF) +% 1;
617 }
618
619 fn write(self: Record, out: *File.Writer) !void {
620 const linesep = "\r\n";
621 // colon, (length, address, type, payload, checksum) as hex, CRLF
622 const BUFSIZE = 1 + (1 + 2 + 1 + max_payload_len + 1) * 2 + linesep.len;
623 var outbuf: [BUFSIZE]u8 = undefined;
624 const payload_bytes = self.getPayloadBytes();
625 assert(payload_bytes.len <= max_payload_len);
626
627 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3X}{4X:0>2}" ++ linesep, .{
628 @as(u8, @intCast(payload_bytes.len)),
629 self.address,
630 @intFromEnum(self.payload),
631 payload_bytes,
632 self.checksum(),
633 });
634 try out.interface.writeAll(line);
635 }
636 };
637
638 pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, in: *File.Reader) !void {
639 var buf: [max_payload_len]u8 = undefined;
640 var bytes_read: usize = 0;
641 while (bytes_read < segment.fileSize) {
642 const row_address: u32 = @intCast(segment.physicalAddress + bytes_read);
643
644 const remaining = segment.fileSize - bytes_read;
645 const dest = buf[0..@min(remaining, max_payload_len)];
646 try in.seekTo(segment.elfOffset + bytes_read);
647 try in.interface.readSliceAll(dest);
648 try self.writeDataRow(row_address, dest);
649
650 bytes_read += dest.len;
651 }
652 }
653
654 fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) !void {
655 const record = Record.Data(address, data);
656 if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) {
657 try Record.Address(address).write(self.out);
658 }
659 try record.write(self.out);
660 self.prev_addr = @intCast(record.address + data.len);
661 }
662
663 fn writeEof(self: HexWriter) !void {
664 try Record.EOF().write(self.out);
665 }
666};
667
668fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
669 const max_address = std.math.maxInt(u32);
670 for (segments) |segment| {
671 if (segment.fileSize > max_address or
672 segment.physicalAddress > max_address - segment.fileSize) return false;
673 }
674 return true;
675}
676
677fn padFile(out: *File.Writer, opt_size: ?u64) !void {
678 const size = opt_size orelse return;
679 try out.file.setEndPos(size);
680}
681
682test "HexWriter.Record.Address has correct payload and checksum" {
683 const record = HexWriter.Record.Address(0x0800_0000);
684 const payload = record.getPayloadBytes();
685 const sum = record.checksum();
686 try std.testing.expect(sum == 0xF2);
687 try std.testing.expect(payload.len == 2);
688 try std.testing.expect(payload[0] == 8);
689 try std.testing.expect(payload[1] == 0);
690}
691
692test "containsValidAddressRange" {
693 var segment = BinaryElfSegment{
694 .physicalAddress = 0,
695 .virtualAddress = 0,
696 .elfOffset = 0,
697 .binaryOffset = 0,
698 .fileSize = 0,
699 .firstSection = null,
700 };
701 var buf: [1]*BinaryElfSegment = .{&segment};
702
703 // segment too big
704 segment.fileSize = std.math.maxInt(u32) + 1;
705 try std.testing.expect(!containsValidAddressRange(&buf));
706
707 // start address too big
708 segment.physicalAddress = std.math.maxInt(u32) + 1;
709 segment.fileSize = 2;
710 try std.testing.expect(!containsValidAddressRange(&buf));
711
712 // max address too big
713 segment.physicalAddress = std.math.maxInt(u32) - 1;
714 segment.fileSize = 2;
715 try std.testing.expect(!containsValidAddressRange(&buf));
716
717 // is ok
718 segment.physicalAddress = std.math.maxInt(u32) - 1;
719 segment.fileSize = 1;
720 try std.testing.expect(containsValidAddressRange(&buf));
721}
722
723const SectionFlags = packed struct {
724 alloc: bool = false,
725 contents: bool = false,
726 load: bool = false,
727 noload: bool = false,
728 readonly: bool = false,
729 code: bool = false,
730 data: bool = false,
731 rom: bool = false,
732 exclude: bool = false,
733 shared: bool = false,
734 debug: bool = false,
735 large: bool = false,
736 merge: bool = false,
737 strings: bool = false,
738};
739
740fn parseSectionFlags(comma_separated_flags: []const u8) SectionFlags {
741 const P = struct {
742 fn parse(flags: *SectionFlags, string: []const u8) void {
743 if (string.len == 0) return;
744
745 if (std.mem.eql(u8, string, "alloc")) {
746 flags.alloc = true;
747 } else if (std.mem.eql(u8, string, "contents")) {
748 flags.contents = true;
749 } else if (std.mem.eql(u8, string, "load")) {
750 flags.load = true;
751 } else if (std.mem.eql(u8, string, "noload")) {
752 flags.noload = true;
753 } else if (std.mem.eql(u8, string, "readonly")) {
754 flags.readonly = true;
755 } else if (std.mem.eql(u8, string, "code")) {
756 flags.code = true;
757 } else if (std.mem.eql(u8, string, "data")) {
758 flags.data = true;
759 } else if (std.mem.eql(u8, string, "rom")) {
760 flags.rom = true;
761 } else if (std.mem.eql(u8, string, "exclude")) {
762 flags.exclude = true;
763 } else if (std.mem.eql(u8, string, "shared")) {
764 flags.shared = true;
765 } else if (std.mem.eql(u8, string, "debug")) {
766 flags.debug = true;
767 } else if (std.mem.eql(u8, string, "large")) {
768 flags.large = true;
769 } else if (std.mem.eql(u8, string, "merge")) {
770 flags.merge = true;
771 } else if (std.mem.eql(u8, string, "strings")) {
772 flags.strings = true;
773 } else {
774 std.log.warn("Skipping unrecognized section flag '{s}'", .{string});
775 }
776 }
777 };
778
779 var flags = SectionFlags{};
780 var offset: usize = 0;
781 for (comma_separated_flags, 0..) |c, i| {
782 if (c == ',') {
783 defer offset = i + 1;
784 const string = comma_separated_flags[offset..i];
785 P.parse(&flags, string);
786 }
787 }
788 P.parse(&flags, comma_separated_flags[offset..]);
789 return flags;
790}
791
792test "Parse section flags" {
793 const F = SectionFlags;
794 try std.testing.expectEqual(F{}, parseSectionFlags(""));
795 try std.testing.expectEqual(F{}, parseSectionFlags(","));
796 try std.testing.expectEqual(F{}, parseSectionFlags("abc"));
797 try std.testing.expectEqual(F{ .alloc = true }, parseSectionFlags("alloc"));
798 try std.testing.expectEqual(F{ .data = true }, parseSectionFlags("data,"));
799 try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code"));
800 try std.testing.expectEqual(F{ .alloc = true, .code = true }, parseSectionFlags("alloc,code,not_supported"));
801}
802
803const SplitResult = struct { first: []const u8, second: []const u8 };
804
805fn splitOption(option: []const u8) ?SplitResult {
806 const separator = '=';
807 if (option.len < 3) return null; // minimum "a=b"
808 for (1..option.len - 1) |i| {
809 if (option[i] == separator) return .{
810 .first = option[0..i],
811 .second = option[i + 1 ..],
812 };
813 }
814 return null;
815}
816
817test "Split option" {
818 {
819 const split = splitOption(".abc=123");
820 try std.testing.expect(split != null);
821 try std.testing.expectEqualStrings(".abc", split.?.first);
822 try std.testing.expectEqualStrings("123", split.?.second);
823 }
824
825 try std.testing.expectEqual(null, splitOption(""));
826 try std.testing.expectEqual(null, splitOption("=abc"));
827 try std.testing.expectEqual(null, splitOption("abc="));
828 try std.testing.expectEqual(null, splitOption("abc"));
829}