Skip to content

Commit be4d469

Browse files
sstricklCommit Bot
authored and
Commit Bot
committed
[pkg/dart2native] Refactor MachO parsing.
* Move parsing functions to static methods of the classes that represent the data being parsed. * Move calculations for adjusting offsets in load commands into the corresponding classes. * Make function to insert a new payload segment an instance method of MachOFile. * Remove unnecessary abstractions and definitions for MachO load commands that we don't need to parse. These refactorings are being done to make later changes easier to review, by first lifting the initial refactorings out into a separate CL. Issue: #49783 Change-Id: I133eb368cbb9ee0d8e4f3998ba1a0bbe8555b8aa Cq-Include-Trybots: luci.dart.try:analyzer-mac-release-try,dart-sdk-mac-arm64-try,dart-sdk-mac-try,pkg-mac-release-arm64-try,pkg-mac-release-try,vm-kernel-precomp-mac-product-x64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256821 Reviewed-by: Daco Harkes <[email protected]> Commit-Queue: Tess Strickland <[email protected]> Reviewed-by: Ryan Macnak <[email protected]>
1 parent ff212e8 commit be4d469

File tree

7 files changed

+1638
-2834
lines changed

7 files changed

+1638
-2834
lines changed

pkg/dart2native/lib/dart2native_macho.dart

Lines changed: 25 additions & 243 deletions
Original file line numberDiff line numberDiff line change
@@ -2,244 +2,15 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:convert';
65
import 'dart:io';
76
import 'dart:math';
8-
import 'dart:typed_data';
97

10-
import 'package:dart2native/macho.dart';
11-
import 'package:dart2native/macho_parser.dart';
12-
13-
const String kSnapshotSegmentName = "__CUSTOM";
14-
const String kSnapshotSectionName = "__dart_app_snap";
15-
const int kMinimumSegmentSize = 0x4000;
16-
// Since arm64 macOS has 16K pages, which is larger than the 4K pages on x64
17-
// macOS, we use this larger page size to ensure the MachO file is aligned
18-
// properly on all architectures.
19-
const int kSegmentAlignment = 0x4000;
20-
21-
int align(int size, int base) {
22-
final int over = size % base;
23-
if (over != 0) {
24-
return size + (base - over);
25-
}
26-
return size;
27-
}
28-
29-
// Utility for aligning parts of MachO headers to the defined sizes.
30-
int vmSizeAlign(int size) {
31-
return align(max(size, kMinimumSegmentSize), kSegmentAlignment);
32-
}
33-
34-
// Returns value + amount only if the original value is within the bounds
35-
// defined by [withinStart, withinStart + withinSize).
36-
Uint32 addIfWithin(
37-
Uint32 value, Uint64 amount, Uint64 withinStart, Uint64 withinSize) {
38-
final intWithinStart = withinStart.asInt();
39-
final intWithinSize = withinSize.asInt();
40-
41-
if (value >= intWithinStart && value < (intWithinStart + intWithinSize)) {
42-
return (value.asUint64() + amount).asUint32();
43-
} else {
44-
return value;
45-
}
46-
}
47-
48-
// Trims a bytestring that an arbitrary number of null characters on the end of
49-
// it.
50-
String trimmedBytestring(Uint8List bytestring) {
51-
return String.fromCharCodes(bytestring.takeWhile((value) => value != 0));
52-
}
8+
import './macho.dart';
9+
import './macho_utils.dart';
5310

5411
// Simplifies casting so we get null values back instead of exceptions.
5512
T? cast<T>(x) => x is T ? x : null;
5613

