exoproc-accessors 0.1.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -235
- package/dist/accessors.d.ts +186 -0
- package/dist/accessors.d.ts.map +1 -0
- package/dist/accessors.js +363 -0
- package/dist/accessors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +725 -109
- package/dist/index.js.map +1 -1
- package/dist/indirect-nthread-host-accessor.d.ts +127 -0
- package/dist/indirect-nthread-host-accessor.d.ts.map +1 -0
- package/dist/indirect-nthread-host-accessor.js +288 -0
- package/dist/indirect-nthread-host-accessor.js.map +1 -0
- package/dist/middleware-accessor.d.ts +82 -14
- package/dist/middleware-accessor.d.ts.map +1 -1
- package/dist/middleware-accessor.js +336 -108
- package/dist/middleware-accessor.js.map +1 -1
- package/dist/stealth-java.d.ts +21 -0
- package/dist/stealth-java.d.ts.map +1 -0
- package/dist/stealth-java.js +119 -0
- package/dist/stealth-java.js.map +1 -0
- package/package.json +20 -10
|
@@ -2,7 +2,8 @@ import { randomUUID } from 'crypto';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
|
-
import { NativeMemory, normalizeType, resolveAddress, alignUp, computeNearAllocRange, freeRegionCandidate, AllocNearRangeError, Kernel32Impl, MsvcrtImpl, MsvcrtLibrary, MemoryBasicInformation, PROCESSENTRY32W_SIZE, MemoryState, MemoryProtection, MemoryFreeType, ToolhelpSnapshotFlag, ProcessAccess, DEFAULT_MACHINECODE_ALIGNMENT, memmem, memmem1, memmem2, memmem4, memmem8, memmemWithoutBuffer, Pattern, Scanner, verifyCoreModules, verifyCoreModulesSync, isModuleLoadedInProcess, isModuleLoadedInProcessSync, MiddlewareAccessor, InittableMiddlewareAccessor,
|
|
5
|
+
import { NativeMemory, normalizeType, resolveAddress, alignUp, computeNearAllocRange, freeRegionCandidate, AllocNearRangeError, Kernel32Impl, MsvcrtImpl, MsvcrtLibrary, MemoryBasicInformation, PROCESSENTRY32W_SIZE, MemoryState, MemoryProtection, MemoryFreeType, ToolhelpSnapshotFlag, ProcessAccess, DEFAULT_MACHINECODE_ALIGNMENT, memmem, memmem1, memmem2, memmem4, memmem8, memmemWithoutBuffer, Pattern, Scanner, verifyCoreModules, verifyCoreModulesSync, isModuleLoadedInProcess, isModuleLoadedInProcessSync, MiddlewareAccessor, InittableMiddlewareAccessor, isInittableAccessor, HostAccessor, } from 'bun-xffi';
|
|
6
|
+
export { HostAccessor };
|
|
6
7
|
export class MsvcrtDependentMiddlewareAccessor extends InittableMiddlewareAccessor {
|
|
7
8
|
async onInit() {
|
|
8
9
|
if (this.isLocal)
|
|
@@ -23,34 +24,6 @@ export class MsvcrtDependentMiddlewareAccessor extends InittableMiddlewareAccess
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
* HostAccessor is a base class that automatically initializes all nested InittableMiddlewareAccessors
|
|
28
|
-
* in the backend decorator chain.
|
|
29
|
-
*/
|
|
30
|
-
export class HostAccessor extends InittableMiddlewareAccessor {
|
|
31
|
-
get processId() {
|
|
32
|
-
return this._processId;
|
|
33
|
-
}
|
|
34
|
-
constructor(backend, root) {
|
|
35
|
-
super(backend, root ?? null);
|
|
36
|
-
if (!root) {
|
|
37
|
-
this.root = this;
|
|
38
|
-
}
|
|
39
|
-
let b = backend;
|
|
40
|
-
while (isMiddlewareAccessor(b)) {
|
|
41
|
-
b = b.backend;
|
|
42
|
-
}
|
|
43
|
-
if (b) {
|
|
44
|
-
this._processId = b.processId;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async onInit() {
|
|
48
|
-
// No-op. Chain initialization is automatically propagated by init().
|
|
49
|
-
}
|
|
50
|
-
onInitSync() {
|
|
51
|
-
// No-op. Chain initialization is automatically propagated by initSync().
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
27
|
/**
|
|
55
28
|
* A HostAccessor that forwards all operations to a dynamically changeable target HostAccessor.
|
|
56
29
|
* This is useful for resolving circular dependencies in middleware chains where the root
|
|
@@ -77,7 +50,15 @@ export class RedirectorHostAccessor extends HostAccessor {
|
|
|
77
50
|
_target;
|
|
78
51
|
constructor(processId, root) {
|
|
79
52
|
super(new ThrowingMemoryAccessor(processId), root);
|
|
80
|
-
|
|
53
|
+
// Deliberately self-rooted (no `root` arg) rather than `this.root`: this
|
|
54
|
+
// placeholder is never a real candidate, but constructing it with the
|
|
55
|
+
// outer `root` would still run through `MiddlewareAccessor`'s
|
|
56
|
+
// constructor -- which unconditionally calls `root.registerChild(this)`
|
|
57
|
+
// -- and register the placeholder itself as a racer on a
|
|
58
|
+
// `RacingHostAccessor` root. Since it always throws and never does real
|
|
59
|
+
// work, it would "win" the race instantly, before any genuine candidate
|
|
60
|
+
// could land.
|
|
61
|
+
this._target = new ThrowingHostAccessor(processId);
|
|
81
62
|
}
|
|
82
63
|
get target() {
|
|
83
64
|
return this._target;
|
|
@@ -232,10 +213,126 @@ export class RedirectorHostAccessor extends HostAccessor {
|
|
|
232
213
|
async onInit() {
|
|
233
214
|
// No-op. The target is initialized separately.
|
|
234
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Mirrors the `close()` override above for the async lifecycle: every op
|
|
218
|
+
* (read/write/call/...) routes to `target` once it's no longer `this`, so
|
|
219
|
+
* `deinit()` must too. Without this, the inherited `deinitNext()` (from
|
|
220
|
+
* `InittableMiddlewareAccessor`) walks `this.backend` -- which for a
|
|
221
|
+
* subclass like `NThreadRaceAccessor` stays pointed at its own
|
|
222
|
+
* placeholder `ThrowingMemoryAccessor` forever (only `target` ever gets
|
|
223
|
+
* reassigned to the winner, per `RedirectorHostAccessor`'s whole design;
|
|
224
|
+
* see its class doc comment) -- so the real accessor `target` resolved to
|
|
225
|
+
* would never actually get deinited, leaving its underlying resource
|
|
226
|
+
* (e.g. a hijacked thread) permanently stuck.
|
|
227
|
+
*
|
|
228
|
+
* Deliberately excludes `t === this.root`: `BootstrapHostAccessor.onInit()`
|
|
229
|
+
* itself resolves `target` to `this.root` once its own backend has landed
|
|
230
|
+
* -- that's a hand-off back to whatever *contains* this accessor (e.g. a
|
|
231
|
+
* per-candidate `BootstrapHostAccessor` inside `NThreadRaceAccessor`
|
|
232
|
+
* resolves to the race accessor itself), not a distinct resource this
|
|
233
|
+
* accessor now owns. Cascading `deinit()` there would tear down the
|
|
234
|
+
* container (and everything else riding on it, e.g. the actual race
|
|
235
|
+
* winner) from a single losing candidate's own cleanup.
|
|
236
|
+
*/
|
|
237
|
+
async onDeinit() {
|
|
238
|
+
const t = this.getTarget();
|
|
239
|
+
if (t !== this && t !== this.root && isInittableAccessor(t)) {
|
|
240
|
+
await t.deinit();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
235
243
|
onInitSync() {
|
|
236
244
|
// No-op. The target is initialized separately.
|
|
237
245
|
}
|
|
238
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* A minimal `RedirectorHostAccessor` mixin meant to be *extended*, not used
|
|
249
|
+
* directly -- its only job is giving a subclass (e.g. `NThreadRaceAccessor`)
|
|
250
|
+
* two things for free: automatic candidate collection, and a `race()` helper
|
|
251
|
+
* to actually run the race once the subclass's own `onInit()` decides it's
|
|
252
|
+
* time. `target` (inherited) is left for the subclass to set to the winner
|
|
253
|
+
* however makes sense for it -- this class never touches it itself.
|
|
254
|
+
*
|
|
255
|
+
* Candidates are never added explicitly -- every `MiddlewareAccessor`'s
|
|
256
|
+
* constructor calls `root.registerChild(this)` (a no-op on every other
|
|
257
|
+
* `IHostAccessor`; see its doc comment), so simply constructing a candidate
|
|
258
|
+
* with a `RacingHostAccessor` (sub)instance as its `root` is enough to enter
|
|
259
|
+
* it into the race.
|
|
260
|
+
*
|
|
261
|
+
* Deliberately knows nothing about *why* a caller is racing candidates -- no
|
|
262
|
+
* `NThread`, no `AbortController`, no timeout, no `AsyncLocalStorage`; a
|
|
263
|
+
* caller that needs to cancel the losers does so itself, by extending this
|
|
264
|
+
* class and overriding `releaseLoser`, and a caller whose candidates need
|
|
265
|
+
* per-candidate context threaded through their own `init()` (e.g.
|
|
266
|
+
* `NThreadRaceAccessor`, whose candidates all share one root and need
|
|
267
|
+
* `root.call()` to resolve back to *this specific* candidate while racing)
|
|
268
|
+
* does so by overriding `startRacer` -- kept as a single, narrow extension
|
|
269
|
+
* point instead of baking any particular mechanism (like
|
|
270
|
+
* `AsyncLocalStorage`) into this generic base.
|
|
271
|
+
*/
|
|
272
|
+
export class RacingHostAccessor extends RedirectorHostAccessor {
|
|
273
|
+
racers = [];
|
|
274
|
+
registerChild(child) {
|
|
275
|
+
// Can fire before our own field initializers run: RedirectorHostAccessor's
|
|
276
|
+
// constructor builds a placeholder `new ThrowingHostAccessor(processId,
|
|
277
|
+
// this.root)`, and `this.root` is this very instance (self-rooted, still
|
|
278
|
+
// mid-construction) when nothing else was passed as `root` -- `this.racers`
|
|
279
|
+
// isn't assigned yet at that point. Harmless to skip: real candidates
|
|
280
|
+
// always register later, from a subclass's own constructor body, which
|
|
281
|
+
// only runs after every `super()` call (and therefore every field
|
|
282
|
+
// initializer) has completed.
|
|
283
|
+
if (!this.racers || !isInittableAccessor(child))
|
|
284
|
+
return;
|
|
285
|
+
this.racers.push(child);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Launches one racer's `init()`. Overridable so a subclass can wrap the
|
|
289
|
+
* call with its own per-candidate context (see `NThreadRaceAccessor`,
|
|
290
|
+
* which threads an `AsyncLocalStorage` context through this so its shared
|
|
291
|
+
* root's `call()` can resolve back to whichever candidate is currently
|
|
292
|
+
* bootstrapping) without this base class needing to know why.
|
|
293
|
+
*/
|
|
294
|
+
startRacer(racer) {
|
|
295
|
+
return racer.init();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Races every registered racer's `init()` and returns whichever settles
|
|
299
|
+
* first; every other racer gets {@link releaseLoser}'d. Doesn't touch
|
|
300
|
+
* `target` itself -- callers (a subclass's own `onInit()` override)
|
|
301
|
+
* decide what to do with the winner.
|
|
302
|
+
*/
|
|
303
|
+
async race() {
|
|
304
|
+
if (this.racers.length === 0) {
|
|
305
|
+
throw new Error('RacingHostAccessor: no racers registered to race');
|
|
306
|
+
}
|
|
307
|
+
const attempts = this.racers.map((racer) => ({
|
|
308
|
+
racer,
|
|
309
|
+
settled: this.startRacer(racer),
|
|
310
|
+
}));
|
|
311
|
+
let winner;
|
|
312
|
+
try {
|
|
313
|
+
winner = await Promise.any(attempts.map((a) => a.settled.then(() => a.racer)));
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
throw new Error(`RacingHostAccessor: none of ${attempts.length} racer(s) could be initialized`, { cause: err });
|
|
317
|
+
}
|
|
318
|
+
for (const a of attempts) {
|
|
319
|
+
if (a.racer === winner)
|
|
320
|
+
continue;
|
|
321
|
+
this.releaseLoser(a.racer, a.settled);
|
|
322
|
+
}
|
|
323
|
+
return winner;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Called once per losing racer, right after a winner is picked. Default:
|
|
327
|
+
* deinit it once (if) its own `init()` eventually settles -- `deinit()` is
|
|
328
|
+
* a no-op for one that never finished or that already failed. Subclasses
|
|
329
|
+
* (e.g. `NThreadRaceAccessor`) can override this to *also* cancel an
|
|
330
|
+
* in-flight racer immediately, instead of only cleaning up after the fact.
|
|
331
|
+
*/
|
|
332
|
+
releaseLoser(racer, settled) {
|
|
333
|
+
settled.then(() => racer.deinit().catch(() => { })).catch(() => { });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
239
336
|
/**
|
|
240
337
|
* BootstrapHostAccessor is a specialized RedirectorHostAccessor designed to resolve circular dependencies
|
|
241
338
|
* during the initialization of decorator/middleware chains.
|
|
@@ -263,7 +360,7 @@ export class BootstrapHostAccessor extends RedirectorHostAccessor {
|
|
|
263
360
|
*/
|
|
264
361
|
export class CallRedirectorAccessor extends MiddlewareAccessor {
|
|
265
362
|
async alloc(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
|
|
266
|
-
const addressVal = address ? resolveAddress(address) :
|
|
363
|
+
const addressVal = address ? resolveAddress(address) : 0;
|
|
267
364
|
const result = await this.root.call(Kernel32Impl.VirtualAlloc, addressVal, size, allocationType, protection);
|
|
268
365
|
if (!result || Number(result) === 0) {
|
|
269
366
|
throw new Error(`VirtualAlloc failed in remote process for size ${size}`);
|
|
@@ -271,7 +368,7 @@ export class CallRedirectorAccessor extends MiddlewareAccessor {
|
|
|
271
368
|
return result;
|
|
272
369
|
}
|
|
273
370
|
allocSync(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
|
|
274
|
-
const addressVal = address ? resolveAddress(address) :
|
|
371
|
+
const addressVal = address ? resolveAddress(address) : 0;
|
|
275
372
|
const result = this.root.callSync(Kernel32Impl.VirtualAlloc, addressVal, size, allocationType, protection);
|
|
276
373
|
if (!result || Number(result) === 0) {
|
|
277
374
|
throw new Error(`VirtualAlloc failed in remote process for size ${size}`);
|
|
@@ -503,31 +600,66 @@ export class IndirectCallRedirectorAccessor extends CallRedirectorAccessor {
|
|
|
503
600
|
});
|
|
504
601
|
return info;
|
|
505
602
|
}
|
|
603
|
+
hasMsvcrt = null;
|
|
604
|
+
async checkMsvcrt() {
|
|
605
|
+
if (this.hasMsvcrt !== null)
|
|
606
|
+
return this.hasMsvcrt;
|
|
607
|
+
const msvcrtBase = MsvcrtLibrary.baseAddress;
|
|
608
|
+
if (msvcrtBase.isNull() || Number(msvcrtBase) === 0) {
|
|
609
|
+
this.hasMsvcrt = false;
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
this.hasMsvcrt = await isModuleLoadedInProcess(this.root, msvcrtBase);
|
|
613
|
+
return this.hasMsvcrt;
|
|
614
|
+
}
|
|
615
|
+
checkMsvcrtSync() {
|
|
616
|
+
if (this.hasMsvcrt !== null)
|
|
617
|
+
return this.hasMsvcrt;
|
|
618
|
+
const msvcrtBase = MsvcrtLibrary.baseAddress;
|
|
619
|
+
if (msvcrtBase.isNull() || Number(msvcrtBase) === 0) {
|
|
620
|
+
this.hasMsvcrt = false;
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
this.hasMsvcrt = isModuleLoadedInProcessSync(this.root, msvcrtBase);
|
|
624
|
+
return this.hasMsvcrt;
|
|
625
|
+
}
|
|
506
626
|
async alloc(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
|
|
507
627
|
// If it's standard READWRITE memory without a specific requested address, use malloc for maximum redirection!
|
|
508
628
|
if (protection === MemoryProtection.READWRITE &&
|
|
509
629
|
(address === null || address === undefined)) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
630
|
+
try {
|
|
631
|
+
if (await this.checkMsvcrt()) {
|
|
632
|
+
const result = await this.root.call(MsvcrtImpl.malloc, size);
|
|
633
|
+
if (result && Number(result) !== 0) {
|
|
634
|
+
const allocatedAddr = Number(result);
|
|
635
|
+
this.mallocs.set(allocatedAddr, size);
|
|
636
|
+
return allocatedAddr;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
// Fallback to VirtualAlloc if msvcrt.dll malloc is not available in target process
|
|
513
642
|
}
|
|
514
|
-
const allocatedAddr = Number(result);
|
|
515
|
-
this.mallocs.set(allocatedAddr, size);
|
|
516
|
-
return allocatedAddr;
|
|
517
643
|
}
|
|
518
|
-
// Otherwise, fall back to VirtualAlloc (e.g. for executable memory blocks)
|
|
644
|
+
// Otherwise, fall back to VirtualAlloc (e.g. for executable memory blocks or if msvcrt not loaded)
|
|
519
645
|
return super.alloc(size, address, protection, allocationType);
|
|
520
646
|
}
|
|
521
647
|
allocSync(size, address = null, protection = MemoryProtection.READWRITE, allocationType = MemoryState.COMMIT | MemoryState.RESERVE) {
|
|
522
648
|
if (protection === MemoryProtection.READWRITE &&
|
|
523
649
|
(address === null || address === undefined)) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
650
|
+
try {
|
|
651
|
+
if (this.checkMsvcrtSync()) {
|
|
652
|
+
const result = this.root.callSync(MsvcrtImpl.malloc, size);
|
|
653
|
+
if (result && Number(result) !== 0) {
|
|
654
|
+
const allocatedAddr = Number(result);
|
|
655
|
+
this.mallocs.set(allocatedAddr, size);
|
|
656
|
+
return allocatedAddr;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
// Fallback to VirtualAlloc if msvcrt.dll malloc is not available in target process
|
|
527
662
|
}
|
|
528
|
-
const allocatedAddr = Number(result);
|
|
529
|
-
this.mallocs.set(allocatedAddr, size);
|
|
530
|
-
return allocatedAddr;
|
|
531
663
|
}
|
|
532
664
|
return super.allocSync(size, address, protection, allocationType);
|
|
533
665
|
}
|
|
@@ -596,19 +728,43 @@ export class IndirectCallRedirectorAccessor extends CallRedirectorAccessor {
|
|
|
596
728
|
export class MarshallingCallableAccessor extends MiddlewareAccessor {
|
|
597
729
|
/**
|
|
598
730
|
* Shared by call()/callSync() -- pure decision of "does this argument need
|
|
599
|
-
* marshalling to a remote buffer, and if so, what bytes
|
|
600
|
-
*
|
|
731
|
+
* marshalling to a remote buffer, and if so, what bytes and whether to copy
|
|
732
|
+
* remote modifications back to the caller's buffer/struct after the call".
|
|
601
733
|
*/
|
|
602
|
-
static
|
|
734
|
+
static marshalInfo(funcName, type, val) {
|
|
603
735
|
const normType = normalizeType(type);
|
|
604
736
|
if (normType === 'cstring' && typeof val === 'string') {
|
|
605
|
-
return Buffer.from(val + '\0', 'utf8');
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return Buffer.from(val + '\0', 'utf16le');
|
|
737
|
+
return { buf: Buffer.from(val + '\0', 'utf8') };
|
|
738
|
+
}
|
|
739
|
+
if (normType === 'cwstring' && typeof val === 'string') {
|
|
740
|
+
return { buf: Buffer.from(val + '\0', 'utf16le') };
|
|
741
|
+
}
|
|
742
|
+
if (typeof val === 'string' &&
|
|
743
|
+
(normType === 'ptr' ||
|
|
744
|
+
normType === 'i64' ||
|
|
745
|
+
normType === 'u64' ||
|
|
746
|
+
normType === 'usize' ||
|
|
747
|
+
normType === 'size_t')) {
|
|
748
|
+
const trimmed = val.trim();
|
|
749
|
+
if (!/^-?0x[0-9a-fA-F]+$/.test(trimmed)) {
|
|
750
|
+
const isAnsi = funcName ? funcName.endsWith('A') : false;
|
|
751
|
+
return {
|
|
752
|
+
buf: Buffer.from(val + '\0', isAnsi ? 'utf8' : 'utf16le'),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
609
755
|
}
|
|
610
|
-
|
|
611
|
-
|
|
756
|
+
if (val && typeof val === 'object') {
|
|
757
|
+
if (Buffer.isBuffer(val)) {
|
|
758
|
+
return { buf: val, writeBackTarget: val };
|
|
759
|
+
}
|
|
760
|
+
if (Buffer.isBuffer(val._backingBuffer)) {
|
|
761
|
+
const backing = val._backingBuffer;
|
|
762
|
+
return { buf: backing, writeBackTarget: backing };
|
|
763
|
+
}
|
|
764
|
+
if (ArrayBuffer.isView(val)) {
|
|
765
|
+
const viewBuf = Buffer.from(val.buffer, val.byteOffset, val.byteLength);
|
|
766
|
+
return { buf: viewBuf, writeBackTarget: viewBuf };
|
|
767
|
+
}
|
|
612
768
|
}
|
|
613
769
|
return null;
|
|
614
770
|
}
|
|
@@ -617,16 +773,43 @@ export class MarshallingCallableAccessor extends MiddlewareAccessor {
|
|
|
617
773
|
const tempAllocations = [];
|
|
618
774
|
try {
|
|
619
775
|
const signatureArgs = func.args || [];
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
776
|
+
const totalArgs = Math.max(signatureArgs.length, args.length);
|
|
777
|
+
for (let i = 0; i < totalArgs; i++) {
|
|
778
|
+
const info = MarshallingCallableAccessor.marshalInfo(func.name, signatureArgs[i] || '', args[i]);
|
|
779
|
+
if (!info) {
|
|
780
|
+
if (typeof args[i] === 'string' &&
|
|
781
|
+
/^-?0x[0-9a-fA-F]+$/.test(args[i].trim())) {
|
|
782
|
+
modifiedArgs[i] = Number(BigInt(args[i].trim()));
|
|
783
|
+
}
|
|
623
784
|
continue;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
785
|
+
}
|
|
786
|
+
const allocLen = Math.max(info.buf.length, 1);
|
|
787
|
+
const remoteAddr = Number(await this.root.alloc(allocLen));
|
|
788
|
+
if (info.buf.length > 0) {
|
|
789
|
+
await this.root.write(remoteAddr, info.buf);
|
|
790
|
+
}
|
|
791
|
+
tempAllocations.push({
|
|
792
|
+
address: remoteAddr,
|
|
793
|
+
size: allocLen,
|
|
794
|
+
writeBackTarget: info.writeBackTarget,
|
|
795
|
+
});
|
|
627
796
|
modifiedArgs[i] = remoteAddr;
|
|
628
797
|
}
|
|
629
|
-
|
|
798
|
+
const result = await this.backend.call(func, ...modifiedArgs);
|
|
799
|
+
for (const alloc of tempAllocations) {
|
|
800
|
+
if (alloc.writeBackTarget && alloc.writeBackTarget.length > 0) {
|
|
801
|
+
try {
|
|
802
|
+
const remoteBytes = await this.root.read(alloc.address, alloc.writeBackTarget.length);
|
|
803
|
+
if (remoteBytes && remoteBytes.length > 0) {
|
|
804
|
+
remoteBytes.copy(alloc.writeBackTarget, 0, 0, Math.min(remoteBytes.length, alloc.writeBackTarget.length));
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
// ignore write-back errors
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return result;
|
|
630
813
|
}
|
|
631
814
|
finally {
|
|
632
815
|
for (const alloc of tempAllocations) {
|
|
@@ -644,16 +827,43 @@ export class MarshallingCallableAccessor extends MiddlewareAccessor {
|
|
|
644
827
|
const tempAllocations = [];
|
|
645
828
|
try {
|
|
646
829
|
const signatureArgs = func.args || [];
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
830
|
+
const totalArgs = Math.max(signatureArgs.length, args.length);
|
|
831
|
+
for (let i = 0; i < totalArgs; i++) {
|
|
832
|
+
const info = MarshallingCallableAccessor.marshalInfo(func.name, signatureArgs[i] || '', args[i]);
|
|
833
|
+
if (!info) {
|
|
834
|
+
if (typeof args[i] === 'string' &&
|
|
835
|
+
/^-?0x[0-9a-fA-F]+$/.test(args[i].trim())) {
|
|
836
|
+
modifiedArgs[i] = Number(BigInt(args[i].trim()));
|
|
837
|
+
}
|
|
650
838
|
continue;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
839
|
+
}
|
|
840
|
+
const allocLen = Math.max(info.buf.length, 1);
|
|
841
|
+
const remoteAddr = Number(this.root.allocSync(allocLen));
|
|
842
|
+
if (info.buf.length > 0) {
|
|
843
|
+
this.root.writeSync(remoteAddr, info.buf);
|
|
844
|
+
}
|
|
845
|
+
tempAllocations.push({
|
|
846
|
+
address: remoteAddr,
|
|
847
|
+
size: allocLen,
|
|
848
|
+
writeBackTarget: info.writeBackTarget,
|
|
849
|
+
});
|
|
654
850
|
modifiedArgs[i] = remoteAddr;
|
|
655
851
|
}
|
|
656
|
-
|
|
852
|
+
const result = this.backend.callSync(func, ...modifiedArgs);
|
|
853
|
+
for (const alloc of tempAllocations) {
|
|
854
|
+
if (alloc.writeBackTarget && alloc.writeBackTarget.length > 0) {
|
|
855
|
+
try {
|
|
856
|
+
const remoteBytes = this.root.readSync(alloc.address, alloc.writeBackTarget.length);
|
|
857
|
+
if (remoteBytes && remoteBytes.length > 0) {
|
|
858
|
+
remoteBytes.copy(alloc.writeBackTarget, 0, 0, Math.min(remoteBytes.length, alloc.writeBackTarget.length));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
// ignore write-back errors
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return result;
|
|
657
867
|
}
|
|
658
868
|
finally {
|
|
659
869
|
for (const alloc of tempAllocations) {
|
|
@@ -1161,33 +1371,41 @@ export class FileTransferReadAccessor extends MsvcrtDependentMiddlewareAccessor
|
|
|
1161
1371
|
if (this.isInitializing || !this.tempFilePath) {
|
|
1162
1372
|
return super.readAfterInit(address, size, offset);
|
|
1163
1373
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1374
|
+
try {
|
|
1375
|
+
await this.ensureOpen();
|
|
1376
|
+
const targetAddr = resolveAddress(address) + offset;
|
|
1377
|
+
// Rewind so we overwrite from the start, dump target memory into the
|
|
1378
|
+
// file, then flush so the local read below sees the fresh bytes.
|
|
1379
|
+
await this.backend.call(MsvcrtImpl.rewind, this.hFile);
|
|
1380
|
+
const written = await this.backend.call(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
|
|
1381
|
+
if (Number(written) === size) {
|
|
1382
|
+
await this.backend.call(MsvcrtImpl.fflush, this.hFile);
|
|
1383
|
+
return fs.readFileSync(this.tempFilePath).subarray(0, size);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
catch {
|
|
1387
|
+
// Fall through to backend read on any CRT staging or file lock failure
|
|
1388
|
+
}
|
|
1389
|
+
return super.readAfterInit(address, size, offset);
|
|
1177
1390
|
}
|
|
1178
1391
|
readAfterInitSync(address, size, offset = 0) {
|
|
1179
1392
|
if (this.isInitializingSync || !this.tempFilePath) {
|
|
1180
1393
|
return super.readAfterInitSync(address, size, offset);
|
|
1181
1394
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1395
|
+
try {
|
|
1396
|
+
this.ensureOpenSync();
|
|
1397
|
+
const targetAddr = resolveAddress(address) + offset;
|
|
1398
|
+
this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
|
|
1399
|
+
const written = this.backend.callSync(MsvcrtImpl.fwrite, targetAddr, 1, size, this.hFile);
|
|
1400
|
+
if (Number(written) === size) {
|
|
1401
|
+
this.backend.callSync(MsvcrtImpl.fflush, this.hFile);
|
|
1402
|
+
return fs.readFileSync(this.tempFilePath).subarray(0, size);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
catch {
|
|
1406
|
+
// Fall through to backend read on any CRT staging or file lock failure
|
|
1188
1407
|
}
|
|
1189
|
-
|
|
1190
|
-
return fs.readFileSync(this.tempFilePath).subarray(0, size);
|
|
1408
|
+
return super.readAfterInitSync(address, size, offset);
|
|
1191
1409
|
}
|
|
1192
1410
|
}
|
|
1193
1411
|
/**
|
|
@@ -1307,17 +1525,22 @@ export class FileTransferWriteAccessor extends MsvcrtDependentMiddlewareAccessor
|
|
|
1307
1525
|
}
|
|
1308
1526
|
const targetAddr = resolveAddress(address) + offset;
|
|
1309
1527
|
const buffer = data instanceof Buffer ? data : Buffer.from(data);
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1528
|
+
try {
|
|
1529
|
+
// Put the payload in the file (local, truncating write) BEFORE opening so
|
|
1530
|
+
// fopen("rb") sees a valid file, then rewind the shared FILE* so fread
|
|
1531
|
+
// re-reads the freshly written bytes from offset 0.
|
|
1532
|
+
fs.writeFileSync(this.tempFilePath, buffer);
|
|
1533
|
+
await this.ensureOpen();
|
|
1534
|
+
await this.backend.call(MsvcrtImpl.rewind, this.hFile);
|
|
1535
|
+
const readBytes = await this.backend.call(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
|
|
1536
|
+
if (Number(readBytes) === buffer.length) {
|
|
1537
|
+
return buffer.length;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
catch {
|
|
1541
|
+
// Fall through to backend write on any CRT staging or file lock failure
|
|
1542
|
+
}
|
|
1543
|
+
return super.writeAfterInit(address, data, offset);
|
|
1321
1544
|
}
|
|
1322
1545
|
writeAfterInitSync(address, data, offset = 0) {
|
|
1323
1546
|
if (this.isInitializingSync || !this.tempFilePath) {
|
|
@@ -1325,14 +1548,19 @@ export class FileTransferWriteAccessor extends MsvcrtDependentMiddlewareAccessor
|
|
|
1325
1548
|
}
|
|
1326
1549
|
const targetAddr = resolveAddress(address) + offset;
|
|
1327
1550
|
const buffer = data instanceof Buffer ? data : Buffer.from(data);
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1551
|
+
try {
|
|
1552
|
+
fs.writeFileSync(this.tempFilePath, buffer);
|
|
1553
|
+
this.ensureOpenSync();
|
|
1554
|
+
this.backend.callSync(MsvcrtImpl.rewind, this.hFile);
|
|
1555
|
+
const readBytes = this.backend.callSync(MsvcrtImpl.fread, targetAddr, 1, buffer.length, this.hFile);
|
|
1556
|
+
if (Number(readBytes) === buffer.length) {
|
|
1557
|
+
return buffer.length;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
catch {
|
|
1561
|
+
// Fall through to backend write on any CRT staging or file lock failure
|
|
1562
|
+
}
|
|
1563
|
+
return super.writeAfterInitSync(address, data, offset);
|
|
1336
1564
|
}
|
|
1337
1565
|
}
|
|
1338
1566
|
/**
|