Created
August 25, 2022 11:18
-
-
Save mraleph/d8ffb32cd419e08e15b8fc245dbbeff7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Based on Martin Kustermann's version and adding support for | |
// finalization of memory mapped views. | |
import 'dart:ffi'; | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'dart:math' as math; | |
import 'package:ffi/ffi.dart'; | |
class Stat extends Struct { | |
@Int64() | |
external int ignored1; | |
@Int64() | |
external int ignored2; | |
@Int64() | |
external int ignored3; | |
@Int64() | |
external int ignored4; | |
@Int64() | |
external int ignored5; | |
@Int64() | |
external int ignored6; | |
@Int64() | |
external int st_size; | |
@Int64() | |
external int ignored7; | |
@Int64() | |
external int ignored8; | |
@Int64() | |
external int ignored9; | |
@Int64() | |
external int ignored10; | |
@Int64() | |
external int ignored11; | |
@Int64() | |
external int ignored12; | |
@Int64() | |
external int ignored13; | |
@Int64() | |
external int ignored14; | |
@Int64() | |
external int ignored15; | |
@Int64() | |
external int ignored16; | |
@Int64() | |
external int ignored17; | |
} | |
// int open(const char *path, int oflag, ...); | |
typedef OpenNative = Int32 Function(Pointer<Utf8> filename, Int32 flags); | |
typedef Open = int Function(Pointer<Utf8> filename, int flags); | |
final open = processSymbols.lookupFunction<OpenNative, Open>('open'); | |
// int __fxstat(int version, int fd, struct stat *buf); | |
typedef FStatNative = Int32 Function(Int32 vers, Int32 fd, Pointer<Stat> stat); | |
typedef FStat = int Function(int vers, int fd, Pointer<Stat> stat); | |
final fstat = processSymbols.lookupFunction<FStatNative, FStat>('__fxstat'); | |
// int close(int fd); | |
typedef CloseNative = IntPtr Function(Int32 fd); | |
typedef Close = int Function(int fd); | |
final close = processSymbols.lookupFunction<CloseNative, Close>('close'); | |
// void* mmap(void* addr, size_t length, | |
// int prot, int flags, | |
// int fd, off_t offset) | |
typedef MMapNative = Pointer<Uint8> Function(Pointer<Uint8> address, IntPtr len, | |
Int32 prot, Int32 flags, Int32 fd, IntPtr offset); | |
typedef MMap = Pointer<Uint8> Function( | |
Pointer<Uint8> address, int len, int prot, int flags, int fd, int offset); | |
final mmap = processSymbols.lookupFunction<MMapNative, MMap>('mmap'); | |
// int munmap(void *addr, size_t length) | |
typedef MUnMapNative = IntPtr Function(Pointer<Uint8> address, IntPtr len); | |
typedef MUnMap = int Function(Pointer<Uint8> address, int len); | |
final munmap = processSymbols.lookupFunction<MUnMapNative, MUnMap>('munmap'); | |
final processSymbols = DynamicLibrary.process(); | |
final munmapNative = processSymbols.lookup<Void>('munmap'); | |
final closeNative = processSymbols.lookup<Void>('close'); | |
final freeNative = processSymbols.lookup<Void>('free'); | |
typedef MprotectNative = Int32 Function(Pointer<Uint8>, IntPtr, Int32); | |
typedef Mprotect = int Function(Pointer<Uint8>, int, int); | |
final mprotect = | |
processSymbols.lookupFunction<MprotectNative, Mprotect>('mprotect'); | |
// DART_EXPORT Dart_Handle | |
// Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type, | |
// void* data, | |
// intptr_t length, | |
// void* peer, | |
// intptr_t external_allocation_size, | |
// Dart_HandleFinalizer callback) | |
typedef Dart_NewExternalTypedDataWithFinalizerNative = Handle Function( | |
Int32, Pointer<Void>, IntPtr, Pointer<Void>, IntPtr, Pointer<Void>); | |
typedef Dart_NewExternalTypedDataWithFinalizerDart = Object Function( | |
int, Pointer<Void>, int, Pointer<Void>, int, Pointer<Void>); | |
final Dart_NewExternalTypedDataWithFinalizer = processSymbols.lookupFunction< | |
Dart_NewExternalTypedDataWithFinalizerNative, | |
Dart_NewExternalTypedDataWithFinalizerDart>( | |
'Dart_NewExternalTypedDataWithFinalizer'); | |
const int kPageSize = 4096; | |
const int kProtRead = 1; | |
const int kProtWrite = 2; | |
const int kProtExec = 4; | |
const int kMapPrivate = 2; | |
const int kMapAnon = 0x20; | |
const int kMapFailed = -1; | |
// We need to attach the finalizer which calls close() and | |
final finalizerAddress = () { | |
final finalizerStub = mmap(nullptr, kPageSize, kProtRead | kProtWrite, | |
kMapPrivate | kMapAnon, -1, 0); | |
finalizerStub.cast<Uint8>().asTypedList(kPageSize).setAll(0, <int>[ | |
// Regenerate by running dart mmap.dart gen | |
// ASM_START | |
// #include <cstddef> | |
// #include <cstdint> | |
// | |
// struct PeerData { | |
// int (*close)(int); | |
// int (*munmap)(void*, size_t); | |
// int (*free)(void*); | |
// void* mapping; | |
// intptr_t size; | |
// intptr_t fd; | |
// }; | |
// | |
// extern "C" void finalizer(void* callback_data, void* peer) { | |
// auto data = static_cast<PeerData*>(peer); | |
// data->munmap(data->mapping, data->size); | |
// data->close(data->fd); | |
// data->free(peer); | |
// } | |
// | |
0x55, 0x48, 0x89, 0xf5, 0x48, 0x8b, 0x76, 0x20, 0x48, 0x8b, 0x7d, 0x18, // | |
0xff, 0x55, 0x08, 0x8b, 0x7d, 0x28, 0xff, 0x55, 0x00, 0x48, 0x8b, 0x45, // | |
0x10, 0x48, 0x89, 0xef, 0x5d, 0xff, 0xe0, // | |
// ASM_END | |
]); | |
if (mprotect(finalizerStub, kPageSize, kProtRead | kProtExec) != 0) { | |
throw 'Failed to write executable code to the memory.'; | |
} | |
return finalizerStub.cast<Void>(); | |
}(); | |
class PeerData extends Struct { | |
external Pointer<Void> close; | |
external Pointer<Void> munmap; | |
external Pointer<Void> free; | |
external Pointer<Uint8> mapping; | |
@IntPtr() | |
external int size; | |
@IntPtr() | |
external int fd; | |
} | |
Uint8List toExternalDataWithFinalizer( | |
Pointer<Uint8> memory, int size, int length, int fd) { | |
final peer = malloc.allocate<PeerData>(sizeOf<PeerData>()); | |
peer.ref.close = closeNative; | |
peer.ref.munmap = munmapNative; | |
peer.ref.free = freeNative; | |
peer.ref.mapping = memory; | |
peer.ref.size = size; | |
peer.ref.fd = fd; | |
return Dart_NewExternalTypedDataWithFinalizer( | |
/*Dart_TypedData_kUint8*/ 2, | |
memory.cast(), | |
length, | |
peer.cast(), | |
size, | |
finalizerAddress, | |
) as Uint8List; | |
} | |
Uint8List viewOfFile(String filename) { | |
final cfilename = filename.toNativeUtf8(); | |
final int fd = open(cfilename, 0); | |
malloc.free(cfilename); | |
if (fd == 0) throw 'failed to open'; | |
try { | |
final length = lengthOfFile(fd); | |
final lengthRoundedUp = (length + kPageSize - 1) & ~(kPageSize - 1); | |
final result = | |
mmap(nullptr, lengthRoundedUp, kProtRead, kMapPrivate, fd, 0); | |
if (result.address == kMapFailed) throw 'failed to map'; | |
try { | |
return toExternalDataWithFinalizer(result, lengthRoundedUp, length, fd); | |
} catch (_) { | |
munmap(result, lengthRoundedUp); | |
rethrow; | |
} | |
} catch (e) { | |
close(fd); | |
rethrow; | |
} | |
} | |
final Pointer<Stat> _statBuffer = malloc<Stat>(); | |
int lengthOfFile(int fd) { | |
final result = fstat(0, fd, _statBuffer); | |
if (result != 0) { | |
print('failed fstat with $result'); | |
} | |
return _statBuffer.ref.st_size; | |
} | |
main(List<String> args) { | |
if (args.length == 1 && args.first == 'gen') { | |
print( | |
'Regenerating inline machine code embedded into ${Platform.script.toFilePath()}'); | |
final lines = File(Platform.script.toFilePath()).readAsLinesSync(); | |
final result = []; | |
var i = 0; | |
while (i < lines.length) { | |
while (i < lines.length && lines[i] != '// ASM_START') { | |
result.add(lines[i++]); | |
} | |
i++; | |
final code = []; | |
while (i < lines.length && lines[i] != '// ASM_END') { | |
if (lines[i].startsWith('//')) { | |
code.add(lines[i]); | |
} | |
i++; | |
} | |
if (code.length != 0) { | |
final temp = Directory.systemTemp.createTempSync(); | |
try { | |
new File('${temp.path}/code.cc').writeAsStringSync(code | |
.map((l) => l.substring(l.startsWith('// ') ? 6 : 2)) | |
.join('\n')); | |
final ccResult = Process.runSync( | |
'gcc', ['-O3', '-c', '-o', 'code.o', 'code.cc'], | |
workingDirectory: temp.path); | |
if (ccResult.exitCode != 0) throw 'Failed to run gcc'; | |
final objcopyResult = Process.runSync('objcopy', | |
['-O', 'binary', '-j', '.text', 'code.o', 'code-bytes'], | |
workingDirectory: temp.path); | |
if (objcopyResult.exitCode != 0) throw 'Failed to run objcopy'; | |
final bytes = File('${temp.path}/code-bytes').readAsBytesSync(); | |
result.add('// ASM_START'); | |
result.addAll(code); | |
result.addAll([ | |
for (var i = 0; i < bytes.length; i += 12) | |
' ' + | |
bytes | |
.sublist(i, math.min(bytes.length, i + 12)) | |
.map((v) => '0x${v.toRadixString(16).padLeft(2, '0')}') | |
.join(', ') + | |
', //' | |
]); | |
result.add('// ASM_END'); | |
} finally { | |
temp.deleteSync(recursive: true); | |
} | |
} | |
i++; | |
} | |
File(Platform.script.toFilePath()).writeAsStringSync(result.join('\n')); | |
return; | |
} | |
String xor(Uint8List bytes) { | |
int xor = 0; | |
// We don't want to measure accessing bytes but only getting bytes from OS. | |
// To ensure fair comparison, we touch one byte on every page, which | |
// ensures it will be mapped. | |
for (int i = 0; i < bytes.length; i += kPageSize) { | |
xor ^= bytes[i]; | |
} | |
return 'xor: $xor'; | |
} | |
void measureDart(String filename) { | |
final sw = Stopwatch()..start(); | |
final result = xor(File(filename).readAsBytesSync()); | |
print('Dart read: took ${sw.elapsed} ($result)'); | |
} | |
void measureMmap(String filename) { | |
final sw = Stopwatch()..start(); | |
final result = xor(viewOfFile(filename)); | |
print('Mmap read: took ${sw.elapsed} ($result)'); | |
} | |
final filename = '/tmp/foo.bin'; | |
for (final size in [ | |
1024, | |
10 * 1024, | |
100 * 1024, | |
1000 * 1024, | |
10000 * 1024, | |
]) { | |
print('Size = ${size ~/ 1024} KiB'); | |
Process.runSync( | |
'dd', ['if=/dev/random', 'of=$filename', 'bs=$size', 'count=1']); | |
measureDart(filename); | |
measureDart(filename); | |
measureDart(filename); | |
measureMmap(filename); | |
measureMmap(filename); | |
measureMmap(filename); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment