Skip to content

Instantly share code, notes, and snippets.

@mraleph
Created August 25, 2022 11:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mraleph/d8ffb32cd419e08e15b8fc245dbbeff7 to your computer and use it in GitHub Desktop.
Save mraleph/d8ffb32cd419e08e15b8fc245dbbeff7 to your computer and use it in GitHub Desktop.
// 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