57-
// Inserts a segment definition into a MachOFile. This does NOT insert the
58-
// actual segment into the file. It only inserts the definition of that segment
59-
// into the MachO header.
60-
//
61-
// In addition to simply specifying the definition for the segment, this
62-
// function also moves the existing __LINKEDIT segment to the end of the header
63-
// definition as is required by the MachO specification (or at least MacOS's
64-
// implementation of it). In doing so there are several offsets in the original
65-
// __LINKEDIT segment that must be updated to point to their new location
66-
// because the __LINKEDIT segment and sections are now in a different
67-
// place. This function takes care of those shifts as well.
68-
//
69-
// Returns the original, unmodified, __LINKEDIT segment.
70-
Future<MachOSegmentCommand64> insertSegmentDefinition(MachOFile file,
71-
File segment, String segmentName, String sectionName) async {
72-
// Load in the data to be inserted.
73-
final segmentData = await segment.readAsBytes();
74-
75-
// Find the existing __LINKEDIT segment
76-
final linkedit = cast<MachOSegmentCommand64>(file.commands
77-
.where((segment) =>
78-
segment.asType() is MachOSegmentCommand64 &&
79-
MachOConstants.SEG_LINKEDIT ==
80-
trimmedBytestring((segment as MachOSegmentCommand64).segname))
81-
.first);
82-
83-
final linkeditIndex = file.commands.indexWhere((segment) =>
84-
segment.asType() is MachOSegmentCommand64 &&
85-
MachOConstants.SEG_LINKEDIT ==
86-
trimmedBytestring((segment as MachOSegmentCommand64).segname));
87-
88-
if (linkedit == null) {
89-
throw FormatException(
90-
"Could not find a __LINKEDIT section in the specified binary.");
91-
} else {
92-
// Create the new segment.
93-
final Uint8List segname = Uint8List(16);
94-
segname.setRange(0, segmentName.length, ascii.encode(segmentName));
95-
segname.fillRange(segmentName.length, 16, 0);
96-
97-
final Uint64 vmaddr = linkedit.vmaddr;
98-
final Uint64 vmsize = Uint64(vmSizeAlign(segmentData.length));
99-
final Uint64 fileoff = linkedit.fileoff;
100-
final Uint64 filesize = vmsize;
101-
final Int32 maxprot = MachOConstants.VM_PROT_READ;
102-
final Int32 initprot = maxprot;
103-
final Uint32 nsects = Uint32(1);
104-
105-
final Uint8List sectname = Uint8List(16);
106-
sectname.setRange(0, sectionName.length, ascii.encode(sectionName));
107-
sectname.fillRange(sectionName.length, 16, 0);
108-
109-
final Uint64 addr = vmaddr;
110-
final Uint64 size = Uint64(segmentData.length);
111-
final Uint32 offset = fileoff.asUint32();
112-
final Uint32 flags = MachOConstants.S_REGULAR;
113-
114-
final Uint32 zero = Uint32(0);
115-
116-
final loadCommandDefinitionSize = 4 * 2;
117-
final sectionDefinitionSize = 16 * 2 + 8 * 2 + 4 * 8;
118-
final segmentDefinitionSize = 16 + 8 * 4 + 4 * 4;
119-
final commandSize = loadCommandDefinitionSize +
120-
segmentDefinitionSize +
121-
sectionDefinitionSize;
122-
123-
final loadCommand =
124-
MachOLoadCommand(MachOConstants.LC_SEGMENT_64, Uint32(commandSize));
125-
126-
final section = MachOSection64(sectname, segname, addr, size, offset, zero,
127-
zero, zero, flags, zero, zero, zero);
128-
129-
final segment = MachOSegmentCommand64(Uint32(commandSize), segname, vmaddr,
130-
vmsize, fileoff, filesize, maxprot, initprot, nsects, zero, [section]);
131-
132-
// Setup the new linkedit command.
133-
final shiftedLinkeditVmaddr = linkedit.vmaddr + segment.vmsize;
134-
final shiftedLinkeditFileoff = linkedit.fileoff + segment.filesize;
135-
final shiftedLinkedit = MachOSegmentCommand64(
136-
linkedit.cmdsize,
137-
linkedit.segname,
138-
shiftedLinkeditVmaddr,
139-
linkedit.vmsize,
140-
shiftedLinkeditFileoff,
141-
linkedit.filesize,
142-
linkedit.maxprot,
143-
linkedit.initprot,
144-
linkedit.nsects,
145-
linkedit.flags,
146-
linkedit.sections);
147-
148-
// Shift all of the related commands that need to reference the new file
149-
// position of the linkedit segment.
150-
for (var i = 0; i < file.commands.length; i++) {
151-
final command = file.commands[i];
152-
153-
final offsetAmount = segment.filesize;
154-
final withinStart = linkedit.fileoff;
155-
final withinSize = linkedit.filesize;
156-
157-
// For the specific command that we need to adjust, we need to move the
158-
// commands' various offsets forward by the new segment's size in the file
159-
// (segment.filesize). However, we need to ensure that when we move the
160-
// offset forward, we exclude cases where the offset was originally
161-
// outside of the linkedit segment (i.e. offset < linkedit.fileoff or
162-
// offset >= linkedit.fileoff + linkedit.filesize). The DRY-ing function
163-
// addIfWithin takes care of that repeated logic.
164-
if (command is MachODyldInfoCommand) {
165-
file.commands[i] = MachODyldInfoCommand(
166-
command.cmd,
167-
command.cmdsize,
168-
addIfWithin(
169-
command.rebase_off, offsetAmount, withinStart, withinSize),
170-
command.rebase_size,
171-
addIfWithin(
172-
command.bind_off, offsetAmount, withinStart, withinSize),
173-
command.bind_size,
174-
addIfWithin(
175-
command.weak_bind_off, offsetAmount, withinStart, withinSize),
176-
command.weak_bind_size,
177-
addIfWithin(
178-
command.lazy_bind_off, offsetAmount, withinStart, withinSize),
179-
command.lazy_bind_size,
180-
addIfWithin(
181-
command.export_off, offsetAmount, withinStart, withinSize),
182-
command.export_size);
183-
} else if (command is MachOSymtabCommand) {
184-
file.commands[i] = MachOSymtabCommand(
185-
command.cmdsize,
186-
addIfWithin(command.symoff, offsetAmount, withinStart, withinSize),
187-
command.nsyms,
188-
addIfWithin(command.stroff, offsetAmount, withinStart, withinSize),
189-
command.strsize);
190-
} else if (command is MachODysymtabCommand) {
191-
file.commands[i] = MachODysymtabCommand(
192-
command.cmdsize,
193-
command.ilocalsym,
194-
command.nlocalsym,
195-
command.iextdefsym,
196-
command.nextdefsym,
197-
command.iundefsym,
198-
command.nundefsym,
199-
addIfWithin(command.tocoff, offsetAmount, withinStart, withinSize),
200-
command.ntoc,
201-
addIfWithin(
202-
command.modtaboff, offsetAmount, withinStart, withinSize),
203-
command.nmodtab,
204-
addIfWithin(
205-
command.extrefsymoff, offsetAmount, withinStart, withinSize),
206-
command.nextrefsyms,
207-
addIfWithin(
208-
command.indirectsymoff, offsetAmount, withinStart, withinSize),
209-
command.nindirectsyms,
210-
addIfWithin(
211-
command.extreloff, offsetAmount, withinStart, withinSize),
212-
command.nextrel,
213-
addIfWithin(
214-
command.locreloff, offsetAmount, withinStart, withinSize),
215-
command.nlocrel);
216-
} else if (command is MachOLinkeditDataCommand) {
217-
file.commands[i] = MachOLinkeditDataCommand(
218-
command.cmd,
219-
command.cmdsize,
220-
addIfWithin(command.dataoff, offsetAmount, withinStart, withinSize),
221-
command.datasize);
222-
}
223-
}
224-
225-
// Now we need to build the new header from these modified pieces.
226-
file.header = MachOHeader(
227-
file.header!.magic,
228-
file.header!.cputype,
229-
file.header!.cpusubtype,
230-
file.header!.filetype,
231-
file.header!.ncmds + Uint32(1),
232-
file.header!.sizeofcmds + loadCommand.cmdsize,
233-
file.header!.flags,
234-
file.header!.reserved);
235-
236-
file.commands[linkeditIndex] = shiftedLinkedit;
237-
file.commands.insert(linkeditIndex, segment);
238-
}
239-
240-
return linkedit;
241-
}
242-
24314
// Pipe from one file stream into another. We do this in chunks to avoid
24415
// excessive memory load.
24516
Future<int> pipeStream(RandomAccessFile from, RandomAccessFile to,
@@ -297,17 +68,22 @@ Future writeAppendedMachOExecutable(
29768
String dartaotruntimePath, String payloadPath, String outputPath) async {
29869
File originalExecutableFile = File(dartaotruntimePath);
29970

300-
MachOFile machOFile = MachOFile();
301-
await machOFile.loadFromFile(originalExecutableFile);
71+
MachOFile machOFile = MachOFile.fromFile(originalExecutableFile);
72+
final oldLinkEditSegmentFileOffset = machOFile.linkEditSegment?.fileOffset;
73+
if (oldLinkEditSegmentFileOffset == null) {
74+
throw FormatException("__LINKEDIT segment not found");
75+
}
30276

30377
// Insert the new segment that contains our snapshot data.
30478
File newSegmentFile = File(payloadPath);
30579

306-
// Note that these two values MUST match the ones in
307-
// runtime/bin/snapshot_utils.cc, which looks specifically for the snapshot in
308-
// this segment/section.
309-
final linkeditCommand = await insertSegmentDefinition(
310-
machOFile, newSegmentFile, kSnapshotSegmentName, kSnapshotSectionName);
80+
// Get the length of the contents of the section to be added.
81+
final payloadLength = newSegmentFile.lengthSync();
82+
// We only need the original offset of the __LINKEDIT segment from the
83+
// original headers, which we've already retrieved. Thus, use the new
84+
// MachOFile from inserting the snapshot segment from here on.
85+
machOFile = machOFile.insertSegmentLoadCommand(
86+
payloadLength, snapshotSegmentName, snapshotSectionName);
31187

31288
// Write out the new executable, with the same contents except the new header.
31389
File outputFile = File(outputPath);
@@ -321,16 +97,22 @@ Future writeAppendedMachOExecutable(
32197
RandomAccessFile originalFileStream = await originalExecutableFile.open();
32298
await originalFileStream.setPosition(headerBytesWritten);
32399

324-
// Write the unchanged data from the original file.
100+
// Write the unchanged data from the original file up to the __LINKEDIT
101+
// segment contents, so we can insert the snapshot there.
325102
await pipeStream(originalFileStream, stream,
326-
numToWrite: linkeditCommand.fileoff.asInt() - headerBytesWritten);
103+
numToWrite: oldLinkEditSegmentFileOffset - headerBytesWritten);
104+
105+
void addPadding(int size) => stream.writeFromSync(List.filled(size, 0));
106+
107+
final snapshotSegment = machOFile.snapshotSegment!;
108+
// There may be additional padding needed between the old __LINKEDIT file
109+
// offset and the start of the new snapshot.
110+
addPadding(snapshotSegment.fileOffset - oldLinkEditSegmentFileOffset);
327111

328112
// Write the inserted section data, ensuring that the data is padded to the
329113
// segment size.
330114
await pipeStream(newSegmentFileStream, stream);
331-
final int newSegmentLength = newSegmentFileStream.lengthSync();
332-
final int alignedSegmentSize = vmSizeAlign(newSegmentLength);
333-
await stream.writeFrom(List.filled(alignedSegmentSize - newSegmentLength, 0));
115+
addPadding(align(payloadLength, segmentAlignment) - payloadLength);
334116

335117
// Copy the rest of the file from the original to the new one.
336118
await pipeStream(originalFileStream, stream);

0 commit comments

Comments
 (0)