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