authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-02 16:01:29-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-02 21:22:45-08:00
log33de937fd91c64cd65894369cf7d92665a8e582e
tree2395cdb339174596c05cb1ab10d8a59fc24ff8ce
parentaa688567f556f9d24cae25f087adf90d96f6906f

move `zig objcopy` command to be lazily built

part of #19063

3 files changed, 1372 insertions(+), 1358 deletions(-)

lib/compiler/objcopy.zig created+1368
......@@ -0,0 +1,1368 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5const elf = std.elf;
6const Allocator = std.mem.Allocator;
7const File = std.fs.File;
8const assert = std.debug.assert;
9
10const fatal = std.zig.fatal;
11const Server = std.zig.Server;
12
13pub fn main() !void {
14 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
15 defer arena_instance.deinit();
16 const arena = arena_instance.allocator();
17
18 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
19 const gpa = general_purpose_allocator.allocator();
20
21 const args = try std.process.argsAlloc(arena);
22 return cmdObjCopy(gpa, arena, args[1..]);
23}
24
25fn cmdObjCopy(
26 gpa: Allocator,
27 arena: Allocator,
28 args: []const []const u8,
29) !void {
30 var i: usize = 0;
31 var opt_out_fmt: ?std.Target.ObjectFormat = null;
32 var opt_input: ?[]const u8 = null;
33 var opt_output: ?[]const u8 = null;
34 var opt_extract: ?[]const u8 = null;
35 var opt_add_debuglink: ?[]const u8 = null;
36 var only_section: ?[]const u8 = null;
37 var pad_to: ?u64 = null;
38 var strip_all: bool = false;
39 var strip_debug: bool = false;
40 var only_keep_debug: bool = false;
41 var compress_debug_sections: bool = false;
42 var listen = false;
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 std.io.getStdOut().writeAll(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 {
108 fatal("unrecognized argument: '{s}'", .{arg});
109 }
110 }
111 const input = opt_input orelse fatal("expected input parameter", .{});
112 const output = opt_output orelse fatal("expected output parameter", .{});
113
114 var in_file = fs.cwd().openFile(input, .{}) catch |err|
115 fatal("unable to open '{s}': {s}", .{ input, @errorName(err) });
116 defer in_file.close();
117
118 const elf_hdr = std.elf.Header.read(in_file) catch |err| switch (err) {
119 error.InvalidElfMagic => fatal("not an ELF file: '{s}'", .{input}),
120 else => fatal("unable to read '{s}': {s}", .{ input, @errorName(err) }),
121 };
122
123 const in_ofmt = .elf;
124
125 const out_fmt: std.Target.ObjectFormat = opt_out_fmt orelse ofmt: {
126 if (mem.endsWith(u8, output, ".hex") or std.mem.endsWith(u8, output, ".ihex")) {
127 break :ofmt .hex;
128 } else if (mem.endsWith(u8, output, ".bin")) {
129 break :ofmt .raw;
130 } else if (mem.endsWith(u8, output, ".elf")) {
131 break :ofmt .elf;
132 } else {
133 break :ofmt in_ofmt;
134 }
135 };
136
137 const mode = mode: {
138 if (out_fmt != .elf or only_keep_debug)
139 break :mode fs.File.default_mode;
140 if (in_file.stat()) |stat|
141 break :mode stat.mode
142 else |_|
143 break :mode fs.File.default_mode;
144 };
145 var out_file = try fs.cwd().createFile(output, .{ .mode = mode });
146 defer out_file.close();
147
148 switch (out_fmt) {
149 .hex, .raw => {
150 if (strip_debug or strip_all or only_keep_debug)
151 fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{});
152 if (opt_extract != null)
153 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
154
155 try emitElf(arena, in_file, out_file, elf_hdr, .{
156 .ofmt = out_fmt,
157 .only_section = only_section,
158 .pad_to = pad_to,
159 });
160 },
161 .elf => {
162 if (elf_hdr.endian != builtin.target.cpu.arch.endian())
163 fatal("zig objcopy: ELF to ELF copying only supports native endian", .{});
164 if (elf_hdr.phoff == 0) // no program header
165 fatal("zig objcopy: ELF to ELF copying only supports programs", .{});
166 if (only_section) |_|
167 fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});
168 if (pad_to) |_|
169 fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});
170
171 try stripElf(arena, in_file, out_file, elf_hdr, .{
172 .strip_debug = strip_debug,
173 .strip_all = strip_all,
174 .only_keep_debug = only_keep_debug,
175 .add_debuglink = opt_add_debuglink,
176 .extract_to = opt_extract,
177 .compress_debug = compress_debug_sections,
178 });
179 return std.process.cleanExit();
180 },
181 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
182 }
183
184 if (listen) {
185 var server = try Server.init(.{
186 .gpa = gpa,
187 .in = std.io.getStdIn(),
188 .out = std.io.getStdOut(),
189 .zig_version = builtin.zig_version_string,
190 });
191 defer server.deinit();
192
193 var seen_update = false;
194 while (true) {
195 const hdr = try server.receiveMessage();
196 switch (hdr.tag) {
197 .exit => {
198 return std.process.cleanExit();
199 },
200 .update => {
201 if (seen_update) {
202 std.debug.print("zig objcopy only supports 1 update for now\n", .{});
203 std.process.exit(1);
204 }
205 seen_update = true;
206
207 try server.serveEmitBinPath(output, .{
208 .flags = .{ .cache_hit = false },
209 });
210 },
211 else => {
212 std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)});
213 std.process.exit(1);
214 },
215 }
216 }
217 }
218 return std.process.cleanExit();
219}
220
221const usage =
222 \\Usage: zig objcopy [options] input output
223 \\
224 \\Options:
225 \\ -h, --help Print this help and exit
226 \\ --output-target=<value> Format of the output file
227 \\ -O <value> Alias for --output-target
228 \\ --only-section=<section> Remove all but <section>
229 \\ -j <value> Alias for --only-section
230 \\ --pad-to <addr> Pad the last section up to address <addr>
231 \\ --strip-debug, -g Remove all debug sections from the output.
232 \\ --strip-all, -S Remove all debug sections and symbol table from the output.
233 \\ --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.
234 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
235 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
236 \\ --compress-debug-sections Compress DWARF debug sections with zlib
237 \\
238;
239
240pub const EmitRawElfOptions = struct {
241 ofmt: std.Target.ObjectFormat,
242 only_section: ?[]const u8 = null,
243 pad_to: ?u64 = null,
244};
245
246fn emitElf(
247 arena: Allocator,
248 in_file: File,
249 out_file: File,
250 elf_hdr: elf.Header,
251 options: EmitRawElfOptions,
252) !void {
253 var binary_elf_output = try BinaryElfOutput.parse(arena, in_file, elf_hdr);
254 defer binary_elf_output.deinit();
255
256 if (options.ofmt == .elf) {
257 fatal("zig objcopy: ELF to ELF copying is not implemented yet", .{});
258 }
259
260 if (options.only_section) |target_name| {
261 switch (options.ofmt) {
262 .hex => fatal("zig objcopy: hex format with sections is not implemented yet", .{}),
263 .raw => {
264 for (binary_elf_output.sections.items) |section| {
265 if (section.name) |curr_name| {
266 if (!std.mem.eql(u8, curr_name, target_name))
267 continue;
268 } else {
269 continue;
270 }
271
272 try writeBinaryElfSection(in_file, out_file, section);
273 try padFile(out_file, options.pad_to);
274 return;
275 }
276 },
277 else => unreachable,
278 }
279
280 return error.SectionNotFound;
281 }
282
283 switch (options.ofmt) {
284 .raw => {
285 for (binary_elf_output.sections.items) |section| {
286 try out_file.seekTo(section.binaryOffset);
287 try writeBinaryElfSection(in_file, out_file, section);
288 }
289 try padFile(out_file, options.pad_to);
290 },
291 .hex => {
292 if (binary_elf_output.segments.items.len == 0) return;
293 if (!containsValidAddressRange(binary_elf_output.segments.items)) {
294 return error.InvalidHexfileAddressRange;
295 }
296
297 var hex_writer = HexWriter{ .out_file = out_file };
298 for (binary_elf_output.segments.items) |segment| {
299 try hex_writer.writeSegment(segment, in_file);
300 }
301 if (options.pad_to) |_| {
302 // Padding to a size in hex files isn't applicable
303 return error.InvalidArgument;
304 }
305 try hex_writer.writeEOF();
306 },
307 else => unreachable,
308 }
309}
310
311const BinaryElfSection = struct {
312 elfOffset: u64,
313 binaryOffset: u64,
314 fileSize: usize,
315 name: ?[]const u8,
316 segment: ?*BinaryElfSegment,
317};
318
319const BinaryElfSegment = struct {
320 physicalAddress: u64,
321 virtualAddress: u64,
322 elfOffset: u64,
323 binaryOffset: u64,
324 fileSize: u64,
325 firstSection: ?*BinaryElfSection,
326};
327
328const BinaryElfOutput = struct {
329 segments: std.ArrayListUnmanaged(*BinaryElfSegment),
330 sections: std.ArrayListUnmanaged(*BinaryElfSection),
331 allocator: Allocator,
332 shstrtab: ?[]const u8,
333
334 const Self = @This();
335
336 pub fn deinit(self: *Self) void {
337 if (self.shstrtab) |shstrtab|
338 self.allocator.free(shstrtab);
339 self.sections.deinit(self.allocator);
340 self.segments.deinit(self.allocator);
341 }
342
343 pub fn parse(allocator: Allocator, elf_file: File, elf_hdr: elf.Header) !Self {
344 var self: Self = .{
345 .segments = .{},
346 .sections = .{},
347 .allocator = allocator,
348 .shstrtab = null,
349 };
350 errdefer self.sections.deinit(allocator);
351 errdefer self.segments.deinit(allocator);
352
353 self.shstrtab = blk: {
354 if (elf_hdr.shstrndx >= elf_hdr.shnum) break :blk null;
355
356 var section_headers = elf_hdr.section_header_iterator(&elf_file);
357
358 var section_counter: usize = 0;
359 while (section_counter < elf_hdr.shstrndx) : (section_counter += 1) {
360 _ = (try section_headers.next()).?;
361 }
362
363 const shstrtab_shdr = (try section_headers.next()).?;
364
365 const buffer = try allocator.alloc(u8, @intCast(shstrtab_shdr.sh_size));
366 errdefer allocator.free(buffer);
367
368 const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset);
369 if (num_read != buffer.len) return error.EndOfStream;
370
371 break :blk buffer;
372 };
373
374 errdefer if (self.shstrtab) |shstrtab| allocator.free(shstrtab);
375
376 var section_headers = elf_hdr.section_header_iterator(&elf_file);
377 while (try section_headers.next()) |section| {
378 if (sectionValidForOutput(section)) {
379 const newSection = try allocator.create(BinaryElfSection);
380
381 newSection.binaryOffset = 0;
382 newSection.elfOffset = section.sh_offset;
383 newSection.fileSize = @intCast(section.sh_size);
384 newSection.segment = null;
385
386 newSection.name = if (self.shstrtab) |shstrtab|
387 std.mem.span(@as([*:0]const u8, @ptrCast(&shstrtab[section.sh_name])))
388 else
389 null;
390
391 try self.sections.append(allocator, newSection);
392 }
393 }
394
395 var program_headers = elf_hdr.program_header_iterator(&elf_file);
396 while (try program_headers.next()) |phdr| {
397 if (phdr.p_type == elf.PT_LOAD) {
398 const newSegment = try allocator.create(BinaryElfSegment);
399
400 newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr;
401 newSegment.virtualAddress = phdr.p_vaddr;
402 newSegment.fileSize = @intCast(phdr.p_filesz);
403 newSegment.elfOffset = phdr.p_offset;
404 newSegment.binaryOffset = 0;
405 newSegment.firstSection = null;
406
407 for (self.sections.items) |section| {
408 if (sectionWithinSegment(section, phdr)) {
409 if (section.segment) |sectionSegment| {
410 if (sectionSegment.elfOffset > newSegment.elfOffset) {
411 section.segment = newSegment;
412 }
413 } else {
414 section.segment = newSegment;
415 }
416
417 if (newSegment.firstSection == null) {
418 newSegment.firstSection = section;
419 }
420 }
421 }
422
423 try self.segments.append(allocator, newSegment);
424 }
425 }
426
427 mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
428
429 for (self.segments.items, 0..) |firstSegment, i| {
430 if (firstSegment.firstSection) |firstSection| {
431 const diff = firstSection.elfOffset - firstSegment.elfOffset;
432
433 firstSegment.elfOffset += diff;
434 firstSegment.fileSize += diff;
435 firstSegment.physicalAddress += diff;
436
437 const basePhysicalAddress = firstSegment.physicalAddress;
438
439 for (self.segments.items[i + 1 ..]) |segment| {
440 segment.binaryOffset = segment.physicalAddress - basePhysicalAddress;
441 }
442 break;
443 }
444 }
445
446 for (self.sections.items) |section| {
447 if (section.segment) |segment| {
448 section.binaryOffset = segment.binaryOffset + (section.elfOffset - segment.elfOffset);
449 }
450 }
451
452 mem.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
453
454 return self;
455 }
456
457 fn sectionWithinSegment(section: *BinaryElfSection, segment: elf.Elf64_Phdr) bool {
458 return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize);
459 }
460
461 fn sectionValidForOutput(shdr: anytype) bool {
462 return shdr.sh_type != elf.SHT_NOBITS and
463 ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC);
464 }
465
466 fn segmentSortCompare(context: void, left: *BinaryElfSegment, right: *BinaryElfSegment) bool {
467 _ = context;
468 if (left.physicalAddress < right.physicalAddress) {
469 return true;
470 }
471 if (left.physicalAddress > right.physicalAddress) {
472 return false;
473 }
474 return false;
475 }
476
477 fn sectionSortCompare(context: void, left: *BinaryElfSection, right: *BinaryElfSection) bool {
478 _ = context;
479 return left.binaryOffset < right.binaryOffset;
480 }
481};
482
483fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSection) !void {
484 try out_file.writeFileAll(elf_file, .{
485 .in_offset = section.elfOffset,
486 .in_len = section.fileSize,
487 });
488}
489
490const HexWriter = struct {
491 prev_addr: ?u32 = null,
492 out_file: File,
493
494 /// Max data bytes per line of output
495 const MAX_PAYLOAD_LEN: u8 = 16;
496
497 fn addressParts(address: u16) [2]u8 {
498 const msb: u8 = @truncate(address >> 8);
499 const lsb: u8 = @truncate(address);
500 return [2]u8{ msb, lsb };
501 }
502
503 const Record = struct {
504 const Type = enum(u8) {
505 Data = 0,
506 EOF = 1,
507 ExtendedSegmentAddress = 2,
508 ExtendedLinearAddress = 4,
509 };
510
511 address: u16,
512 payload: union(Type) {
513 Data: []const u8,
514 EOF: void,
515 ExtendedSegmentAddress: [2]u8,
516 ExtendedLinearAddress: [2]u8,
517 },
518
519 fn EOF() Record {
520 return Record{
521 .address = 0,
522 .payload = .EOF,
523 };
524 }
525
526 fn Data(address: u32, data: []const u8) Record {
527 return Record{
528 .address = @intCast(address % 0x10000),
529 .payload = .{ .Data = data },
530 };
531 }
532
533 fn Address(address: u32) Record {
534 assert(address > 0xFFFF);
535 const segment: u16 = @intCast(address / 0x10000);
536 if (address > 0xFFFFF) {
537 return Record{
538 .address = 0,
539 .payload = .{ .ExtendedLinearAddress = addressParts(segment) },
540 };
541 } else {
542 return Record{
543 .address = 0,
544 .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) },
545 };
546 }
547 }
548
549 fn getPayloadBytes(self: *const Record) []const u8 {
550 return switch (self.payload) {
551 .Data => |d| d,
552 .EOF => @as([]const u8, &.{}),
553 .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg,
554 };
555 }
556
557 fn checksum(self: Record) u8 {
558 const payload_bytes = self.getPayloadBytes();
559
560 var sum: u8 = @intCast(payload_bytes.len);
561 const parts = addressParts(self.address);
562 sum +%= parts[0];
563 sum +%= parts[1];
564 sum +%= @intFromEnum(self.payload);
565 for (payload_bytes) |byte| {
566 sum +%= byte;
567 }
568 return (sum ^ 0xFF) +% 1;
569 }
570
571 fn write(self: Record, file: File) File.WriteError!void {
572 const linesep = "\r\n";
573 // colon, (length, address, type, payload, checksum) as hex, CRLF
574 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
575 var outbuf: [BUFSIZE]u8 = undefined;
576 const payload_bytes = self.getPayloadBytes();
577 assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
578
579 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
580 @as(u8, @intCast(payload_bytes.len)),
581 self.address,
582 @intFromEnum(self.payload),
583 std.fmt.fmtSliceHexUpper(payload_bytes),
584 self.checksum(),
585 });
586 try file.writeAll(line);
587 }
588 };
589
590 pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, elf_file: File) !void {
591 var buf: [MAX_PAYLOAD_LEN]u8 = undefined;
592 var bytes_read: usize = 0;
593 while (bytes_read < segment.fileSize) {
594 const row_address: u32 = @intCast(segment.physicalAddress + bytes_read);
595
596 const remaining = segment.fileSize - bytes_read;
597 const to_read: usize = @intCast(@min(remaining, MAX_PAYLOAD_LEN));
598 const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read);
599 if (did_read < to_read) return error.UnexpectedEOF;
600
601 try self.writeDataRow(row_address, buf[0..did_read]);
602
603 bytes_read += did_read;
604 }
605 }
606
607 fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) File.WriteError!void {
608 const record = Record.Data(address, data);
609 if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) {
610 try Record.Address(address).write(self.out_file);
611 }
612 try record.write(self.out_file);
613 self.prev_addr = @intCast(record.address + data.len);
614 }
615
616 fn writeEOF(self: HexWriter) File.WriteError!void {
617 try Record.EOF().write(self.out_file);
618 }
619};
620
621fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
622 const max_address = std.math.maxInt(u32);
623 for (segments) |segment| {
624 if (segment.fileSize > max_address or
625 segment.physicalAddress > max_address - segment.fileSize) return false;
626 }
627 return true;
628}
629
630fn padFile(f: File, opt_size: ?u64) !void {
631 const size = opt_size orelse return;
632 try f.setEndPos(size);
633}
634
635test "HexWriter.Record.Address has correct payload and checksum" {
636 const record = HexWriter.Record.Address(0x0800_0000);
637 const payload = record.getPayloadBytes();
638 const sum = record.checksum();
639 try std.testing.expect(sum == 0xF2);
640 try std.testing.expect(payload.len == 2);
641 try std.testing.expect(payload[0] == 8);
642 try std.testing.expect(payload[1] == 0);
643}
644
645test "containsValidAddressRange" {
646 var segment = BinaryElfSegment{
647 .physicalAddress = 0,
648 .virtualAddress = 0,
649 .elfOffset = 0,
650 .binaryOffset = 0,
651 .fileSize = 0,
652 .firstSection = null,
653 };
654 var buf: [1]*BinaryElfSegment = .{&segment};
655
656 // segment too big
657 segment.fileSize = std.math.maxInt(u32) + 1;
658 try std.testing.expect(!containsValidAddressRange(&buf));
659
660 // start address too big
661 segment.physicalAddress = std.math.maxInt(u32) + 1;
662 segment.fileSize = 2;
663 try std.testing.expect(!containsValidAddressRange(&buf));
664
665 // max address too big
666 segment.physicalAddress = std.math.maxInt(u32) - 1;
667 segment.fileSize = 2;
668 try std.testing.expect(!containsValidAddressRange(&buf));
669
670 // is ok
671 segment.physicalAddress = std.math.maxInt(u32) - 1;
672 segment.fileSize = 1;
673 try std.testing.expect(containsValidAddressRange(&buf));
674}
675
676// -------------
677// ELF to ELF stripping
678
679const StripElfOptions = struct {
680 extract_to: ?[]const u8 = null,
681 add_debuglink: ?[]const u8 = null,
682 strip_all: bool = false,
683 strip_debug: bool = false,
684 only_keep_debug: bool = false,
685 compress_debug: bool = false,
686};
687
688fn stripElf(
689 allocator: Allocator,
690 in_file: File,
691 out_file: File,
692 elf_hdr: elf.Header,
693 options: StripElfOptions,
694) !void {
695 const Filter = ElfFileHelper.Filter;
696 const DebugLink = ElfFileHelper.DebugLink;
697
698 const filter: Filter = filter: {
699 if (options.only_keep_debug) break :filter .debug;
700 if (options.strip_all) break :filter .program;
701 if (options.strip_debug) break :filter .program_and_symbols;
702 break :filter .all;
703 };
704
705 const filter_complement: ?Filter = blk: {
706 if (options.extract_to) |_| {
707 break :blk switch (filter) {
708 .program => .debug_and_symbols,
709 .debug => .program_and_symbols,
710 .program_and_symbols => .debug,
711 .debug_and_symbols => .program,
712 .all => fatal("zig objcopy: nothing to extract", .{}),
713 };
714 } else {
715 break :blk null;
716 }
717 };
718 const debuglink_path = path: {
719 if (options.add_debuglink) |path| break :path path;
720 if (options.extract_to) |path| break :path path;
721 break :path null;
722 };
723
724 switch (elf_hdr.is_64) {
725 inline else => |is_64| {
726 var elf_file = try ElfFile(is_64).parse(allocator, in_file, elf_hdr);
727 defer elf_file.deinit();
728
729 if (filter_complement) |flt| {
730 // write the .dbg file and close it, so it can be read back to compute the debuglink checksum.
731 const path = options.extract_to.?;
732 const dbg_file = std.fs.cwd().createFile(path, .{}) catch |err| {
733 fatal("zig objcopy: unable to create '{s}': {s}", .{ path, @errorName(err) });
734 };
735 defer dbg_file.close();
736
737 try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt, .compress_debug = options.compress_debug });
738 }
739
740 const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null;
741 try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug });
742 },
743 }
744}
745
746// note: this is "a minimal effort implementation"
747// It doesn't support all possibile elf files: some sections type may need fixups, the program header may need fix up, ...
748// It was written for a specific use case (strip debug info to a sperate file, for linux 64-bits executables built with `zig` or `zig c++` )
749// It moves and reoders the sections as little as possible to avoid having to do fixups.
750// TODO: support non-native endianess
751
752fn ElfFile(comptime is_64: bool) type {
753 const Elf_Ehdr = if (is_64) elf.Elf64_Ehdr else elf.Elf32_Ehdr;
754 const Elf_Phdr = if (is_64) elf.Elf64_Phdr else elf.Elf32_Phdr;
755 const Elf_Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr;
756 const Elf_Chdr = if (is_64) elf.Elf64_Chdr else elf.Elf32_Chdr;
757 const Elf_Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;
758 const Elf_Verdef = if (is_64) elf.Elf64_Verdef else elf.Elf32_Verdef;
759 const Elf_OffSize = if (is_64) elf.Elf64_Off else elf.Elf32_Off;
760
761 return struct {
762 raw_elf_header: Elf_Ehdr,
763 program_segments: []const Elf_Phdr,
764 sections: []const Section,
765 arena: std.heap.ArenaAllocator,
766
767 const SectionCategory = ElfFileHelper.SectionCategory;
768 const section_memory_align = @alignOf(Elf_Sym); // most restrictive of what we may load in memory
769 const Section = struct {
770 section: Elf_Shdr,
771 name: []const u8 = "",
772 segment: ?*const Elf_Phdr = null, // if the section is used by a program segment (there can be more than one)
773 payload: ?[]align(section_memory_align) const u8 = null, // if we need the data in memory
774 category: SectionCategory = .none, // should the section be kept in the exe or stripped to the debug database, or both.
775 };
776
777 const Self = @This();
778
779 pub fn parse(gpa: Allocator, in_file: File, header: elf.Header) !Self {
780 var arena = std.heap.ArenaAllocator.init(gpa);
781 errdefer arena.deinit();
782 const allocator = arena.allocator();
783
784 var raw_header: Elf_Ehdr = undefined;
785 {
786 const bytes_read = try in_file.preadAll(std.mem.asBytes(&raw_header), 0);
787 if (bytes_read < @sizeOf(Elf_Ehdr))
788 return error.TRUNCATED_ELF;
789 }
790
791 // program header: list of segments
792 const program_segments = blk: {
793 if (@sizeOf(Elf_Phdr) != header.phentsize)
794 fatal("zig objcopy: unsuported ELF file, unexpected phentsize ({d})", .{header.phentsize});
795
796 const program_header = try allocator.alloc(Elf_Phdr, header.phnum);
797 const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(program_header), header.phoff);
798 if (bytes_read < @sizeOf(Elf_Phdr) * header.phnum)
799 return error.TRUNCATED_ELF;
800 break :blk program_header;
801 };
802
803 // section header
804 const sections = blk: {
805 if (@sizeOf(Elf_Shdr) != header.shentsize)
806 fatal("zig objcopy: unsuported ELF file, unexpected shentsize ({d})", .{header.shentsize});
807
808 const section_header = try allocator.alloc(Section, header.shnum);
809
810 const raw_section_header = try allocator.alloc(Elf_Shdr, header.shnum);
811 defer allocator.free(raw_section_header);
812 const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(raw_section_header), header.shoff);
813 if (bytes_read < @sizeOf(Elf_Phdr) * header.shnum)
814 return error.TRUNCATED_ELF;
815
816 for (section_header, raw_section_header) |*section, hdr| {
817 section.* = .{ .section = hdr };
818 }
819 break :blk section_header;
820 };
821
822 // load data to memory for some sections:
823 // string tables for access
824 // sections than need modifications when other sections move.
825 for (sections, 0..) |*section, idx| {
826 const need_data = switch (section.section.sh_type) {
827 elf.DT_VERSYM => true,
828 elf.SHT_SYMTAB, elf.SHT_DYNSYM => true,
829 else => false,
830 };
831 const need_strings = (idx == header.shstrndx);
832
833 if (need_data or need_strings) {
834 const buffer = try allocator.alignedAlloc(u8, section_memory_align, @intCast(section.section.sh_size));
835 const bytes_read = try in_file.preadAll(buffer, section.section.sh_offset);
836 if (bytes_read != section.section.sh_size) return error.TRUNCATED_ELF;
837 section.payload = buffer;
838 }
839 }
840
841 // fill-in sections info:
842 // resolve the name
843 // find if a program segment uses the section
844 // categorize sections usage (used by program segments, debug datadase, common metadata, symbol table)
845 for (sections) |*section| {
846 section.segment = for (program_segments) |*seg| {
847 if (sectionWithinSegment(section.section, seg.*)) break seg;
848 } else null;
849
850 if (section.section.sh_name != 0 and header.shstrndx != elf.SHN_UNDEF)
851 section.name = std.mem.span(@as([*:0]const u8, @ptrCast(&sections[header.shstrndx].payload.?[section.section.sh_name])));
852
853 const category_from_program: SectionCategory = if (section.segment != null) .exe else .debug;
854 section.category = switch (section.section.sh_type) {
855 elf.SHT_NOTE => .common,
856 elf.SHT_SYMTAB => .symbols, // "strip all" vs "strip only debug"
857 elf.SHT_DYNSYM => .exe,
858 elf.SHT_PROGBITS => cat: {
859 if (std.mem.eql(u8, section.name, ".comment")) break :cat .exe;
860 if (std.mem.eql(u8, section.name, ".gnu_debuglink")) break :cat .none;
861 break :cat category_from_program;
862 },
863 elf.SHT_LOPROC...elf.SHT_HIPROC => .common, // don't strip unknown sections
864 elf.SHT_LOUSER...elf.SHT_HIUSER => .common, // don't strip unknown sections
865 else => category_from_program,
866 };
867 }
868
869 sections[0].category = .common; // mandatory null section
870 if (header.shstrndx != elf.SHN_UNDEF)
871 sections[header.shstrndx].category = .common; // string table for the headers
872
873 // recursively propagate section categories to their linked sections, so that they are kept together
874 var dirty: u1 = 1;
875 while (dirty != 0) {
876 dirty = 0;
877
878 for (sections) |*section| {
879 if (section.section.sh_link != elf.SHN_UNDEF)
880 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_link].category, section.category);
881 if ((section.section.sh_flags & elf.SHF_INFO_LINK) != 0 and section.section.sh_info != elf.SHN_UNDEF)
882 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_info].category, section.category);
883 }
884 }
885
886 return Self{
887 .arena = arena,
888 .raw_elf_header = raw_header,
889 .program_segments = program_segments,
890 .sections = sections,
891 };
892 }
893
894 pub fn deinit(self: *Self) void {
895 self.arena.deinit();
896 }
897
898 const Filter = ElfFileHelper.Filter;
899 const DebugLink = ElfFileHelper.DebugLink;
900 const EmitElfOptions = struct {
901 section_filter: Filter = .all,
902 debuglink: ?DebugLink = null,
903 compress_debug: bool = false,
904 };
905 fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void {
906 var arena = std.heap.ArenaAllocator.init(gpa);
907 defer arena.deinit();
908 const allocator = arena.allocator();
909
910 // when emitting the stripped exe:
911 // - unused sections are removed
912 // when emitting the debug file:
913 // - all sections are kept, but some are emptied and their types is changed to SHT_NOBITS
914 // the program header is kept unchanged. (`strip` does update it, but `eu-strip` does not, and it still works)
915
916 const Update = struct {
917 action: ElfFileHelper.Action,
918
919 // remap the indexs after omitting the filtered sections
920 remap_idx: u16,
921
922 // optionally overrides the payload from the source file
923 payload: ?[]align(section_memory_align) const u8 = null,
924 section: ?Elf_Shdr = null,
925 };
926 const sections_update = try allocator.alloc(Update, self.sections.len);
927 const new_shnum = blk: {
928 var next_idx: u16 = 0;
929 for (self.sections, sections_update) |section, *update| {
930 const action = ElfFileHelper.selectAction(section.category, options.section_filter);
931 const remap_idx = idx: {
932 if (action == .strip) break :idx elf.SHN_UNDEF;
933 next_idx += 1;
934 break :idx next_idx - 1;
935 };
936 update.* = Update{ .action = action, .remap_idx = remap_idx };
937 }
938
939 if (options.debuglink != null)
940 next_idx += 1;
941
942 break :blk next_idx;
943 };
944
945 // add a ".gnu_debuglink" to the string table if needed
946 const debuglink_name: u32 = blk: {
947 if (options.debuglink == null) break :blk elf.SHN_UNDEF;
948 if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF)
949 fatal("zig objcopy: no strtab, cannot add the debuglink section", .{}); // TODO add the section if needed?
950
951 const strtab = &self.sections[self.raw_elf_header.e_shstrndx];
952 const update = &sections_update[self.raw_elf_header.e_shstrndx];
953
954 const name: []const u8 = ".gnu_debuglink";
955 const new_offset: u32 = @intCast(strtab.payload.?.len);
956 const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1);
957 @memcpy(buf[0..new_offset], strtab.payload.?);
958 @memcpy(buf[new_offset..][0..name.len], name);
959 buf[new_offset + name.len] = 0;
960
961 assert(update.action == .keep);
962 update.payload = buf;
963
964 break :blk new_offset;
965 };
966
967 // maybe compress .debug sections
968 if (options.compress_debug) {
969 for (self.sections[1..], sections_update[1..]) |section, *update| {
970 if (update.action != .keep) continue;
971 if (!std.mem.startsWith(u8, section.name, ".debug_")) continue;
972 if ((section.section.sh_flags & elf.SHF_COMPRESSED) != 0) continue; // already compressed
973
974 const chdr = Elf_Chdr{
975 .ch_type = elf.COMPRESS.ZLIB,
976 .ch_size = section.section.sh_size,
977 .ch_addralign = section.section.sh_addralign,
978 };
979
980 const compressed_payload = try ElfFileHelper.tryCompressSection(allocator, in_file, section.section.sh_offset, section.section.sh_size, std.mem.asBytes(&chdr));
981 if (compressed_payload) |payload| {
982 update.payload = payload;
983 update.section = section.section;
984 update.section.?.sh_addralign = @alignOf(Elf_Chdr);
985 update.section.?.sh_size = @intCast(payload.len);
986 update.section.?.sh_flags |= elf.SHF_COMPRESSED;
987 }
988 }
989 }
990
991 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);
992 defer cmdbuf.deinit();
993 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);
994 var eof_offset: Elf_OffSize = 0; // track the end of the data written so far.
995
996 // build the updated headers
997 // nb: updated_elf_header will be updated before the actual write
998 var updated_elf_header = self.raw_elf_header;
999 if (updated_elf_header.e_shstrndx != elf.SHN_UNDEF)
1000 updated_elf_header.e_shstrndx = sections_update[updated_elf_header.e_shstrndx].remap_idx;
1001 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = std.mem.asBytes(&updated_elf_header), .out_offset = 0 } });
1002 eof_offset = @sizeOf(Elf_Ehdr);
1003
1004 // program header as-is.
1005 // nb: for only-debug files, removing it appears to work, but is invalid by ELF specifcation.
1006 {
1007 assert(updated_elf_header.e_phoff == @sizeOf(Elf_Ehdr));
1008 const data = std.mem.sliceAsBytes(self.program_segments);
1009 assert(data.len == @as(usize, updated_elf_header.e_phentsize) * updated_elf_header.e_phnum);
1010 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = data, .out_offset = updated_elf_header.e_phoff } });
1011 eof_offset = updated_elf_header.e_phoff + @as(Elf_OffSize, @intCast(data.len));
1012 }
1013
1014 // update sections and queue payload writes
1015 const updated_section_header = blk: {
1016 const dest_sections = try allocator.alloc(Elf_Shdr, new_shnum);
1017
1018 {
1019 // the ELF format doesn't specify the order for all sections.
1020 // this code only supports when they are in increasing file order.
1021 var offset: u64 = eof_offset;
1022 for (self.sections[1..]) |section| {
1023 if (section.section.sh_type == elf.SHT_NOBITS)
1024 continue;
1025 if (section.section.sh_offset < offset) {
1026 fatal("zig objcopy: unsuported ELF file", .{});
1027 }
1028 offset = section.section.sh_offset;
1029 }
1030 }
1031
1032 dest_sections[0] = self.sections[0].section;
1033
1034 var dest_section_idx: u32 = 1;
1035 for (self.sections[1..], sections_update[1..]) |section, update| {
1036 if (update.action == .strip) continue;
1037 assert(update.remap_idx == dest_section_idx);
1038
1039 const src = if (update.section) |*s| s else &section.section;
1040 const dest = &dest_sections[dest_section_idx];
1041 const payload = if (update.payload) |data| data else section.payload;
1042 dest_section_idx += 1;
1043
1044 dest.* = src.*;
1045
1046 if (src.sh_link != elf.SHN_UNDEF)
1047 dest.sh_link = sections_update[src.sh_link].remap_idx;
1048 if ((src.sh_flags & elf.SHF_INFO_LINK) != 0 and src.sh_info != elf.SHN_UNDEF)
1049 dest.sh_info = sections_update[src.sh_info].remap_idx;
1050
1051 if (payload) |data|
1052 dest.sh_size = @intCast(data.len);
1053
1054 const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign;
1055 dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign);
1056 if (src.sh_offset != dest.sh_offset and section.segment != null and update.action != .empty and dest.sh_type != elf.SHT_NOTE and dest.sh_type != elf.SHT_NOBITS) {
1057 if (src.sh_offset > dest.sh_offset) {
1058 dest.sh_offset = src.sh_offset; // add padding to avoid modifing the program segments
1059 } else {
1060 fatal("zig objcopy: cannot adjust program segments", .{});
1061 }
1062 }
1063 assert(dest.sh_addr % addralign == dest.sh_offset % addralign);
1064
1065 if (update.action == .empty)
1066 dest.sh_type = elf.SHT_NOBITS;
1067
1068 if (dest.sh_type != elf.SHT_NOBITS) {
1069 if (payload) |src_data| {
1070 // update sections payload and write
1071 const dest_data = switch (src.sh_type) {
1072 elf.DT_VERSYM => dst_data: {
1073 const data = try allocator.alignedAlloc(u8, section_memory_align, src_data.len);
1074 @memcpy(data, src_data);
1075
1076 const defs = @as([*]Elf_Verdef, @ptrCast(data))[0 .. @as(usize, @intCast(src.sh_size)) / @sizeOf(Elf_Verdef)];
1077 for (defs) |*def| {
1078 if (def.vd_ndx != elf.SHN_UNDEF)
1079 def.vd_ndx = sections_update[src.sh_info].remap_idx;
1080 }
1081
1082 break :dst_data data;
1083 },
1084 elf.SHT_SYMTAB, elf.SHT_DYNSYM => dst_data: {
1085 const data = try allocator.alignedAlloc(u8, section_memory_align, src_data.len);
1086 @memcpy(data, src_data);
1087
1088 const syms = @as([*]Elf_Sym, @ptrCast(data))[0 .. @as(usize, @intCast(src.sh_size)) / @sizeOf(Elf_Sym)];
1089 for (syms) |*sym| {
1090 if (sym.st_shndx != elf.SHN_UNDEF and sym.st_shndx < elf.SHN_LORESERVE)
1091 sym.st_shndx = sections_update[sym.st_shndx].remap_idx;
1092 }
1093
1094 break :dst_data data;
1095 },
1096 else => src_data,
1097 };
1098
1099 assert(dest_data.len == dest.sh_size);
1100 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = dest_data, .out_offset = dest.sh_offset } });
1101 eof_offset = dest.sh_offset + dest.sh_size;
1102 } else {
1103 // direct contents copy
1104 cmdbuf.appendAssumeCapacity(.{ .copy_range = .{ .in_offset = src.sh_offset, .len = dest.sh_size, .out_offset = dest.sh_offset } });
1105 eof_offset = dest.sh_offset + dest.sh_size;
1106 }
1107 } else {
1108 // account for alignment padding even in empty sections to keep logical section order
1109 eof_offset = dest.sh_offset;
1110 }
1111 }
1112
1113 // add a ".gnu_debuglink" section
1114 if (options.debuglink) |link| {
1115 const payload = payload: {
1116 const crc_offset = std.mem.alignForward(usize, link.name.len + 1, 4);
1117 const buf = try allocator.alignedAlloc(u8, 4, crc_offset + 4);
1118 @memcpy(buf[0..link.name.len], link.name);
1119 @memset(buf[link.name.len..crc_offset], 0);
1120 @memcpy(buf[crc_offset..], std.mem.asBytes(&link.crc32));
1121 break :payload buf;
1122 };
1123
1124 dest_sections[dest_section_idx] = Elf_Shdr{
1125 .sh_name = debuglink_name,
1126 .sh_type = elf.SHT_PROGBITS,
1127 .sh_flags = 0,
1128 .sh_addr = 0,
1129 .sh_offset = eof_offset,
1130 .sh_size = @intCast(payload.len),
1131 .sh_link = elf.SHN_UNDEF,
1132 .sh_info = elf.SHN_UNDEF,
1133 .sh_addralign = 4,
1134 .sh_entsize = 0,
1135 };
1136 dest_section_idx += 1;
1137
1138 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = payload, .out_offset = eof_offset } });
1139 eof_offset += @as(Elf_OffSize, @intCast(payload.len));
1140 }
1141
1142 assert(dest_section_idx == new_shnum);
1143 break :blk dest_sections;
1144 };
1145
1146 // write the section header at the tail
1147 {
1148 const offset = std.mem.alignForward(Elf_OffSize, eof_offset, @alignOf(Elf_Shdr));
1149
1150 const data = std.mem.sliceAsBytes(updated_section_header);
1151 assert(data.len == @as(usize, updated_elf_header.e_shentsize) * new_shnum);
1152 updated_elf_header.e_shoff = offset;
1153 updated_elf_header.e_shnum = new_shnum;
1154
1155 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = data, .out_offset = updated_elf_header.e_shoff } });
1156 }
1157
1158 try ElfFileHelper.write(allocator, out_file, in_file, cmdbuf.items);
1159 }
1160
1161 fn sectionWithinSegment(section: Elf_Shdr, segment: Elf_Phdr) bool {
1162 const file_size = if (section.sh_type == elf.SHT_NOBITS) 0 else section.sh_size;
1163 return segment.p_offset <= section.sh_offset and (segment.p_offset + segment.p_filesz) >= (section.sh_offset + file_size);
1164 }
1165 };
1166}
1167
1168const ElfFileHelper = struct {
1169 const DebugLink = struct { name: []const u8, crc32: u32 };
1170 const Filter = enum { all, program, debug, program_and_symbols, debug_and_symbols };
1171
1172 const SectionCategory = enum { common, exe, debug, symbols, none };
1173 fn propagateCategory(cur: *SectionCategory, new: SectionCategory) u1 {
1174 const cat: SectionCategory = switch (cur.*) {
1175 .none => new,
1176 .common => .common,
1177 .debug => switch (new) {
1178 .none, .debug => .debug,
1179 else => new,
1180 },
1181 .exe => switch (new) {
1182 .common => .common,
1183 .none, .debug, .exe => .exe,
1184 .symbols => .exe,
1185 },
1186 .symbols => switch (new) {
1187 .none, .common, .debug, .exe => unreachable,
1188 .symbols => .symbols,
1189 },
1190 };
1191
1192 if (cur.* != cat) {
1193 cur.* = cat;
1194 return 1;
1195 } else {
1196 return 0;
1197 }
1198 }
1199
1200 const Action = enum { keep, strip, empty };
1201 fn selectAction(category: SectionCategory, filter: Filter) Action {
1202 if (category == .none) return .strip;
1203 return switch (filter) {
1204 .all => switch (category) {
1205 .none => .strip,
1206 else => .keep,
1207 },
1208 .program => switch (category) {
1209 .common, .exe => .keep,
1210 else => .strip,
1211 },
1212 .program_and_symbols => switch (category) {
1213 .common, .exe, .symbols => .keep,
1214 else => .strip,
1215 },
1216 .debug => switch (category) {
1217 .exe, .symbols => .empty,
1218 .none => .strip,
1219 else => .keep,
1220 },
1221 .debug_and_symbols => switch (category) {
1222 .exe => .empty,
1223 .none => .strip,
1224 else => .keep,
1225 },
1226 };
1227 }
1228
1229 const WriteCmd = union(enum) {
1230 copy_range: struct { in_offset: u64, len: u64, out_offset: u64 },
1231 write_data: struct { data: []const u8, out_offset: u64 },
1232 };
1233 fn write(allocator: Allocator, out_file: File, in_file: File, cmds: []const WriteCmd) !void {
1234 // consolidate holes between writes:
1235 // by coping original padding data from in_file (by fusing contiguous ranges)
1236 // by writing zeroes otherwise
1237 const zeroes = [1]u8{0} ** 4096;
1238 var consolidated = std.ArrayList(WriteCmd).init(allocator);
1239 defer consolidated.deinit();
1240 try consolidated.ensureUnusedCapacity(cmds.len * 2);
1241 var offset: u64 = 0;
1242 var fused_cmd: ?WriteCmd = null;
1243 for (cmds) |cmd| {
1244 switch (cmd) {
1245 .write_data => |data| {
1246 assert(data.out_offset >= offset);
1247 if (fused_cmd) |prev| {
1248 consolidated.appendAssumeCapacity(prev);
1249 fused_cmd = null;
1250 }
1251 if (data.out_offset > offset) {
1252 consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(data.out_offset - offset)], .out_offset = offset } });
1253 }
1254 consolidated.appendAssumeCapacity(cmd);
1255 offset = data.out_offset + data.data.len;
1256 },
1257 .copy_range => |range| {
1258 assert(range.out_offset >= offset);
1259 if (fused_cmd) |prev| {
1260 if (range.in_offset >= prev.copy_range.in_offset + prev.copy_range.len and (range.out_offset - prev.copy_range.out_offset == range.in_offset - prev.copy_range.in_offset)) {
1261 fused_cmd = .{ .copy_range = .{
1262 .in_offset = prev.copy_range.in_offset,
1263 .out_offset = prev.copy_range.out_offset,
1264 .len = (range.out_offset + range.len) - prev.copy_range.out_offset,
1265 } };
1266 } else {
1267 consolidated.appendAssumeCapacity(prev);
1268 if (range.out_offset > offset) {
1269 consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(range.out_offset - offset)], .out_offset = offset } });
1270 }
1271 fused_cmd = cmd;
1272 }
1273 } else {
1274 fused_cmd = cmd;
1275 }
1276 offset = range.out_offset + range.len;
1277 },
1278 }
1279 }
1280 if (fused_cmd) |cmd| {
1281 consolidated.appendAssumeCapacity(cmd);
1282 }
1283
1284 // write the output file
1285 for (consolidated.items) |cmd| {
1286 switch (cmd) {
1287 .write_data => |data| {
1288 var iovec = [_]std.os.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }};
1289 try out_file.pwritevAll(&iovec, data.out_offset);
1290 },
1291 .copy_range => |range| {
1292 const copied_bytes = try in_file.copyRangeAll(range.in_offset, out_file, range.out_offset, range.len);
1293 if (copied_bytes < range.len) return error.TRUNCATED_ELF;
1294 },
1295 }
1296 }
1297 }
1298
1299 fn tryCompressSection(allocator: Allocator, in_file: File, offset: u64, size: u64, prefix: []const u8) !?[]align(8) const u8 {
1300 if (size < prefix.len) return null;
1301
1302 try in_file.seekTo(offset);
1303 var section_reader = std.io.limitedReader(in_file.reader(), size);
1304
1305 // allocate as large as decompressed data. if the compression doesn't fit, keep the data uncompressed.
1306 const compressed_data = try allocator.alignedAlloc(u8, 8, @intCast(size));
1307 var compressed_stream = std.io.fixedBufferStream(compressed_data);
1308
1309 try compressed_stream.writer().writeAll(prefix);
1310
1311 {
1312 var compressor = try std.compress.zlib.compressor(compressed_stream.writer(), .{});
1313
1314 var buf: [8000]u8 = undefined;
1315 while (true) {
1316 const bytes_read = try section_reader.read(&buf);
1317 if (bytes_read == 0) break;
1318 const bytes_written = compressor.write(buf[0..bytes_read]) catch |err| switch (err) {
1319 error.NoSpaceLeft => {
1320 allocator.free(compressed_data);
1321 return null;
1322 },
1323 else => return err,
1324 };
1325 std.debug.assert(bytes_written == bytes_read);
1326 }
1327 compressor.finish() catch |err| switch (err) {
1328 error.NoSpaceLeft => {
1329 allocator.free(compressed_data);
1330 return null;
1331 },
1332 else => return err,
1333 };
1334 }
1335
1336 const compressed_len: usize = @intCast(compressed_stream.getPos() catch unreachable);
1337 const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data;
1338 return data[0..compressed_len];
1339 }
1340
1341 fn createDebugLink(path: []const u8) DebugLink {
1342 const file = std.fs.cwd().openFile(path, .{}) catch |err| {
1343 fatal("zig objcopy: could not open `{s}`: {s}\n", .{ path, @errorName(err) });
1344 };
1345 defer file.close();
1346
1347 const crc = ElfFileHelper.computeFileCrc(file) catch |err| {
1348 fatal("zig objcopy: could not read `{s}`: {s}\n", .{ path, @errorName(err) });
1349 };
1350 return .{
1351 .name = std.fs.path.basename(path),
1352 .crc32 = crc,
1353 };
1354 }
1355
1356 fn computeFileCrc(file: File) !u32 {
1357 var buf: [8000]u8 = undefined;
1358
1359 try file.seekTo(0);
1360 var hasher = std.hash.Crc32.init();
1361 while (true) {
1362 const bytes_read = try file.read(&buf);
1363 if (bytes_read == 0) break;
1364 hasher.update(buf[0..bytes_read]);
1365 }
1366 return hasher.final();
1367 }
1368};
src/main.zig+4-1
......@@ -297,7 +297,10 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
297297 .root_src_path = "fmt.zig",
298298 });
299299 } else if (mem.eql(u8, cmd, "objcopy")) {
300 return @import("objcopy.zig").cmdObjCopy(gpa, arena, cmd_args);
300 return jitCmd(gpa, arena, cmd_args, .{
301 .cmd_name = "objcopy",
302 .root_src_path = "objcopy.zig",
303 });
301304 } else if (mem.eql(u8, cmd, "fetch")) {
302305 return cmdFetch(gpa, arena, cmd_args);
303306 } else if (mem.eql(u8, cmd, "libc")) {
src/objcopy.zig deleted-1357
......@@ -1,1357 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const fs = std.fs;
4const elf = std.elf;
5const Allocator = std.mem.Allocator;
6const File = std.fs.File;
7const assert = std.debug.assert;
8
9const main = @import("main.zig");
10const fatal = main.fatal;
11const Server = std.zig.Server;
12const build_options = @import("build_options");
13
14pub fn cmdObjCopy(
15 gpa: Allocator,
16 arena: Allocator,
17 args: []const []const u8,
18) !void {
19 var i: usize = 0;
20 var opt_out_fmt: ?std.Target.ObjectFormat = null;
21 var opt_input: ?[]const u8 = null;
22 var opt_output: ?[]const u8 = null;
23 var opt_extract: ?[]const u8 = null;
24 var opt_add_debuglink: ?[]const u8 = null;
25 var only_section: ?[]const u8 = null;
26 var pad_to: ?u64 = null;
27 var strip_all: bool = false;
28 var strip_debug: bool = false;
29 var only_keep_debug: bool = false;
30 var compress_debug_sections: bool = false;
31 var listen = false;
32 while (i < args.len) : (i += 1) {
33 const arg = args[i];
34 if (!mem.startsWith(u8, arg, "-")) {
35 if (opt_input == null) {
36 opt_input = arg;
37 } else if (opt_output == null) {
38 opt_output = arg;
39 } else {
40 fatal("unexpected positional argument: '{s}'", .{arg});
41 }
42 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
43 return std.io.getStdOut().writeAll(usage);
44 } else if (mem.eql(u8, arg, "-O") or mem.eql(u8, arg, "--output-target")) {
45 i += 1;
46 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
47 const next_arg = args[i];
48 if (mem.eql(u8, next_arg, "binary")) {
49 opt_out_fmt = .raw;
50 } else {
51 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
52 fatal("invalid output format: '{s}'", .{next_arg});
53 }
54 } else if (mem.startsWith(u8, arg, "--output-target=")) {
55 const next_arg = arg["--output-target=".len..];
56 if (mem.eql(u8, next_arg, "binary")) {
57 opt_out_fmt = .raw;
58 } else {
59 opt_out_fmt = std.meta.stringToEnum(std.Target.ObjectFormat, next_arg) orelse
60 fatal("invalid output format: '{s}'", .{next_arg});
61 }
62 } else if (mem.eql(u8, arg, "-j") or mem.eql(u8, arg, "--only-section")) {
63 i += 1;
64 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
65 only_section = args[i];
66 } else if (mem.eql(u8, arg, "--listen=-")) {
67 listen = true;
68 } else if (mem.startsWith(u8, arg, "--only-section=")) {
69 only_section = arg["--only-section=".len..];
70 } else if (mem.eql(u8, arg, "--pad-to")) {
71 i += 1;
72 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
73 pad_to = std.fmt.parseInt(u64, args[i], 0) catch |err| {
74 fatal("unable to parse: '{s}': {s}", .{ args[i], @errorName(err) });
75 };
76 } else if (mem.eql(u8, arg, "-g") or mem.eql(u8, arg, "--strip-debug")) {
77 strip_debug = true;
78 } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-all")) {
79 strip_all = true;
80 } else if (mem.eql(u8, arg, "--only-keep-debug")) {
81 only_keep_debug = true;
82 } else if (mem.eql(u8, arg, "--compress-debug-sections")) {
83 compress_debug_sections = true;
84 } else if (mem.startsWith(u8, arg, "--add-gnu-debuglink=")) {
85 opt_add_debuglink = arg["--add-gnu-debuglink=".len..];
86 } else if (mem.eql(u8, arg, "--add-gnu-debuglink")) {
87 i += 1;
88 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
89 opt_add_debuglink = args[i];
90 } else if (mem.startsWith(u8, arg, "--extract-to=")) {
91 opt_extract = arg["--extract-to=".len..];
92 } else if (mem.eql(u8, arg, "--extract-to")) {
93 i += 1;
94 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
95 opt_extract = args[i];
96 } else {
97 fatal("unrecognized argument: '{s}'", .{arg});
98 }
99 }
100 const input = opt_input orelse fatal("expected input parameter", .{});
101 const output = opt_output orelse fatal("expected output parameter", .{});
102
103 var in_file = fs.cwd().openFile(input, .{}) catch |err|
104 fatal("unable to open '{s}': {s}", .{ input, @errorName(err) });
105 defer in_file.close();
106
107 const elf_hdr = std.elf.Header.read(in_file) catch |err| switch (err) {
108 error.InvalidElfMagic => fatal("not an ELF file: '{s}'", .{input}),
109 else => fatal("unable to read '{s}': {s}", .{ input, @errorName(err) }),
110 };
111
112 const in_ofmt = .elf;
113
114 const out_fmt: std.Target.ObjectFormat = opt_out_fmt orelse ofmt: {
115 if (mem.endsWith(u8, output, ".hex") or std.mem.endsWith(u8, output, ".ihex")) {
116 break :ofmt .hex;
117 } else if (mem.endsWith(u8, output, ".bin")) {
118 break :ofmt .raw;
119 } else if (mem.endsWith(u8, output, ".elf")) {
120 break :ofmt .elf;
121 } else {
122 break :ofmt in_ofmt;
123 }
124 };
125
126 const mode = mode: {
127 if (out_fmt != .elf or only_keep_debug)
128 break :mode fs.File.default_mode;
129 if (in_file.stat()) |stat|
130 break :mode stat.mode
131 else |_|
132 break :mode fs.File.default_mode;
133 };
134 var out_file = try fs.cwd().createFile(output, .{ .mode = mode });
135 defer out_file.close();
136
137 switch (out_fmt) {
138 .hex, .raw => {
139 if (strip_debug or strip_all or only_keep_debug)
140 fatal("zig objcopy: ELF to RAW or HEX copying does not support --strip", .{});
141 if (opt_extract != null)
142 fatal("zig objcopy: ELF to RAW or HEX copying does not support --extract-to", .{});
143
144 try emitElf(arena, in_file, out_file, elf_hdr, .{
145 .ofmt = out_fmt,
146 .only_section = only_section,
147 .pad_to = pad_to,
148 });
149 },
150 .elf => {
151 if (elf_hdr.endian != @import("builtin").target.cpu.arch.endian())
152 fatal("zig objcopy: ELF to ELF copying only supports native endian", .{});
153 if (elf_hdr.phoff == 0) // no program header
154 fatal("zig objcopy: ELF to ELF copying only supports programs", .{});
155 if (only_section) |_|
156 fatal("zig objcopy: ELF to ELF copying does not support --only-section", .{});
157 if (pad_to) |_|
158 fatal("zig objcopy: ELF to ELF copying does not support --pad-to", .{});
159
160 try stripElf(arena, in_file, out_file, elf_hdr, .{
161 .strip_debug = strip_debug,
162 .strip_all = strip_all,
163 .only_keep_debug = only_keep_debug,
164 .add_debuglink = opt_add_debuglink,
165 .extract_to = opt_extract,
166 .compress_debug = compress_debug_sections,
167 });
168 return std.process.cleanExit();
169 },
170 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
171 }
172
173 if (listen) {
174 var server = try Server.init(.{
175 .gpa = gpa,
176 .in = std.io.getStdIn(),
177 .out = std.io.getStdOut(),
178 .zig_version = build_options.version,
179 });
180 defer server.deinit();
181
182 var seen_update = false;
183 while (true) {
184 const hdr = try server.receiveMessage();
185 switch (hdr.tag) {
186 .exit => {
187 return std.process.cleanExit();
188 },
189 .update => {
190 if (seen_update) {
191 std.debug.print("zig objcopy only supports 1 update for now\n", .{});
192 std.process.exit(1);
193 }
194 seen_update = true;
195
196 try server.serveEmitBinPath(output, .{
197 .flags = .{ .cache_hit = false },
198 });
199 },
200 else => {
201 std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)});
202 std.process.exit(1);
203 },
204 }
205 }
206 }
207 return std.process.cleanExit();
208}
209
210const usage =
211 \\Usage: zig objcopy [options] input output
212 \\
213 \\Options:
214 \\ -h, --help Print this help and exit
215 \\ --output-target=<value> Format of the output file
216 \\ -O <value> Alias for --output-target
217 \\ --only-section=<section> Remove all but <section>
218 \\ -j <value> Alias for --only-section
219 \\ --pad-to <addr> Pad the last section up to address <addr>
220 \\ --strip-debug, -g Remove all debug sections from the output.
221 \\ --strip-all, -S Remove all debug sections and symbol table from the output.
222 \\ --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.
223 \\ --add-gnu-debuglink=<file> Creates a .gnu_debuglink section which contains a reference to <file> and adds it to the output file.
224 \\ --extract-to <file> Extract the removed sections into <file>, and add a .gnu-debuglink section.
225 \\ --compress-debug-sections Compress DWARF debug sections with zlib
226 \\
227;
228
229pub const EmitRawElfOptions = struct {
230 ofmt: std.Target.ObjectFormat,
231 only_section: ?[]const u8 = null,
232 pad_to: ?u64 = null,
233};
234
235fn emitElf(
236 arena: Allocator,
237 in_file: File,
238 out_file: File,
239 elf_hdr: elf.Header,
240 options: EmitRawElfOptions,
241) !void {
242 var binary_elf_output = try BinaryElfOutput.parse(arena, in_file, elf_hdr);
243 defer binary_elf_output.deinit();
244
245 if (options.ofmt == .elf) {
246 fatal("zig objcopy: ELF to ELF copying is not implemented yet", .{});
247 }
248
249 if (options.only_section) |target_name| {
250 switch (options.ofmt) {
251 .hex => fatal("zig objcopy: hex format with sections is not implemented yet", .{}),
252 .raw => {
253 for (binary_elf_output.sections.items) |section| {
254 if (section.name) |curr_name| {
255 if (!std.mem.eql(u8, curr_name, target_name))
256 continue;
257 } else {
258 continue;
259 }
260
261 try writeBinaryElfSection(in_file, out_file, section);
262 try padFile(out_file, options.pad_to);
263 return;
264 }
265 },
266 else => unreachable,
267 }
268
269 return error.SectionNotFound;
270 }
271
272 switch (options.ofmt) {
273 .raw => {
274 for (binary_elf_output.sections.items) |section| {
275 try out_file.seekTo(section.binaryOffset);
276 try writeBinaryElfSection(in_file, out_file, section);
277 }
278 try padFile(out_file, options.pad_to);
279 },
280 .hex => {
281 if (binary_elf_output.segments.items.len == 0) return;
282 if (!containsValidAddressRange(binary_elf_output.segments.items)) {
283 return error.InvalidHexfileAddressRange;
284 }
285
286 var hex_writer = HexWriter{ .out_file = out_file };
287 for (binary_elf_output.segments.items) |segment| {
288 try hex_writer.writeSegment(segment, in_file);
289 }
290 if (options.pad_to) |_| {
291 // Padding to a size in hex files isn't applicable
292 return error.InvalidArgument;
293 }
294 try hex_writer.writeEOF();
295 },
296 else => unreachable,
297 }
298}
299
300const BinaryElfSection = struct {
301 elfOffset: u64,
302 binaryOffset: u64,
303 fileSize: usize,
304 name: ?[]const u8,
305 segment: ?*BinaryElfSegment,
306};
307
308const BinaryElfSegment = struct {
309 physicalAddress: u64,
310 virtualAddress: u64,
311 elfOffset: u64,
312 binaryOffset: u64,
313 fileSize: u64,
314 firstSection: ?*BinaryElfSection,
315};
316
317const BinaryElfOutput = struct {
318 segments: std.ArrayListUnmanaged(*BinaryElfSegment),
319 sections: std.ArrayListUnmanaged(*BinaryElfSection),
320 allocator: Allocator,
321 shstrtab: ?[]const u8,
322
323 const Self = @This();
324
325 pub fn deinit(self: *Self) void {
326 if (self.shstrtab) |shstrtab|
327 self.allocator.free(shstrtab);
328 self.sections.deinit(self.allocator);
329 self.segments.deinit(self.allocator);
330 }
331
332 pub fn parse(allocator: Allocator, elf_file: File, elf_hdr: elf.Header) !Self {
333 var self: Self = .{
334 .segments = .{},
335 .sections = .{},
336 .allocator = allocator,
337 .shstrtab = null,
338 };
339 errdefer self.sections.deinit(allocator);
340 errdefer self.segments.deinit(allocator);
341
342 self.shstrtab = blk: {
343 if (elf_hdr.shstrndx >= elf_hdr.shnum) break :blk null;
344
345 var section_headers = elf_hdr.section_header_iterator(&elf_file);
346
347 var section_counter: usize = 0;
348 while (section_counter < elf_hdr.shstrndx) : (section_counter += 1) {
349 _ = (try section_headers.next()).?;
350 }
351
352 const shstrtab_shdr = (try section_headers.next()).?;
353
354 const buffer = try allocator.alloc(u8, @intCast(shstrtab_shdr.sh_size));
355 errdefer allocator.free(buffer);
356
357 const num_read = try elf_file.preadAll(buffer, shstrtab_shdr.sh_offset);
358 if (num_read != buffer.len) return error.EndOfStream;
359
360 break :blk buffer;
361 };
362
363 errdefer if (self.shstrtab) |shstrtab| allocator.free(shstrtab);
364
365 var section_headers = elf_hdr.section_header_iterator(&elf_file);
366 while (try section_headers.next()) |section| {
367 if (sectionValidForOutput(section)) {
368 const newSection = try allocator.create(BinaryElfSection);
369
370 newSection.binaryOffset = 0;
371 newSection.elfOffset = section.sh_offset;
372 newSection.fileSize = @intCast(section.sh_size);
373 newSection.segment = null;
374
375 newSection.name = if (self.shstrtab) |shstrtab|
376 std.mem.span(@as([*:0]const u8, @ptrCast(&shstrtab[section.sh_name])))
377 else
378 null;
379
380 try self.sections.append(allocator, newSection);
381 }
382 }
383
384 var program_headers = elf_hdr.program_header_iterator(&elf_file);
385 while (try program_headers.next()) |phdr| {
386 if (phdr.p_type == elf.PT_LOAD) {
387 const newSegment = try allocator.create(BinaryElfSegment);
388
389 newSegment.physicalAddress = if (phdr.p_paddr != 0) phdr.p_paddr else phdr.p_vaddr;
390 newSegment.virtualAddress = phdr.p_vaddr;
391 newSegment.fileSize = @intCast(phdr.p_filesz);
392 newSegment.elfOffset = phdr.p_offset;
393 newSegment.binaryOffset = 0;
394 newSegment.firstSection = null;
395
396 for (self.sections.items) |section| {
397 if (sectionWithinSegment(section, phdr)) {
398 if (section.segment) |sectionSegment| {
399 if (sectionSegment.elfOffset > newSegment.elfOffset) {
400 section.segment = newSegment;
401 }
402 } else {
403 section.segment = newSegment;
404 }
405
406 if (newSegment.firstSection == null) {
407 newSegment.firstSection = section;
408 }
409 }
410 }
411
412 try self.segments.append(allocator, newSegment);
413 }
414 }
415
416 mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
417
418 for (self.segments.items, 0..) |firstSegment, i| {
419 if (firstSegment.firstSection) |firstSection| {
420 const diff = firstSection.elfOffset - firstSegment.elfOffset;
421
422 firstSegment.elfOffset += diff;
423 firstSegment.fileSize += diff;
424 firstSegment.physicalAddress += diff;
425
426 const basePhysicalAddress = firstSegment.physicalAddress;
427
428 for (self.segments.items[i + 1 ..]) |segment| {
429 segment.binaryOffset = segment.physicalAddress - basePhysicalAddress;
430 }
431 break;
432 }
433 }
434
435 for (self.sections.items) |section| {
436 if (section.segment) |segment| {
437 section.binaryOffset = segment.binaryOffset + (section.elfOffset - segment.elfOffset);
438 }
439 }
440
441 mem.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
442
443 return self;
444 }
445
446 fn sectionWithinSegment(section: *BinaryElfSection, segment: elf.Elf64_Phdr) bool {
447 return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize);
448 }
449
450 fn sectionValidForOutput(shdr: anytype) bool {
451 return shdr.sh_type != elf.SHT_NOBITS and
452 ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC);
453 }
454
455 fn segmentSortCompare(context: void, left: *BinaryElfSegment, right: *BinaryElfSegment) bool {
456 _ = context;
457 if (left.physicalAddress < right.physicalAddress) {
458 return true;
459 }
460 if (left.physicalAddress > right.physicalAddress) {
461 return false;
462 }
463 return false;
464 }
465
466 fn sectionSortCompare(context: void, left: *BinaryElfSection, right: *BinaryElfSection) bool {
467 _ = context;
468 return left.binaryOffset < right.binaryOffset;
469 }
470};
471
472fn writeBinaryElfSection(elf_file: File, out_file: File, section: *BinaryElfSection) !void {
473 try out_file.writeFileAll(elf_file, .{
474 .in_offset = section.elfOffset,
475 .in_len = section.fileSize,
476 });
477}
478
479const HexWriter = struct {
480 prev_addr: ?u32 = null,
481 out_file: File,
482
483 /// Max data bytes per line of output
484 const MAX_PAYLOAD_LEN: u8 = 16;
485
486 fn addressParts(address: u16) [2]u8 {
487 const msb: u8 = @truncate(address >> 8);
488 const lsb: u8 = @truncate(address);
489 return [2]u8{ msb, lsb };
490 }
491
492 const Record = struct {
493 const Type = enum(u8) {
494 Data = 0,
495 EOF = 1,
496 ExtendedSegmentAddress = 2,
497 ExtendedLinearAddress = 4,
498 };
499
500 address: u16,
501 payload: union(Type) {
502 Data: []const u8,
503 EOF: void,
504 ExtendedSegmentAddress: [2]u8,
505 ExtendedLinearAddress: [2]u8,
506 },
507
508 fn EOF() Record {
509 return Record{
510 .address = 0,
511 .payload = .EOF,
512 };
513 }
514
515 fn Data(address: u32, data: []const u8) Record {
516 return Record{
517 .address = @intCast(address % 0x10000),
518 .payload = .{ .Data = data },
519 };
520 }
521
522 fn Address(address: u32) Record {
523 assert(address > 0xFFFF);
524 const segment: u16 = @intCast(address / 0x10000);
525 if (address > 0xFFFFF) {
526 return Record{
527 .address = 0,
528 .payload = .{ .ExtendedLinearAddress = addressParts(segment) },
529 };
530 } else {
531 return Record{
532 .address = 0,
533 .payload = .{ .ExtendedSegmentAddress = addressParts(segment << 12) },
534 };
535 }
536 }
537
538 fn getPayloadBytes(self: *const Record) []const u8 {
539 return switch (self.payload) {
540 .Data => |d| d,
541 .EOF => @as([]const u8, &.{}),
542 .ExtendedSegmentAddress, .ExtendedLinearAddress => |*seg| seg,
543 };
544 }
545
546 fn checksum(self: Record) u8 {
547 const payload_bytes = self.getPayloadBytes();
548
549 var sum: u8 = @intCast(payload_bytes.len);
550 const parts = addressParts(self.address);
551 sum +%= parts[0];
552 sum +%= parts[1];
553 sum +%= @intFromEnum(self.payload);
554 for (payload_bytes) |byte| {
555 sum +%= byte;
556 }
557 return (sum ^ 0xFF) +% 1;
558 }
559
560 fn write(self: Record, file: File) File.WriteError!void {
561 const linesep = "\r\n";
562 // colon, (length, address, type, payload, checksum) as hex, CRLF
563 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
564 var outbuf: [BUFSIZE]u8 = undefined;
565 const payload_bytes = self.getPayloadBytes();
566 assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
567
568 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
569 @as(u8, @intCast(payload_bytes.len)),
570 self.address,
571 @intFromEnum(self.payload),
572 std.fmt.fmtSliceHexUpper(payload_bytes),
573 self.checksum(),
574 });
575 try file.writeAll(line);
576 }
577 };
578
579 pub fn writeSegment(self: *HexWriter, segment: *const BinaryElfSegment, elf_file: File) !void {
580 var buf: [MAX_PAYLOAD_LEN]u8 = undefined;
581 var bytes_read: usize = 0;
582 while (bytes_read < segment.fileSize) {
583 const row_address: u32 = @intCast(segment.physicalAddress + bytes_read);
584
585 const remaining = segment.fileSize - bytes_read;
586 const to_read: usize = @intCast(@min(remaining, MAX_PAYLOAD_LEN));
587 const did_read = try elf_file.preadAll(buf[0..to_read], segment.elfOffset + bytes_read);
588 if (did_read < to_read) return error.UnexpectedEOF;
589
590 try self.writeDataRow(row_address, buf[0..did_read]);
591
592 bytes_read += did_read;
593 }
594 }
595
596 fn writeDataRow(self: *HexWriter, address: u32, data: []const u8) File.WriteError!void {
597 const record = Record.Data(address, data);
598 if (address > 0xFFFF and (self.prev_addr == null or record.address != self.prev_addr.?)) {
599 try Record.Address(address).write(self.out_file);
600 }
601 try record.write(self.out_file);
602 self.prev_addr = @intCast(record.address + data.len);
603 }
604
605 fn writeEOF(self: HexWriter) File.WriteError!void {
606 try Record.EOF().write(self.out_file);
607 }
608};
609
610fn containsValidAddressRange(segments: []*BinaryElfSegment) bool {
611 const max_address = std.math.maxInt(u32);
612 for (segments) |segment| {
613 if (segment.fileSize > max_address or
614 segment.physicalAddress > max_address - segment.fileSize) return false;
615 }
616 return true;
617}
618
619fn padFile(f: File, opt_size: ?u64) !void {
620 const size = opt_size orelse return;
621 try f.setEndPos(size);
622}
623
624test "HexWriter.Record.Address has correct payload and checksum" {
625 const record = HexWriter.Record.Address(0x0800_0000);
626 const payload = record.getPayloadBytes();
627 const sum = record.checksum();
628 try std.testing.expect(sum == 0xF2);
629 try std.testing.expect(payload.len == 2);
630 try std.testing.expect(payload[0] == 8);
631 try std.testing.expect(payload[1] == 0);
632}
633
634test "containsValidAddressRange" {
635 var segment = BinaryElfSegment{
636 .physicalAddress = 0,
637 .virtualAddress = 0,
638 .elfOffset = 0,
639 .binaryOffset = 0,
640 .fileSize = 0,
641 .firstSection = null,
642 };
643 var buf: [1]*BinaryElfSegment = .{&segment};
644
645 // segment too big
646 segment.fileSize = std.math.maxInt(u32) + 1;
647 try std.testing.expect(!containsValidAddressRange(&buf));
648
649 // start address too big
650 segment.physicalAddress = std.math.maxInt(u32) + 1;
651 segment.fileSize = 2;
652 try std.testing.expect(!containsValidAddressRange(&buf));
653
654 // max address too big
655 segment.physicalAddress = std.math.maxInt(u32) - 1;
656 segment.fileSize = 2;
657 try std.testing.expect(!containsValidAddressRange(&buf));
658
659 // is ok
660 segment.physicalAddress = std.math.maxInt(u32) - 1;
661 segment.fileSize = 1;
662 try std.testing.expect(containsValidAddressRange(&buf));
663}
664
665// -------------
666// ELF to ELF stripping
667
668const StripElfOptions = struct {
669 extract_to: ?[]const u8 = null,
670 add_debuglink: ?[]const u8 = null,
671 strip_all: bool = false,
672 strip_debug: bool = false,
673 only_keep_debug: bool = false,
674 compress_debug: bool = false,
675};
676
677fn stripElf(
678 allocator: Allocator,
679 in_file: File,
680 out_file: File,
681 elf_hdr: elf.Header,
682 options: StripElfOptions,
683) !void {
684 const Filter = ElfFileHelper.Filter;
685 const DebugLink = ElfFileHelper.DebugLink;
686
687 const filter: Filter = filter: {
688 if (options.only_keep_debug) break :filter .debug;
689 if (options.strip_all) break :filter .program;
690 if (options.strip_debug) break :filter .program_and_symbols;
691 break :filter .all;
692 };
693
694 const filter_complement: ?Filter = blk: {
695 if (options.extract_to) |_| {
696 break :blk switch (filter) {
697 .program => .debug_and_symbols,
698 .debug => .program_and_symbols,
699 .program_and_symbols => .debug,
700 .debug_and_symbols => .program,
701 .all => fatal("zig objcopy: nothing to extract", .{}),
702 };
703 } else {
704 break :blk null;
705 }
706 };
707 const debuglink_path = path: {
708 if (options.add_debuglink) |path| break :path path;
709 if (options.extract_to) |path| break :path path;
710 break :path null;
711 };
712
713 switch (elf_hdr.is_64) {
714 inline else => |is_64| {
715 var elf_file = try ElfFile(is_64).parse(allocator, in_file, elf_hdr);
716 defer elf_file.deinit();
717
718 if (filter_complement) |flt| {
719 // write the .dbg file and close it, so it can be read back to compute the debuglink checksum.
720 const path = options.extract_to.?;
721 const dbg_file = std.fs.cwd().createFile(path, .{}) catch |err| {
722 fatal("zig objcopy: unable to create '{s}': {s}", .{ path, @errorName(err) });
723 };
724 defer dbg_file.close();
725
726 try elf_file.emit(allocator, dbg_file, in_file, .{ .section_filter = flt, .compress_debug = options.compress_debug });
727 }
728
729 const debuglink: ?DebugLink = if (debuglink_path) |path| ElfFileHelper.createDebugLink(path) else null;
730 try elf_file.emit(allocator, out_file, in_file, .{ .section_filter = filter, .debuglink = debuglink, .compress_debug = options.compress_debug });
731 },
732 }
733}
734
735// note: this is "a minimal effort implementation"
736// It doesn't support all possibile elf files: some sections type may need fixups, the program header may need fix up, ...
737// It was written for a specific use case (strip debug info to a sperate file, for linux 64-bits executables built with `zig` or `zig c++` )
738// It moves and reoders the sections as little as possible to avoid having to do fixups.
739// TODO: support non-native endianess
740
741fn ElfFile(comptime is_64: bool) type {
742 const Elf_Ehdr = if (is_64) elf.Elf64_Ehdr else elf.Elf32_Ehdr;
743 const Elf_Phdr = if (is_64) elf.Elf64_Phdr else elf.Elf32_Phdr;
744 const Elf_Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr;
745 const Elf_Chdr = if (is_64) elf.Elf64_Chdr else elf.Elf32_Chdr;
746 const Elf_Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;
747 const Elf_Verdef = if (is_64) elf.Elf64_Verdef else elf.Elf32_Verdef;
748 const Elf_OffSize = if (is_64) elf.Elf64_Off else elf.Elf32_Off;
749
750 return struct {
751 raw_elf_header: Elf_Ehdr,
752 program_segments: []const Elf_Phdr,
753 sections: []const Section,
754 arena: std.heap.ArenaAllocator,
755
756 const SectionCategory = ElfFileHelper.SectionCategory;
757 const section_memory_align = @alignOf(Elf_Sym); // most restrictive of what we may load in memory
758 const Section = struct {
759 section: Elf_Shdr,
760 name: []const u8 = "",
761 segment: ?*const Elf_Phdr = null, // if the section is used by a program segment (there can be more than one)
762 payload: ?[]align(section_memory_align) const u8 = null, // if we need the data in memory
763 category: SectionCategory = .none, // should the section be kept in the exe or stripped to the debug database, or both.
764 };
765
766 const Self = @This();
767
768 pub fn parse(gpa: Allocator, in_file: File, header: elf.Header) !Self {
769 var arena = std.heap.ArenaAllocator.init(gpa);
770 errdefer arena.deinit();
771 const allocator = arena.allocator();
772
773 var raw_header: Elf_Ehdr = undefined;
774 {
775 const bytes_read = try in_file.preadAll(std.mem.asBytes(&raw_header), 0);
776 if (bytes_read < @sizeOf(Elf_Ehdr))
777 return error.TRUNCATED_ELF;
778 }
779
780 // program header: list of segments
781 const program_segments = blk: {
782 if (@sizeOf(Elf_Phdr) != header.phentsize)
783 fatal("zig objcopy: unsuported ELF file, unexpected phentsize ({d})", .{header.phentsize});
784
785 const program_header = try allocator.alloc(Elf_Phdr, header.phnum);
786 const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(program_header), header.phoff);
787 if (bytes_read < @sizeOf(Elf_Phdr) * header.phnum)
788 return error.TRUNCATED_ELF;
789 break :blk program_header;
790 };
791
792 // section header
793 const sections = blk: {
794 if (@sizeOf(Elf_Shdr) != header.shentsize)
795 fatal("zig objcopy: unsuported ELF file, unexpected shentsize ({d})", .{header.shentsize});
796
797 const section_header = try allocator.alloc(Section, header.shnum);
798
799 const raw_section_header = try allocator.alloc(Elf_Shdr, header.shnum);
800 defer allocator.free(raw_section_header);
801 const bytes_read = try in_file.preadAll(std.mem.sliceAsBytes(raw_section_header), header.shoff);
802 if (bytes_read < @sizeOf(Elf_Phdr) * header.shnum)
803 return error.TRUNCATED_ELF;
804
805 for (section_header, raw_section_header) |*section, hdr| {
806 section.* = .{ .section = hdr };
807 }
808 break :blk section_header;
809 };
810
811 // load data to memory for some sections:
812 // string tables for access
813 // sections than need modifications when other sections move.
814 for (sections, 0..) |*section, idx| {
815 const need_data = switch (section.section.sh_type) {
816 elf.DT_VERSYM => true,
817 elf.SHT_SYMTAB, elf.SHT_DYNSYM => true,
818 else => false,
819 };
820 const need_strings = (idx == header.shstrndx);
821
822 if (need_data or need_strings) {
823 const buffer = try allocator.alignedAlloc(u8, section_memory_align, @intCast(section.section.sh_size));
824 const bytes_read = try in_file.preadAll(buffer, section.section.sh_offset);
825 if (bytes_read != section.section.sh_size) return error.TRUNCATED_ELF;
826 section.payload = buffer;
827 }
828 }
829
830 // fill-in sections info:
831 // resolve the name
832 // find if a program segment uses the section
833 // categorize sections usage (used by program segments, debug datadase, common metadata, symbol table)
834 for (sections) |*section| {
835 section.segment = for (program_segments) |*seg| {
836 if (sectionWithinSegment(section.section, seg.*)) break seg;
837 } else null;
838
839 if (section.section.sh_name != 0 and header.shstrndx != elf.SHN_UNDEF)
840 section.name = std.mem.span(@as([*:0]const u8, @ptrCast(&sections[header.shstrndx].payload.?[section.section.sh_name])));
841
842 const category_from_program: SectionCategory = if (section.segment != null) .exe else .debug;
843 section.category = switch (section.section.sh_type) {
844 elf.SHT_NOTE => .common,
845 elf.SHT_SYMTAB => .symbols, // "strip all" vs "strip only debug"
846 elf.SHT_DYNSYM => .exe,
847 elf.SHT_PROGBITS => cat: {
848 if (std.mem.eql(u8, section.name, ".comment")) break :cat .exe;
849 if (std.mem.eql(u8, section.name, ".gnu_debuglink")) break :cat .none;
850 break :cat category_from_program;
851 },
852 elf.SHT_LOPROC...elf.SHT_HIPROC => .common, // don't strip unknown sections
853 elf.SHT_LOUSER...elf.SHT_HIUSER => .common, // don't strip unknown sections
854 else => category_from_program,
855 };
856 }
857
858 sections[0].category = .common; // mandatory null section
859 if (header.shstrndx != elf.SHN_UNDEF)
860 sections[header.shstrndx].category = .common; // string table for the headers
861
862 // recursively propagate section categories to their linked sections, so that they are kept together
863 var dirty: u1 = 1;
864 while (dirty != 0) {
865 dirty = 0;
866
867 for (sections) |*section| {
868 if (section.section.sh_link != elf.SHN_UNDEF)
869 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_link].category, section.category);
870 if ((section.section.sh_flags & elf.SHF_INFO_LINK) != 0 and section.section.sh_info != elf.SHN_UNDEF)
871 dirty |= ElfFileHelper.propagateCategory(&sections[section.section.sh_info].category, section.category);
872 }
873 }
874
875 return Self{
876 .arena = arena,
877 .raw_elf_header = raw_header,
878 .program_segments = program_segments,
879 .sections = sections,
880 };
881 }
882
883 pub fn deinit(self: *Self) void {
884 self.arena.deinit();
885 }
886
887 const Filter = ElfFileHelper.Filter;
888 const DebugLink = ElfFileHelper.DebugLink;
889 const EmitElfOptions = struct {
890 section_filter: Filter = .all,
891 debuglink: ?DebugLink = null,
892 compress_debug: bool = false,
893 };
894 fn emit(self: *const Self, gpa: Allocator, out_file: File, in_file: File, options: EmitElfOptions) !void {
895 var arena = std.heap.ArenaAllocator.init(gpa);
896 defer arena.deinit();
897 const allocator = arena.allocator();
898
899 // when emitting the stripped exe:
900 // - unused sections are removed
901 // when emitting the debug file:
902 // - all sections are kept, but some are emptied and their types is changed to SHT_NOBITS
903 // the program header is kept unchanged. (`strip` does update it, but `eu-strip` does not, and it still works)
904
905 const Update = struct {
906 action: ElfFileHelper.Action,
907
908 // remap the indexs after omitting the filtered sections
909 remap_idx: u16,
910
911 // optionally overrides the payload from the source file
912 payload: ?[]align(section_memory_align) const u8 = null,
913 section: ?Elf_Shdr = null,
914 };
915 const sections_update = try allocator.alloc(Update, self.sections.len);
916 const new_shnum = blk: {
917 var next_idx: u16 = 0;
918 for (self.sections, sections_update) |section, *update| {
919 const action = ElfFileHelper.selectAction(section.category, options.section_filter);
920 const remap_idx = idx: {
921 if (action == .strip) break :idx elf.SHN_UNDEF;
922 next_idx += 1;
923 break :idx next_idx - 1;
924 };
925 update.* = Update{ .action = action, .remap_idx = remap_idx };
926 }
927
928 if (options.debuglink != null)
929 next_idx += 1;
930
931 break :blk next_idx;
932 };
933
934 // add a ".gnu_debuglink" to the string table if needed
935 const debuglink_name: u32 = blk: {
936 if (options.debuglink == null) break :blk elf.SHN_UNDEF;
937 if (self.raw_elf_header.e_shstrndx == elf.SHN_UNDEF)
938 fatal("zig objcopy: no strtab, cannot add the debuglink section", .{}); // TODO add the section if needed?
939
940 const strtab = &self.sections[self.raw_elf_header.e_shstrndx];
941 const update = &sections_update[self.raw_elf_header.e_shstrndx];
942
943 const name: []const u8 = ".gnu_debuglink";
944 const new_offset: u32 = @intCast(strtab.payload.?.len);
945 const buf = try allocator.alignedAlloc(u8, section_memory_align, new_offset + name.len + 1);
946 @memcpy(buf[0..new_offset], strtab.payload.?);
947 @memcpy(buf[new_offset..][0..name.len], name);
948 buf[new_offset + name.len] = 0;
949
950 assert(update.action == .keep);
951 update.payload = buf;
952
953 break :blk new_offset;
954 };
955
956 // maybe compress .debug sections
957 if (options.compress_debug) {
958 for (self.sections[1..], sections_update[1..]) |section, *update| {
959 if (update.action != .keep) continue;
960 if (!std.mem.startsWith(u8, section.name, ".debug_")) continue;
961 if ((section.section.sh_flags & elf.SHF_COMPRESSED) != 0) continue; // already compressed
962
963 const chdr = Elf_Chdr{
964 .ch_type = elf.COMPRESS.ZLIB,
965 .ch_size = section.section.sh_size,
966 .ch_addralign = section.section.sh_addralign,
967 };
968
969 const compressed_payload = try ElfFileHelper.tryCompressSection(allocator, in_file, section.section.sh_offset, section.section.sh_size, std.mem.asBytes(&chdr));
970 if (compressed_payload) |payload| {
971 update.payload = payload;
972 update.section = section.section;
973 update.section.?.sh_addralign = @alignOf(Elf_Chdr);
974 update.section.?.sh_size = @intCast(payload.len);
975 update.section.?.sh_flags |= elf.SHF_COMPRESSED;
976 }
977 }
978 }
979
980 var cmdbuf = std.ArrayList(ElfFileHelper.WriteCmd).init(allocator);
981 defer cmdbuf.deinit();
982 try cmdbuf.ensureUnusedCapacity(3 + new_shnum);
983 var eof_offset: Elf_OffSize = 0; // track the end of the data written so far.
984
985 // build the updated headers
986 // nb: updated_elf_header will be updated before the actual write
987 var updated_elf_header = self.raw_elf_header;
988 if (updated_elf_header.e_shstrndx != elf.SHN_UNDEF)
989 updated_elf_header.e_shstrndx = sections_update[updated_elf_header.e_shstrndx].remap_idx;
990 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = std.mem.asBytes(&updated_elf_header), .out_offset = 0 } });
991 eof_offset = @sizeOf(Elf_Ehdr);
992
993 // program header as-is.
994 // nb: for only-debug files, removing it appears to work, but is invalid by ELF specifcation.
995 {
996 assert(updated_elf_header.e_phoff == @sizeOf(Elf_Ehdr));
997 const data = std.mem.sliceAsBytes(self.program_segments);
998 assert(data.len == @as(usize, updated_elf_header.e_phentsize) * updated_elf_header.e_phnum);
999 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = data, .out_offset = updated_elf_header.e_phoff } });
1000 eof_offset = updated_elf_header.e_phoff + @as(Elf_OffSize, @intCast(data.len));
1001 }
1002
1003 // update sections and queue payload writes
1004 const updated_section_header = blk: {
1005 const dest_sections = try allocator.alloc(Elf_Shdr, new_shnum);
1006
1007 {
1008 // the ELF format doesn't specify the order for all sections.
1009 // this code only supports when they are in increasing file order.
1010 var offset: u64 = eof_offset;
1011 for (self.sections[1..]) |section| {
1012 if (section.section.sh_type == elf.SHT_NOBITS)
1013 continue;
1014 if (section.section.sh_offset < offset) {
1015 fatal("zig objcopy: unsuported ELF file", .{});
1016 }
1017 offset = section.section.sh_offset;
1018 }
1019 }
1020
1021 dest_sections[0] = self.sections[0].section;
1022
1023 var dest_section_idx: u32 = 1;
1024 for (self.sections[1..], sections_update[1..]) |section, update| {
1025 if (update.action == .strip) continue;
1026 assert(update.remap_idx == dest_section_idx);
1027
1028 const src = if (update.section) |*s| s else &section.section;
1029 const dest = &dest_sections[dest_section_idx];
1030 const payload = if (update.payload) |data| data else section.payload;
1031 dest_section_idx += 1;
1032
1033 dest.* = src.*;
1034
1035 if (src.sh_link != elf.SHN_UNDEF)
1036 dest.sh_link = sections_update[src.sh_link].remap_idx;
1037 if ((src.sh_flags & elf.SHF_INFO_LINK) != 0 and src.sh_info != elf.SHN_UNDEF)
1038 dest.sh_info = sections_update[src.sh_info].remap_idx;
1039
1040 if (payload) |data|
1041 dest.sh_size = @intCast(data.len);
1042
1043 const addralign = if (src.sh_addralign == 0 or dest.sh_type == elf.SHT_NOBITS) 1 else src.sh_addralign;
1044 dest.sh_offset = std.mem.alignForward(Elf_OffSize, eof_offset, addralign);
1045 if (src.sh_offset != dest.sh_offset and section.segment != null and update.action != .empty and dest.sh_type != elf.SHT_NOTE and dest.sh_type != elf.SHT_NOBITS) {
1046 if (src.sh_offset > dest.sh_offset) {
1047 dest.sh_offset = src.sh_offset; // add padding to avoid modifing the program segments
1048 } else {
1049 fatal("zig objcopy: cannot adjust program segments", .{});
1050 }
1051 }
1052 assert(dest.sh_addr % addralign == dest.sh_offset % addralign);
1053
1054 if (update.action == .empty)
1055 dest.sh_type = elf.SHT_NOBITS;
1056
1057 if (dest.sh_type != elf.SHT_NOBITS) {
1058 if (payload) |src_data| {
1059 // update sections payload and write
1060 const dest_data = switch (src.sh_type) {
1061 elf.DT_VERSYM => dst_data: {
1062 const data = try allocator.alignedAlloc(u8, section_memory_align, src_data.len);
1063 @memcpy(data, src_data);
1064
1065 const defs = @as([*]Elf_Verdef, @ptrCast(data))[0 .. @as(usize, @intCast(src.sh_size)) / @sizeOf(Elf_Verdef)];
1066 for (defs) |*def| {
1067 if (def.vd_ndx != elf.SHN_UNDEF)
1068 def.vd_ndx = sections_update[src.sh_info].remap_idx;
1069 }
1070
1071 break :dst_data data;
1072 },
1073 elf.SHT_SYMTAB, elf.SHT_DYNSYM => dst_data: {
1074 const data = try allocator.alignedAlloc(u8, section_memory_align, src_data.len);
1075 @memcpy(data, src_data);
1076
1077 const syms = @as([*]Elf_Sym, @ptrCast(data))[0 .. @as(usize, @intCast(src.sh_size)) / @sizeOf(Elf_Sym)];
1078 for (syms) |*sym| {
1079 if (sym.st_shndx != elf.SHN_UNDEF and sym.st_shndx < elf.SHN_LORESERVE)
1080 sym.st_shndx = sections_update[sym.st_shndx].remap_idx;
1081 }
1082
1083 break :dst_data data;
1084 },
1085 else => src_data,
1086 };
1087
1088 assert(dest_data.len == dest.sh_size);
1089 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = dest_data, .out_offset = dest.sh_offset } });
1090 eof_offset = dest.sh_offset + dest.sh_size;
1091 } else {
1092 // direct contents copy
1093 cmdbuf.appendAssumeCapacity(.{ .copy_range = .{ .in_offset = src.sh_offset, .len = dest.sh_size, .out_offset = dest.sh_offset } });
1094 eof_offset = dest.sh_offset + dest.sh_size;
1095 }
1096 } else {
1097 // account for alignment padding even in empty sections to keep logical section order
1098 eof_offset = dest.sh_offset;
1099 }
1100 }
1101
1102 // add a ".gnu_debuglink" section
1103 if (options.debuglink) |link| {
1104 const payload = payload: {
1105 const crc_offset = std.mem.alignForward(usize, link.name.len + 1, 4);
1106 const buf = try allocator.alignedAlloc(u8, 4, crc_offset + 4);
1107 @memcpy(buf[0..link.name.len], link.name);
1108 @memset(buf[link.name.len..crc_offset], 0);
1109 @memcpy(buf[crc_offset..], std.mem.asBytes(&link.crc32));
1110 break :payload buf;
1111 };
1112
1113 dest_sections[dest_section_idx] = Elf_Shdr{
1114 .sh_name = debuglink_name,
1115 .sh_type = elf.SHT_PROGBITS,
1116 .sh_flags = 0,
1117 .sh_addr = 0,
1118 .sh_offset = eof_offset,
1119 .sh_size = @intCast(payload.len),
1120 .sh_link = elf.SHN_UNDEF,
1121 .sh_info = elf.SHN_UNDEF,
1122 .sh_addralign = 4,
1123 .sh_entsize = 0,
1124 };
1125 dest_section_idx += 1;
1126
1127 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = payload, .out_offset = eof_offset } });
1128 eof_offset += @as(Elf_OffSize, @intCast(payload.len));
1129 }
1130
1131 assert(dest_section_idx == new_shnum);
1132 break :blk dest_sections;
1133 };
1134
1135 // write the section header at the tail
1136 {
1137 const offset = std.mem.alignForward(Elf_OffSize, eof_offset, @alignOf(Elf_Shdr));
1138
1139 const data = std.mem.sliceAsBytes(updated_section_header);
1140 assert(data.len == @as(usize, updated_elf_header.e_shentsize) * new_shnum);
1141 updated_elf_header.e_shoff = offset;
1142 updated_elf_header.e_shnum = new_shnum;
1143
1144 cmdbuf.appendAssumeCapacity(.{ .write_data = .{ .data = data, .out_offset = updated_elf_header.e_shoff } });
1145 }
1146
1147 try ElfFileHelper.write(allocator, out_file, in_file, cmdbuf.items);
1148 }
1149
1150 fn sectionWithinSegment(section: Elf_Shdr, segment: Elf_Phdr) bool {
1151 const file_size = if (section.sh_type == elf.SHT_NOBITS) 0 else section.sh_size;
1152 return segment.p_offset <= section.sh_offset and (segment.p_offset + segment.p_filesz) >= (section.sh_offset + file_size);
1153 }
1154 };
1155}
1156
1157const ElfFileHelper = struct {
1158 const DebugLink = struct { name: []const u8, crc32: u32 };
1159 const Filter = enum { all, program, debug, program_and_symbols, debug_and_symbols };
1160
1161 const SectionCategory = enum { common, exe, debug, symbols, none };
1162 fn propagateCategory(cur: *SectionCategory, new: SectionCategory) u1 {
1163 const cat: SectionCategory = switch (cur.*) {
1164 .none => new,
1165 .common => .common,
1166 .debug => switch (new) {
1167 .none, .debug => .debug,
1168 else => new,
1169 },
1170 .exe => switch (new) {
1171 .common => .common,
1172 .none, .debug, .exe => .exe,
1173 .symbols => .exe,
1174 },
1175 .symbols => switch (new) {
1176 .none, .common, .debug, .exe => unreachable,
1177 .symbols => .symbols,
1178 },
1179 };
1180
1181 if (cur.* != cat) {
1182 cur.* = cat;
1183 return 1;
1184 } else {
1185 return 0;
1186 }
1187 }
1188
1189 const Action = enum { keep, strip, empty };
1190 fn selectAction(category: SectionCategory, filter: Filter) Action {
1191 if (category == .none) return .strip;
1192 return switch (filter) {
1193 .all => switch (category) {
1194 .none => .strip,
1195 else => .keep,
1196 },
1197 .program => switch (category) {
1198 .common, .exe => .keep,
1199 else => .strip,
1200 },
1201 .program_and_symbols => switch (category) {
1202 .common, .exe, .symbols => .keep,
1203 else => .strip,
1204 },
1205 .debug => switch (category) {
1206 .exe, .symbols => .empty,
1207 .none => .strip,
1208 else => .keep,
1209 },
1210 .debug_and_symbols => switch (category) {
1211 .exe => .empty,
1212 .none => .strip,
1213 else => .keep,
1214 },
1215 };
1216 }
1217
1218 const WriteCmd = union(enum) {
1219 copy_range: struct { in_offset: u64, len: u64, out_offset: u64 },
1220 write_data: struct { data: []const u8, out_offset: u64 },
1221 };
1222 fn write(allocator: Allocator, out_file: File, in_file: File, cmds: []const WriteCmd) !void {
1223 // consolidate holes between writes:
1224 // by coping original padding data from in_file (by fusing contiguous ranges)
1225 // by writing zeroes otherwise
1226 const zeroes = [1]u8{0} ** 4096;
1227 var consolidated = std.ArrayList(WriteCmd).init(allocator);
1228 defer consolidated.deinit();
1229 try consolidated.ensureUnusedCapacity(cmds.len * 2);
1230 var offset: u64 = 0;
1231 var fused_cmd: ?WriteCmd = null;
1232 for (cmds) |cmd| {
1233 switch (cmd) {
1234 .write_data => |data| {
1235 assert(data.out_offset >= offset);
1236 if (fused_cmd) |prev| {
1237 consolidated.appendAssumeCapacity(prev);
1238 fused_cmd = null;
1239 }
1240 if (data.out_offset > offset) {
1241 consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(data.out_offset - offset)], .out_offset = offset } });
1242 }
1243 consolidated.appendAssumeCapacity(cmd);
1244 offset = data.out_offset + data.data.len;
1245 },
1246 .copy_range => |range| {
1247 assert(range.out_offset >= offset);
1248 if (fused_cmd) |prev| {
1249 if (range.in_offset >= prev.copy_range.in_offset + prev.copy_range.len and (range.out_offset - prev.copy_range.out_offset == range.in_offset - prev.copy_range.in_offset)) {
1250 fused_cmd = .{ .copy_range = .{
1251 .in_offset = prev.copy_range.in_offset,
1252 .out_offset = prev.copy_range.out_offset,
1253 .len = (range.out_offset + range.len) - prev.copy_range.out_offset,
1254 } };
1255 } else {
1256 consolidated.appendAssumeCapacity(prev);
1257 if (range.out_offset > offset) {
1258 consolidated.appendAssumeCapacity(.{ .write_data = .{ .data = zeroes[0..@intCast(range.out_offset - offset)], .out_offset = offset } });
1259 }
1260 fused_cmd = cmd;
1261 }
1262 } else {
1263 fused_cmd = cmd;
1264 }
1265 offset = range.out_offset + range.len;
1266 },
1267 }
1268 }
1269 if (fused_cmd) |cmd| {
1270 consolidated.appendAssumeCapacity(cmd);
1271 }
1272
1273 // write the output file
1274 for (consolidated.items) |cmd| {
1275 switch (cmd) {
1276 .write_data => |data| {
1277 var iovec = [_]std.os.iovec_const{.{ .iov_base = data.data.ptr, .iov_len = data.data.len }};
1278 try out_file.pwritevAll(&iovec, data.out_offset);
1279 },
1280 .copy_range => |range| {
1281 const copied_bytes = try in_file.copyRangeAll(range.in_offset, out_file, range.out_offset, range.len);
1282 if (copied_bytes < range.len) return error.TRUNCATED_ELF;
1283 },
1284 }
1285 }
1286 }
1287
1288 fn tryCompressSection(allocator: Allocator, in_file: File, offset: u64, size: u64, prefix: []const u8) !?[]align(8) const u8 {
1289 if (size < prefix.len) return null;
1290
1291 try in_file.seekTo(offset);
1292 var section_reader = std.io.limitedReader(in_file.reader(), size);
1293
1294 // allocate as large as decompressed data. if the compression doesn't fit, keep the data uncompressed.
1295 const compressed_data = try allocator.alignedAlloc(u8, 8, @intCast(size));
1296 var compressed_stream = std.io.fixedBufferStream(compressed_data);
1297
1298 try compressed_stream.writer().writeAll(prefix);
1299
1300 {
1301 var compressor = try std.compress.zlib.compressor(compressed_stream.writer(), .{});
1302
1303 var buf: [8000]u8 = undefined;
1304 while (true) {
1305 const bytes_read = try section_reader.read(&buf);
1306 if (bytes_read == 0) break;
1307 const bytes_written = compressor.write(buf[0..bytes_read]) catch |err| switch (err) {
1308 error.NoSpaceLeft => {
1309 allocator.free(compressed_data);
1310 return null;
1311 },
1312 else => return err,
1313 };
1314 std.debug.assert(bytes_written == bytes_read);
1315 }
1316 compressor.finish() catch |err| switch (err) {
1317 error.NoSpaceLeft => {
1318 allocator.free(compressed_data);
1319 return null;
1320 },
1321 else => return err,
1322 };
1323 }
1324
1325 const compressed_len: usize = @intCast(compressed_stream.getPos() catch unreachable);
1326 const data = allocator.realloc(compressed_data, compressed_len) catch compressed_data;
1327 return data[0..compressed_len];
1328 }
1329
1330 fn createDebugLink(path: []const u8) DebugLink {
1331 const file = std.fs.cwd().openFile(path, .{}) catch |err| {
1332 fatal("zig objcopy: could not open `{s}`: {s}\n", .{ path, @errorName(err) });
1333 };
1334 defer file.close();
1335
1336 const crc = ElfFileHelper.computeFileCrc(file) catch |err| {
1337 fatal("zig objcopy: could not read `{s}`: {s}\n", .{ path, @errorName(err) });
1338 };
1339 return .{
1340 .name = std.fs.path.basename(path),
1341 .crc32 = crc,
1342 };
1343 }
1344
1345 fn computeFileCrc(file: File) !u32 {
1346 var buf: [8000]u8 = undefined;
1347
1348 try file.seekTo(0);
1349 var hasher = std.hash.Crc32.init();
1350 while (true) {
1351 const bytes_read = try file.read(&buf);
1352 if (bytes_read == 0) break;
1353 hasher.update(buf[0..bytes_read]);
1354 }
1355 return hasher.final();
1356 }
1357};