2
2
// for details. All rights reserved. Use of this source code is governed by a
3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
- import 'dart:convert' ;
6
5
import 'dart:io' ;
7
6
import 'dart:math' ;
8
- import 'dart:typed_data' ;
9
7
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' ;
53
10
54
11
// Simplifies casting so we get null values back instead of exceptions.
55
12
T ? cast <T >(x) => x is T ? x : null ;
56
13
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
-
243
14
// Pipe from one file stream into another. We do this in chunks to avoid
244
15
// excessive memory load.
245
16
Future <int > pipeStream (RandomAccessFile from, RandomAccessFile to,
@@ -297,17 +68,22 @@ Future writeAppendedMachOExecutable(
297
68
String dartaotruntimePath, String payloadPath, String outputPath) async {
298
69
File originalExecutableFile = File (dartaotruntimePath);
299
70
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
+ }
302
76
303
77
// Insert the new segment that contains our snapshot data.
304
78
File newSegmentFile = File (payloadPath);
305
79
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);
311
87
312
88
// Write out the new executable, with the same contents except the new header.
313
89
File outputFile = File (outputPath);
@@ -321,16 +97,22 @@ Future writeAppendedMachOExecutable(
321
97
RandomAccessFile originalFileStream = await originalExecutableFile.open ();
322
98
await originalFileStream.setPosition (headerBytesWritten);
323
99
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.
325
102
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);
327
111
328
112
// Write the inserted section data, ensuring that the data is padded to the
329
113
// segment size.
330
114
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);
334
116
335
117
// Copy the rest of the file from the original to the new one.
336
118
await pipeStream (originalFileStream, stream);
0 commit comments