zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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 }