master
1entries: std.ArrayList(Entry) = .empty,
2buffer: std.ArrayList(u8) = .empty,
3
4pub const Entry = struct {
5 offset: u64,
6 segment_id: u8,
7
8 pub fn lessThan(ctx: void, entry: Entry, other: Entry) bool {
9 _ = ctx;
10 if (entry.segment_id == other.segment_id) {
11 return entry.offset < other.offset;
12 }
13 return entry.segment_id < other.segment_id;
14 }
15};
16
17pub fn deinit(rebase: *Rebase, gpa: Allocator) void {
18 rebase.entries.deinit(gpa);
19 rebase.buffer.deinit(gpa);
20}
21
22pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void {
23 const tracy = trace(@src());
24 defer tracy.end();
25
26 const gpa = macho_file.base.comp.gpa;
27
28 var objects = try std.array_list.Managed(File.Index).initCapacity(gpa, macho_file.objects.items.len + 2);
29 defer objects.deinit();
30 objects.appendSliceAssumeCapacity(macho_file.objects.items);
31 if (macho_file.getZigObject()) |obj| objects.appendAssumeCapacity(obj.index);
32 if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
33
34 for (objects.items) |index| {
35 const file = macho_file.getFile(index).?;
36 for (file.getAtoms()) |atom_index| {
37 const atom = file.getAtom(atom_index) orelse continue;
38 if (!atom.isAlive()) continue;
39 if (atom.getInputSection(macho_file).isZerofill()) continue;
40 const atom_addr = atom.getAddress(macho_file);
41 const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
42 const seg = macho_file.segments.items[seg_id];
43 for (atom.getRelocs(macho_file)) |rel| {
44 if (rel.type != .unsigned or rel.meta.length != 3) continue;
45 if (rel.tag == .@"extern") {
46 const sym = rel.getTargetSymbol(atom.*, macho_file);
47 if (sym.isTlvInit(macho_file)) continue;
48 if (sym.flags.import) continue;
49 }
50 const rel_offset = rel.offset - atom.off;
51 try rebase.entries.append(gpa, .{
52 .offset = atom_addr + rel_offset - seg.vmaddr,
53 .segment_id = seg_id,
54 });
55 }
56 }
57 }
58
59 if (macho_file.got_sect_index) |sid| {
60 const seg_id = macho_file.sections.items(.segment_id)[sid];
61 const seg = macho_file.segments.items[seg_id];
62 for (macho_file.got.symbols.items, 0..) |ref, idx| {
63 const sym = ref.getSymbol(macho_file).?;
64 const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
65 if (!sym.flags.import) {
66 try rebase.entries.append(gpa, .{
67 .offset = addr - seg.vmaddr,
68 .segment_id = seg_id,
69 });
70 }
71 }
72 }
73
74 if (macho_file.la_symbol_ptr_sect_index) |sid| {
75 const sect = macho_file.sections.items(.header)[sid];
76 const seg_id = macho_file.sections.items(.segment_id)[sid];
77 const seg = macho_file.segments.items[seg_id];
78 for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
79 const sym = ref.getSymbol(macho_file).?;
80 const addr = sect.addr + idx * @sizeOf(u64);
81 const rebase_entry = Rebase.Entry{
82 .offset = addr - seg.vmaddr,
83 .segment_id = seg_id,
84 };
85 if ((sym.flags.import and !sym.flags.weak) or !sym.flags.import) {
86 try rebase.entries.append(gpa, rebase_entry);
87 }
88 }
89 }
90
91 if (macho_file.tlv_ptr_sect_index) |sid| {
92 const seg_id = macho_file.sections.items(.segment_id)[sid];
93 const seg = macho_file.segments.items[seg_id];
94 for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
95 const sym = ref.getSymbol(macho_file).?;
96 const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
97 if (!sym.flags.import) {
98 try rebase.entries.append(gpa, .{
99 .offset = addr - seg.vmaddr,
100 .segment_id = seg_id,
101 });
102 }
103 }
104 }
105
106 try rebase.finalize(gpa);
107 macho_file.dyld_info_cmd.rebase_size = mem.alignForward(u32, @intCast(rebase.buffer.items.len), @alignOf(u64));
108}
109
110fn finalize(rebase: *Rebase, gpa: Allocator) !void {
111 if (rebase.entries.items.len == 0) return;
112
113 log.debug("rebase opcodes", .{});
114
115 std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan);
116
117 var allocating: std.Io.Writer.Allocating = .fromArrayList(gpa, &rebase.buffer);
118 defer rebase.buffer = allocating.toArrayList();
119 const writer = &allocating.writer;
120
121 try setTypePointer(writer);
122
123 var start: usize = 0;
124 var seg_id: ?u8 = null;
125 for (rebase.entries.items, 0..) |entry, i| {
126 if (seg_id != null and seg_id.? == entry.segment_id) continue;
127 try finalizeSegment(rebase.entries.items[start..i], writer);
128 seg_id = entry.segment_id;
129 start = i;
130 }
131
132 try finalizeSegment(rebase.entries.items[start..], writer);
133 try done(writer);
134}
135
136fn finalizeSegment(entries: []const Entry, writer: anytype) !void {
137 if (entries.len == 0) return;
138
139 const segment_id = entries[0].segment_id;
140 var offset = entries[0].offset;
141 try setSegmentOffset(segment_id, offset, writer);
142
143 var count: usize = 0;
144 var skip: u64 = 0;
145 var state: enum {
146 start,
147 times,
148 times_skip,
149 } = .times;
150
151 var i: usize = 0;
152 while (i < entries.len) : (i += 1) {
153 log.debug("{x}, {d}, {x}, {s}", .{ offset, count, skip, @tagName(state) });
154 const current_offset = entries[i].offset;
155 log.debug(" => {x}", .{current_offset});
156 switch (state) {
157 .start => {
158 if (offset < current_offset) {
159 const delta = current_offset - offset;
160 try addAddr(delta, writer);
161 offset += delta;
162 }
163 state = .times;
164 offset += @sizeOf(u64);
165 count = 1;
166 },
167 .times => {
168 const delta = current_offset - offset;
169 if (delta == 0) {
170 count += 1;
171 offset += @sizeOf(u64);
172 continue;
173 }
174 if (count == 1) {
175 state = .times_skip;
176 skip = delta;
177 offset += skip;
178 i -= 1;
179 } else {
180 try rebaseTimes(count, writer);
181 state = .start;
182 i -= 1;
183 }
184 },
185 .times_skip => {
186 if (current_offset < offset) {
187 count -= 1;
188 if (count == 1) {
189 try rebaseAddAddr(skip, writer);
190 } else {
191 try rebaseTimesSkip(count, skip, writer);
192 }
193 state = .start;
194 offset = offset - (@sizeOf(u64) + skip);
195 i -= 2;
196 continue;
197 }
198
199 const delta = current_offset - offset;
200 if (delta == 0) {
201 count += 1;
202 offset += @sizeOf(u64) + skip;
203 } else {
204 try rebaseTimesSkip(count, skip, writer);
205 state = .start;
206 i -= 1;
207 }
208 },
209 }
210 }
211
212 switch (state) {
213 .start => unreachable,
214 .times => {
215 try rebaseTimes(count, writer);
216 },
217 .times_skip => {
218 try rebaseTimesSkip(count, skip, writer);
219 },
220 }
221}
222
223fn setTypePointer(writer: anytype) !void {
224 log.debug(">>> set type: {d}", .{macho.REBASE_TYPE_POINTER});
225 try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @as(u4, @truncate(macho.REBASE_TYPE_POINTER)));
226}
227
228fn setSegmentOffset(segment_id: u8, offset: u64, writer: anytype) !void {
229 log.debug(">>> set segment: {d} and offset: {x}", .{ segment_id, offset });
230 try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @as(u4, @truncate(segment_id)));
231 try writer.writeUleb128(offset);
232}
233
234fn rebaseAddAddr(addr: u64, writer: anytype) !void {
235 log.debug(">>> rebase with add: {x}", .{addr});
236 try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB);
237 try writer.writeUleb128(addr);
238}
239
240fn rebaseTimes(count: usize, writer: anytype) !void {
241 log.debug(">>> rebase with count: {d}", .{count});
242 if (count <= 0xf) {
243 try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @as(u4, @truncate(count)));
244 } else {
245 try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES);
246 try writer.writeUleb128(count);
247 }
248}
249
250fn rebaseTimesSkip(count: usize, skip: u64, writer: anytype) !void {
251 log.debug(">>> rebase with count: {d} and skip: {x}", .{ count, skip });
252 try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB);
253 try writer.writeUleb128(count);
254 try writer.writeUleb128(skip);
255}
256
257fn addAddr(addr: u64, writer: anytype) !void {
258 log.debug(">>> add: {x}", .{addr});
259 if (std.mem.isAlignedGeneric(u64, addr, @sizeOf(u64))) {
260 const imm = @divExact(addr, @sizeOf(u64));
261 if (imm <= 0xf) {
262 try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | @as(u4, @truncate(imm)));
263 return;
264 }
265 }
266 try writer.writeByte(macho.REBASE_OPCODE_ADD_ADDR_ULEB);
267 try writer.writeUleb128(addr);
268}
269
270fn done(writer: anytype) !void {
271 log.debug(">>> done", .{});
272 try writer.writeByte(macho.REBASE_OPCODE_DONE);
273}
274
275pub fn write(rebase: Rebase, writer: anytype) !void {
276 try writer.writeAll(rebase.buffer.items);
277}
278
279test "rebase - no entries" {
280 const gpa = testing.allocator;
281
282 var rebase = Rebase{};
283 defer rebase.deinit(gpa);
284
285 try rebase.finalize(gpa);
286 try testing.expectEqual(0, rebase.buffer.items.len);
287}
288
289test "rebase - single entry" {
290 const gpa = testing.allocator;
291
292 var rebase = Rebase{};
293 defer rebase.deinit(gpa);
294 try rebase.entries.append(gpa, .{
295 .segment_id = 1,
296 .offset = 0x10,
297 });
298 try rebase.finalize(gpa);
299 try testing.expectEqualSlices(u8, &[_]u8{
300 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
301 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
302 0x10,
303 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
304 macho.REBASE_OPCODE_DONE,
305 }, rebase.buffer.items);
306}
307
308test "rebase - emitTimes - IMM" {
309 const gpa = testing.allocator;
310
311 var rebase = Rebase{};
312 defer rebase.deinit(gpa);
313
314 var i: u64 = 0;
315 while (i < 10) : (i += 1) {
316 try rebase.entries.append(gpa, .{
317 .segment_id = 1,
318 .offset = i * @sizeOf(u64),
319 });
320 }
321
322 try rebase.finalize(gpa);
323
324 try testing.expectEqualSlices(u8, &[_]u8{
325 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
326 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
327 0x0,
328 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 10,
329 macho.REBASE_OPCODE_DONE,
330 }, rebase.buffer.items);
331}
332
333test "rebase - emitTimes - ULEB" {
334 const gpa = testing.allocator;
335
336 var rebase = Rebase{};
337 defer rebase.deinit(gpa);
338
339 var i: u64 = 0;
340 while (i < 100) : (i += 1) {
341 try rebase.entries.append(gpa, .{
342 .segment_id = 1,
343 .offset = i * @sizeOf(u64),
344 });
345 }
346
347 try rebase.finalize(gpa);
348
349 try testing.expectEqualSlices(u8, &[_]u8{
350 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
351 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
352 0x0,
353 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
354 0x64,
355 macho.REBASE_OPCODE_DONE,
356 }, rebase.buffer.items);
357}
358
359test "rebase - emitTimes followed by addAddr followed by emitTimes" {
360 const gpa = testing.allocator;
361
362 var rebase = Rebase{};
363 defer rebase.deinit(gpa);
364
365 var offset: u64 = 0;
366 var i: u64 = 0;
367 while (i < 15) : (i += 1) {
368 try rebase.entries.append(gpa, .{
369 .segment_id = 1,
370 .offset = offset,
371 });
372 offset += @sizeOf(u64);
373 }
374
375 offset += @sizeOf(u64);
376
377 try rebase.entries.append(gpa, .{
378 .segment_id = 1,
379 .offset = offset,
380 });
381
382 try rebase.finalize(gpa);
383
384 try testing.expectEqualSlices(u8, &[_]u8{
385 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
386 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
387 0x0,
388 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 15,
389 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
390 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
391 macho.REBASE_OPCODE_DONE,
392 }, rebase.buffer.items);
393}
394
395test "rebase - emitTimesSkip" {
396 const gpa = testing.allocator;
397
398 var rebase = Rebase{};
399 defer rebase.deinit(gpa);
400
401 var offset: u64 = 0;
402 var i: u64 = 0;
403 while (i < 15) : (i += 1) {
404 try rebase.entries.append(gpa, .{
405 .segment_id = 1,
406 .offset = offset,
407 });
408 offset += 2 * @sizeOf(u64);
409 }
410
411 try rebase.finalize(gpa);
412
413 try testing.expectEqualSlices(u8, &[_]u8{
414 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
415 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
416 0x0,
417 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
418 0xf,
419 0x8,
420 macho.REBASE_OPCODE_DONE,
421 }, rebase.buffer.items);
422}
423
424test "rebase - complex" {
425 const gpa = testing.allocator;
426
427 var rebase = Rebase{};
428 defer rebase.deinit(gpa);
429
430 try rebase.entries.append(gpa, .{
431 .segment_id = 1,
432 .offset = 0,
433 });
434 try rebase.entries.append(gpa, .{
435 .segment_id = 1,
436 .offset = 0x10,
437 });
438 try rebase.entries.append(gpa, .{
439 .segment_id = 1,
440 .offset = 0x40,
441 });
442 try rebase.entries.append(gpa, .{
443 .segment_id = 1,
444 .offset = 0x48,
445 });
446 try rebase.entries.append(gpa, .{
447 .segment_id = 1,
448 .offset = 0x50,
449 });
450 try rebase.entries.append(gpa, .{
451 .segment_id = 1,
452 .offset = 0x58,
453 });
454 try rebase.entries.append(gpa, .{
455 .segment_id = 1,
456 .offset = 0x70,
457 });
458 try rebase.finalize(gpa);
459
460 try testing.expectEqualSlices(u8, &[_]u8{
461 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
462 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
463 0x0,
464 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
465 0x2,
466 0x8,
467 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 4,
468 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 4,
469 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 2,
470 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
471 macho.REBASE_OPCODE_DONE,
472 }, rebase.buffer.items);
473}
474
475test "rebase - complex 2" {
476 const gpa = testing.allocator;
477
478 var rebase = Rebase{};
479 defer rebase.deinit(gpa);
480
481 try rebase.entries.append(gpa, .{
482 .segment_id = 1,
483 .offset = 0,
484 });
485 try rebase.entries.append(gpa, .{
486 .segment_id = 1,
487 .offset = 0x10,
488 });
489 try rebase.entries.append(gpa, .{
490 .segment_id = 1,
491 .offset = 0x28,
492 });
493 try rebase.entries.append(gpa, .{
494 .segment_id = 1,
495 .offset = 0x48,
496 });
497 try rebase.entries.append(gpa, .{
498 .segment_id = 1,
499 .offset = 0x78,
500 });
501 try rebase.entries.append(gpa, .{
502 .segment_id = 1,
503 .offset = 0xb8,
504 });
505 try rebase.entries.append(gpa, .{
506 .segment_id = 2,
507 .offset = 0x0,
508 });
509 try rebase.entries.append(gpa, .{
510 .segment_id = 2,
511 .offset = 0x8,
512 });
513 try rebase.entries.append(gpa, .{
514 .segment_id = 2,
515 .offset = 0x10,
516 });
517 try rebase.entries.append(gpa, .{
518 .segment_id = 2,
519 .offset = 0x18,
520 });
521 try rebase.entries.append(gpa, .{
522 .segment_id = 3,
523 .offset = 0x0,
524 });
525 try rebase.entries.append(gpa, .{
526 .segment_id = 3,
527 .offset = 0x20,
528 });
529 try rebase.entries.append(gpa, .{
530 .segment_id = 3,
531 .offset = 0x40,
532 });
533 try rebase.entries.append(gpa, .{
534 .segment_id = 3,
535 .offset = 0x60,
536 });
537 try rebase.entries.append(gpa, .{
538 .segment_id = 3,
539 .offset = 0x68,
540 });
541 try rebase.finalize(gpa);
542
543 try testing.expectEqualSlices(u8, &[_]u8{
544 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
545 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
546 0x0,
547 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
548 0x2,
549 0x8,
550 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
551 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
552 0x2,
553 0x18,
554 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 2,
555 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
556 0x2,
557 0x38,
558 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
559 0x0,
560 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 4,
561 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 3,
562 0x0,
563 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
564 0x3,
565 0x18,
566 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 2,
567 macho.REBASE_OPCODE_DONE,
568 }, rebase.buffer.items);
569}
570
571test "rebase - composite" {
572 const gpa = testing.allocator;
573
574 var rebase = Rebase{};
575 defer rebase.deinit(gpa);
576
577 try rebase.entries.append(gpa, .{
578 .segment_id = 1,
579 .offset = 0x8,
580 });
581 try rebase.entries.append(gpa, .{
582 .segment_id = 1,
583 .offset = 0x38,
584 });
585 try rebase.entries.append(gpa, .{
586 .segment_id = 1,
587 .offset = 0xa0,
588 });
589 try rebase.entries.append(gpa, .{
590 .segment_id = 1,
591 .offset = 0xa8,
592 });
593 try rebase.entries.append(gpa, .{
594 .segment_id = 1,
595 .offset = 0xb0,
596 });
597 try rebase.entries.append(gpa, .{
598 .segment_id = 1,
599 .offset = 0xc0,
600 });
601 try rebase.entries.append(gpa, .{
602 .segment_id = 1,
603 .offset = 0xc8,
604 });
605 try rebase.entries.append(gpa, .{
606 .segment_id = 1,
607 .offset = 0xd0,
608 });
609 try rebase.entries.append(gpa, .{
610 .segment_id = 1,
611 .offset = 0xd8,
612 });
613 try rebase.entries.append(gpa, .{
614 .segment_id = 1,
615 .offset = 0xe0,
616 });
617 try rebase.entries.append(gpa, .{
618 .segment_id = 1,
619 .offset = 0xe8,
620 });
621 try rebase.entries.append(gpa, .{
622 .segment_id = 1,
623 .offset = 0xf0,
624 });
625 try rebase.entries.append(gpa, .{
626 .segment_id = 1,
627 .offset = 0xf8,
628 });
629 try rebase.entries.append(gpa, .{
630 .segment_id = 1,
631 .offset = 0x108,
632 });
633 try rebase.finalize(gpa);
634
635 try testing.expectEqualSlices(u8, &[_]u8{
636 macho.REBASE_OPCODE_SET_TYPE_IMM | macho.REBASE_TYPE_POINTER,
637 macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 1,
638 0x8,
639 macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
640 0x2,
641 0x28,
642 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 7,
643 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 3,
644 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
645 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 8,
646 macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED | 1,
647 macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1,
648 macho.REBASE_OPCODE_DONE,
649 }, rebase.buffer.items);
650}
651
652const std = @import("std");
653const assert = std.debug.assert;
654const log = std.log.scoped(.link_dyld_info);
655const macho = std.macho;
656const mem = std.mem;
657const testing = std.testing;
658const Allocator = mem.Allocator;
659const Writer = std.Io.Writer;
660
661const trace = @import("../../../tracy.zig").trace;
662const File = @import("../file.zig").File;
663const MachO = @import("../../MachO.zig");
664const Rebase = @This();