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