@cheatron/nthread 1.0.0

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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/dist/crt.d.ts +24 -0
  4. package/dist/crt.d.ts.map +1 -0
  5. package/dist/crt.js +28 -0
  6. package/dist/crt.js.map +1 -0
  7. package/dist/errors.d.ts +50 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +84 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/globals.d.ts +48 -0
  12. package/dist/globals.d.ts.map +1 -0
  13. package/dist/globals.js +132 -0
  14. package/dist/globals.js.map +1 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/logger.d.ts +2 -0
  20. package/dist/logger.d.ts.map +1 -0
  21. package/dist/logger.js +3 -0
  22. package/dist/logger.js.map +1 -0
  23. package/dist/memory/alloc-options.d.ts +23 -0
  24. package/dist/memory/alloc-options.d.ts.map +1 -0
  25. package/dist/memory/alloc-options.js +1 -0
  26. package/dist/memory/alloc-options.js.map +1 -0
  27. package/dist/memory/heap.d.ts +147 -0
  28. package/dist/memory/heap.d.ts.map +1 -0
  29. package/dist/memory/heap.js +276 -0
  30. package/dist/memory/heap.js.map +1 -0
  31. package/dist/memory/romem.d.ts +69 -0
  32. package/dist/memory/romem.d.ts.map +1 -0
  33. package/dist/memory/romem.js +108 -0
  34. package/dist/memory/romem.js.map +1 -0
  35. package/dist/nthread-heap.d.ts +65 -0
  36. package/dist/nthread-heap.d.ts.map +1 -0
  37. package/dist/nthread-heap.js +193 -0
  38. package/dist/nthread-heap.js.map +1 -0
  39. package/dist/nthread.d.ts +146 -0
  40. package/dist/nthread.d.ts.map +1 -0
  41. package/dist/nthread.js +421 -0
  42. package/dist/nthread.js.map +1 -0
  43. package/dist/thread/captured-thread.d.ts +68 -0
  44. package/dist/thread/captured-thread.d.ts.map +1 -0
  45. package/dist/thread/captured-thread.js +167 -0
  46. package/dist/thread/captured-thread.js.map +1 -0
  47. package/dist/thread/proxy-thread.d.ts +92 -0
  48. package/dist/thread/proxy-thread.d.ts.map +1 -0
  49. package/dist/thread/proxy-thread.js +154 -0
  50. package/dist/thread/proxy-thread.js.map +1 -0
  51. package/package.json +57 -0
@@ -0,0 +1,65 @@
1
+ import * as Native from '@cheatron/native';
2
+ import { NThread } from './nthread.js';
3
+ import type { CapturedThread } from './thread/captured-thread.js';
4
+ import type { ProxyThread } from './thread/proxy-thread.js';
5
+ import type { GeneralPurposeRegs } from './globals.js';
6
+ import type { AllocOptions } from './memory/alloc-options.js';
7
+ /** Default initial heap block size (bytes). */
8
+ export declare const DEFAULT_NTHREAD_HEAP_SIZE = 65536;
9
+ /** Default maximum heap size — heap doubles up to this limit before falling back to super. */
10
+ export declare const DEFAULT_NTHREAD_HEAP_MAX_SIZE: number;
11
+ /**
12
+ * NThreadHeap extends {@link NThread} with a single growing heap per injection.
13
+ *
14
+ * On first allocation a {@link Heap} of `heapSize` bytes is created in the
15
+ * target process via `calloc`. When it fills up the heap is **doubled** (up to
16
+ * `maxSize`) — the old block is kept resident so its existing allocations can
17
+ * still be freed, but new allocations come from the freshly grown block.
18
+ *
19
+ * When even a `maxSize`-sized block cannot satisfy a request (or `maxSize` is
20
+ * already reached), `super.threadAlloc()` is called — the base {@link NThread}
21
+ * allocator, which uses `msvcrt!malloc` / `calloc` / `realloc`.
22
+ *
23
+ * ### Lifecycle
24
+ * - `proxy.close()` destroys the active heap **and** all previous blocks, then
25
+ * restores the thread context via `super.threadClose()`.
26
+ *
27
+ * ### When to prefer NThreadHeap over NThread
28
+ * - Many small allocations: one `calloc` round-trip per block instead of per alloc
29
+ * - Readonly data: zone-typed allocs benefit from romem snapshot-skip writes
30
+ * - Predictable lifetime: all heap memory freed atomically on `proxy.close()`
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const nt = new NThreadHeap(65536, 524288); // initial 64 KiB, max 512 KiB
35
+ * const [proxy] = await nt.inject(tid);
36
+ *
37
+ * const ptr = await proxy.alloc(64, { readonly: true, fill: 0 });
38
+ * await proxy.write(ptr, myBuffer);
39
+ *
40
+ * await proxy.close(); // destroys all heap blocks, restores thread
41
+ * ```
42
+ */
43
+ export declare class NThreadHeap extends NThread {
44
+ /** Initial heap block size (bytes). */
45
+ readonly heapSize: number;
46
+ /** Maximum heap block size (bytes). Exceeded → super.threadAlloc(). */
47
+ readonly maxSize: number;
48
+ private state;
49
+ constructor(heapSize?: number, maxSize?: number, processId?: number, sleepAddress?: Native.NativePointer, pushretAddress?: Native.NativePointer, regKey?: GeneralPurposeRegs);
50
+ protected threadClose(proxy: ProxyThread, captured: CapturedThread, suicide?: number): Promise<void>;
51
+ protected threadAlloc(proxy: ProxyThread, size: number, opts?: AllocOptions): Promise<Native.NativePointer>;
52
+ protected threadFree(proxy: ProxyThread, ptr: Native.NativePointer): Promise<void>;
53
+ private getState;
54
+ /**
55
+ * Tries to allocate from the active heap. Grows the heap if full (up to
56
+ * `maxSize`). Returns `null` when the request must be served by super.
57
+ */
58
+ private allocFromHeap;
59
+ /** Silent wrapper around `Heap.alloc` / `Heap.allocReadonly`. Returns `null` on failure. */
60
+ private tryAlloc;
61
+ /** Computes readonly zone size for a new heap block. */
62
+ private calcRoSize;
63
+ private reallocInternal;
64
+ }
65
+ //# sourceMappingURL=nthread-heap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nthread-heap.d.ts","sourceRoot":"","sources":["../src/nthread-heap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,+CAA+C;AAC/C,eAAO,MAAM,yBAAyB,QAAQ,CAAC;AAE/C,8FAA8F;AAC9F,eAAO,MAAM,6BAA6B,QAAgC,CAAC;AAc3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,WAAY,SAAQ,OAAO;IACtC,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,KAAK,CAAsC;gBAGjD,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,CAAC,aAAa,EACnC,cAAc,CAAC,EAAE,MAAM,CAAC,aAAa,EACrC,MAAM,CAAC,EAAE,kBAAkB;cAWJ,WAAW,CAClC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,cAAc,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;cAYS,WAAW,CAClC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC;cAoBP,UAAU,CACjC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,MAAM,CAAC,aAAa,GACxB,OAAO,CAAC,IAAI,CAAC;IAiBhB,OAAO,CAAC,QAAQ;IAShB;;;OAGG;YACW,aAAa;IA4C3B,4FAA4F;IAC5F,OAAO,CAAC,QAAQ;IAQhB,wDAAwD;IACxD,OAAO,CAAC,UAAU;YAIJ,eAAe;CA0D9B"}
@@ -0,0 +1,193 @@
1
+ import * as Native from '@cheatron/native';
2
+ import { NThread } from './nthread.js';
3
+ import { Heap } from './memory/heap.js';
4
+ /** Default initial heap block size (bytes). */
5
+ export const DEFAULT_NTHREAD_HEAP_SIZE = 65536;
6
+ /** Default maximum heap size — heap doubles up to this limit before falling back to super. */
7
+ export const DEFAULT_NTHREAD_HEAP_MAX_SIZE = DEFAULT_NTHREAD_HEAP_SIZE * 8; // 512 KiB
8
+ /**
9
+ * NThreadHeap extends {@link NThread} with a single growing heap per injection.
10
+ *
11
+ * On first allocation a {@link Heap} of `heapSize` bytes is created in the
12
+ * target process via `calloc`. When it fills up the heap is **doubled** (up to
13
+ * `maxSize`) — the old block is kept resident so its existing allocations can
14
+ * still be freed, but new allocations come from the freshly grown block.
15
+ *
16
+ * When even a `maxSize`-sized block cannot satisfy a request (or `maxSize` is
17
+ * already reached), `super.threadAlloc()` is called — the base {@link NThread}
18
+ * allocator, which uses `msvcrt!malloc` / `calloc` / `realloc`.
19
+ *
20
+ * ### Lifecycle
21
+ * - `proxy.close()` destroys the active heap **and** all previous blocks, then
22
+ * restores the thread context via `super.threadClose()`.
23
+ *
24
+ * ### When to prefer NThreadHeap over NThread
25
+ * - Many small allocations: one `calloc` round-trip per block instead of per alloc
26
+ * - Readonly data: zone-typed allocs benefit from romem snapshot-skip writes
27
+ * - Predictable lifetime: all heap memory freed atomically on `proxy.close()`
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const nt = new NThreadHeap(65536, 524288); // initial 64 KiB, max 512 KiB
32
+ * const [proxy] = await nt.inject(tid);
33
+ *
34
+ * const ptr = await proxy.alloc(64, { readonly: true, fill: 0 });
35
+ * await proxy.write(ptr, myBuffer);
36
+ *
37
+ * await proxy.close(); // destroys all heap blocks, restores thread
38
+ * ```
39
+ */
40
+ export class NThreadHeap extends NThread {
41
+ /** Initial heap block size (bytes). */
42
+ heapSize;
43
+ /** Maximum heap block size (bytes). Exceeded → super.threadAlloc(). */
44
+ maxSize;
45
+ state = new Map();
46
+ constructor(heapSize, maxSize, processId, sleepAddress, pushretAddress, regKey) {
47
+ super(processId, sleepAddress, pushretAddress, regKey);
48
+ this.heapSize = heapSize ?? DEFAULT_NTHREAD_HEAP_SIZE;
49
+ this.maxSize = maxSize ?? DEFAULT_NTHREAD_HEAP_MAX_SIZE;
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Overrides
53
+ // ---------------------------------------------------------------------------
54
+ async threadClose(proxy, captured, suicide) {
55
+ const s = this.state.get(proxy);
56
+ if (s) {
57
+ const allHeaps = s.heap ? [...s.prevHeaps, s.heap] : s.prevHeaps;
58
+ for (const heap of allHeaps) {
59
+ await heap.destroy(proxy);
60
+ }
61
+ this.state.delete(proxy);
62
+ }
63
+ await super.threadClose(proxy, captured, suicide);
64
+ }
65
+ async threadAlloc(proxy, size, opts) {
66
+ if (opts?.address) {
67
+ return this.reallocInternal(proxy, opts.address, size, opts);
68
+ }
69
+ const ro = opts?.readonly ?? false;
70
+ const ptr = await this.allocFromHeap(proxy, size, ro);
71
+ // ptr === null → heap can't serve it; fall back to super (NThread/malloc)
72
+ if (ptr === null) {
73
+ return super.threadAlloc(proxy, size, opts);
74
+ }
75
+ if (opts?.fill !== undefined) {
76
+ await proxy.write(ptr, Buffer.alloc(size, opts.fill & 0xff));
77
+ }
78
+ return ptr;
79
+ }
80
+ async threadFree(proxy, ptr) {
81
+ const s = this.state.get(proxy);
82
+ const entry = s?.allocations.get(ptr.address);
83
+ s?.allocations.delete(ptr.address);
84
+ // Unknown or super-backed → delegate to NThread base (crt.free)
85
+ if (!entry || entry === 'super') {
86
+ return super.threadFree(proxy, ptr);
87
+ }
88
+ entry.heap.free(entry.alloc);
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Internal helpers
92
+ // ---------------------------------------------------------------------------
93
+ getState(proxy) {
94
+ let s = this.state.get(proxy);
95
+ if (!s) {
96
+ s = { heap: null, prevHeaps: [], allocations: new Map() };
97
+ this.state.set(proxy, s);
98
+ }
99
+ return s;
100
+ }
101
+ /**
102
+ * Tries to allocate from the active heap. Grows the heap if full (up to
103
+ * `maxSize`). Returns `null` when the request must be served by super.
104
+ */
105
+ async allocFromHeap(proxy, size, ro) {
106
+ const s = this.getState(proxy);
107
+ if (s.heap) {
108
+ // Try current active heap
109
+ const result = this.tryAlloc(s.heap, size, ro);
110
+ if (result) {
111
+ s.allocations.set(result.remote.address, {
112
+ alloc: result,
113
+ heap: s.heap,
114
+ });
115
+ return result.remote;
116
+ }
117
+ // Full — can we grow?
118
+ if (s.heap.totalSize >= this.maxSize)
119
+ return null; // at ceiling
120
+ const newSize = Math.min(s.heap.totalSize * 2, this.maxSize);
121
+ if (size > newSize)
122
+ return null; // request too big for any heap block
123
+ s.prevHeaps.push(s.heap);
124
+ s.heap = await Heap.create(proxy, newSize, this.calcRoSize(newSize, ro));
125
+ }
126
+ else {
127
+ // First alloc — create the initial heap
128
+ if (size > this.maxSize)
129
+ return null;
130
+ const initSize = Math.max(Math.min(this.heapSize, this.maxSize), size);
131
+ s.heap = await Heap.create(proxy, initSize, this.calcRoSize(initSize, ro));
132
+ }
133
+ const result = this.tryAlloc(s.heap, size, ro);
134
+ if (!result)
135
+ return null; // shouldn't happen
136
+ s.allocations.set(result.remote.address, { alloc: result, heap: s.heap });
137
+ return result.remote;
138
+ }
139
+ /** Silent wrapper around `Heap.alloc` / `Heap.allocReadonly`. Returns `null` on failure. */
140
+ tryAlloc(heap, size, ro) {
141
+ try {
142
+ return ro ? heap.allocReadonly(size) : heap.alloc(size);
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ /** Computes readonly zone size for a new heap block. */
149
+ calcRoSize(totalSize, ro) {
150
+ return ro ? Math.floor((totalSize * 3) / 4) : Math.floor(totalSize / 4);
151
+ }
152
+ async reallocInternal(proxy, address, newSize, opts) {
153
+ const s = this.getState(proxy);
154
+ const entry = s.allocations.get(address.address);
155
+ if (!entry || entry === 'super') {
156
+ // Delegate entirely to NThread base (CRT realloc path)
157
+ if (entry === 'super')
158
+ s.allocations.delete(address.address);
159
+ const newPtr = await super.threadAlloc(proxy, newSize, opts);
160
+ s.allocations.set(newPtr.address, 'super');
161
+ return newPtr;
162
+ }
163
+ // Detect old zone: address < base + roSize → was readonly
164
+ const oldRo = entry.alloc.remote.address <
165
+ entry.heap.base.address + BigInt(entry.heap.roSize);
166
+ // Preserve old zone unless caller explicitly requests a change
167
+ const ro = opts?.readonly ?? oldRo;
168
+ // Allocate new block (heap if possible, otherwise super/CRT — but NOT realloc on old address)
169
+ const newRaw = await this.allocFromHeap(proxy, newSize, ro);
170
+ const newPtr = newRaw ??
171
+ (await super.threadAlloc(proxy, newSize, {
172
+ ...opts,
173
+ address: undefined,
174
+ }));
175
+ if (!newRaw)
176
+ s.allocations.set(newPtr.address, 'super');
177
+ // Copy old content
178
+ const copyLen = Math.min(entry.alloc.size, newSize);
179
+ if (copyLen > 0) {
180
+ const oldData = await proxy.read(address, copyLen);
181
+ await proxy.write(newPtr, oldData);
182
+ }
183
+ // Fill newly added bytes when growing
184
+ if (newSize > copyLen && opts?.fill !== undefined) {
185
+ const fillPtr = new Native.NativePointer(newPtr.address + BigInt(copyLen));
186
+ await proxy.write(fillPtr, Buffer.alloc(newSize - copyLen, opts.fill & 0xff));
187
+ }
188
+ entry.heap.free(entry.alloc);
189
+ s.allocations.delete(address.address);
190
+ return newPtr;
191
+ }
192
+ }
193
+ //# sourceMappingURL=nthread-heap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nthread-heap.js","sourceRoot":"","sources":["../src/nthread-heap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,IAAI,EAAkB,MAAM,kBAAkB,CAAC;AAMxD,+CAA+C;AAC/C,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAK,CAAC;AAE/C,8FAA8F;AAC9F,MAAM,CAAC,MAAM,6BAA6B,GAAG,yBAAyB,GAAG,CAAC,CAAC,CAAC,UAAU;AActF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,WAAY,SAAQ,OAAO;IACtC,uCAAuC;IAC9B,QAAQ,CAAS;IAC1B,uEAAuE;IAC9D,OAAO,CAAS;IAEjB,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEnD,YACE,QAAiB,EACjB,OAAgB,EAChB,SAAkB,EAClB,YAAmC,EACnC,cAAqC,EACrC,MAA2B;QAE3B,KAAK,CAAC,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,yBAAyB,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,6BAA6B,CAAC;IAC1D,CAAC;IAED,8EAA8E;IAC9E,YAAY;IACZ,8EAA8E;IAE3D,KAAK,CAAC,WAAW,CAClC,KAAkB,EAClB,QAAwB,EACxB,OAAgB;QAEhB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC;YACN,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACjE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAEkB,KAAK,CAAC,WAAW,CAClC,KAAkB,EAClB,IAAY,EACZ,IAAmB;QAEnB,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,EAAE,GAAG,IAAI,EAAE,QAAQ,IAAI,KAAK,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAEtD,0EAA0E;QAC1E,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9C,CAAC;QAED,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;IAEkB,KAAK,CAAC,UAAU,CACjC,KAAkB,EAClB,GAAyB;QAEzB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,CAAC,EAAE,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEnC,gEAAgE;QAChE,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,8EAA8E;IAC9E,mBAAmB;IACnB,8EAA8E;IAEtE,QAAQ,CAAC,KAAkB;QACjC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;YAC1D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,aAAa,CACzB,KAAkB,EAClB,IAAY,EACZ,EAAW;QAEX,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE/B,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACX,0BAA0B;YAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACX,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;oBACvC,KAAK,EAAE,MAAM;oBACb,IAAI,EAAE,CAAC,CAAC,IAAI;iBACb,CAAC,CAAC;gBACH,OAAO,MAAM,CAAC,MAAM,CAAC;YACvB,CAAC;YAED,sBAAsB;YACtB,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,CAAC,aAAa;YAEhE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7D,IAAI,IAAI,GAAG,OAAO;gBAAE,OAAO,IAAI,CAAC,CAAC,qCAAqC;YAEtE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC,CAAC,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,wCAAwC;YACxC,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;YACvE,CAAC,CAAC,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CACxB,KAAK,EACL,QAAQ,EACR,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAC9B,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,CAAC,mBAAmB;QAE7C,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,4FAA4F;IACpF,QAAQ,CAAC,IAAU,EAAE,IAAY,EAAE,EAAW;QACpD,IAAI,CAAC;YACH,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,wDAAwD;IAChD,UAAU,CAAC,SAAiB,EAAE,EAAW;QAC/C,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAC1E,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,KAAkB,EAClB,OAA6B,EAC7B,OAAe,EACf,IAAmB;QAEnB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEjD,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAChC,uDAAuD;YACvD,IAAI,KAAK,KAAK,OAAO;gBAAE,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC7D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YAC7D,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC3C,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,0DAA0D;QAC1D,MAAM,KAAK,GACT,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO;YAC1B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEtD,+DAA+D;QAC/D,MAAM,EAAE,GAAG,IAAI,EAAE,QAAQ,IAAI,KAAK,CAAC;QAEnC,8FAA8F;QAC9F,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,MAAM,GACV,MAAM;YACN,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE;gBACvC,GAAG,IAAI;gBACP,OAAO,EAAE,SAAS;aACnB,CAAC,CAAC,CAAC;QACN,IAAI,CAAC,MAAM;YAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAExD,mBAAmB;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,sCAAsC;QACtC,IAAI,OAAO,GAAG,OAAO,IAAI,IAAI,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;YAClD,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,aAAa,CACtC,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CACjC,CAAC;YACF,MAAM,KAAK,CAAC,KAAK,CACf,OAAO,EACP,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAClD,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEtC,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -0,0 +1,146 @@
1
+ import * as Native from '@cheatron/native';
2
+ import { type GeneralPurposeRegs } from './globals.js';
3
+ import { ProxyThread } from './thread/proxy-thread.js';
4
+ import { CapturedThread } from './thread/captured-thread.js';
5
+ import type { AllocOptions } from './memory/alloc-options.js';
6
+ export { STACK_ADD } from './thread/captured-thread.js';
7
+ /**
8
+ * Register-compatible argument type.
9
+ * All values are ultimately converted to bigint for register assignment (RCX, RDX, R8, R9).
10
+ * Accepts NativePointer (→ .address) and number (→ BigInt()) for convenience.
11
+ */
12
+ export type Arg = bigint | number | Native.NativePointer;
13
+ /**
14
+ * NThread: Orchestrates non-invasive thread hijacking.
15
+ *
16
+ * It works by suspending a target thread, redirecting its Rip to a 'pushreg/ret' gadget,
17
+ * setting a 'sleep' address as the target, and waiting for the thread to 'land'
18
+ * at said sleep address. Once landed, the thread is effectively seized without
19
+ * stopping the underlying process or requiring complex debugging APIs.
20
+ *
21
+ * The actual thread state (context cache, suspend tracking, proxy) is managed by
22
+ * the contained CapturedThread instance, created during inject().
23
+ */
24
+ export declare class NThread {
25
+ /** Optional process ID for diagnostics and logging */
26
+ processId?: number;
27
+ /** Address of an infinite loop gadget ('jmp .') used to hold the thread */
28
+ sleepAddress: Native.NativePointer;
29
+ /** Address of a pivot gadget ('push reg; ret') used to redirect execution */
30
+ pushretAddress: Native.NativePointer;
31
+ /** The register key (e.g., 'Rbx') used for the pushret pivot */
32
+ regKey: GeneralPurposeRegs;
33
+ /**
34
+ * Creates an NThread instance and prepares redirect gadgets.
35
+ * @param processId Optional process ID for diagnostics and logging.
36
+ * @param sleepAddress Optional explicit sleep gadget.
37
+ * @param pushretAddress Optional explicit pushret gadget.
38
+ * @param regKey Optional register preference for the pushret gadget.
39
+ */
40
+ constructor(processId?: number, sleepAddress?: Native.NativePointer, pushretAddress?: Native.NativePointer, regKey?: GeneralPurposeRegs);
41
+ /**
42
+ * Executes the hijacking flow:
43
+ * 1. Create a CapturedThread from the given thread parameter.
44
+ * 2. Suspend the thread.
45
+ * 3. Capture and save current register state.
46
+ * 4. Redirect Rip to PushRet gadget.
47
+ * 5. Point the chosen register (RegKey) to the Sleep gadget.
48
+ * 6. Adjust stack (Rsp) to a safe scratch area.
49
+ * 7. Resume and wait for the thread to 'trap' itself in the loop.
50
+ *
51
+ * @param thread Thread object or Thread ID to hijack.
52
+ */
53
+ inject(thread: Native.Thread | number): Promise<[ProxyThread, CapturedThread]>;
54
+ /** Writes data to the target process; dispatches NativePointer vs Buffer. */
55
+ private threadWrite;
56
+ /**
57
+ * Hook: called to release the proxy and captured thread.
58
+ * Subclasses can override to perform cleanup (e.g. destroy a heap pool)
59
+ * before closing. Default: terminate (if suicide) then close the handle.
60
+ */
61
+ protected threadClose(_proxy: ProxyThread, captured: CapturedThread, suicide?: number): Promise<void>;
62
+ /**
63
+ * Hook: allocates memory in the target process.
64
+ * Default: `malloc` / `calloc` / `malloc+memset` depending on `opts.fill`;
65
+ * delegates to `msvcrt!realloc` when `opts.address` is provided.
66
+ * Subclasses can override to use a pre-allocated heap instead.
67
+ */
68
+ protected threadAlloc(proxy: ProxyThread, size: number, opts?: AllocOptions): Promise<Native.NativePointer>;
69
+ /**
70
+ * Hook: frees a pointer in the target process.
71
+ * Default: `msvcrt!free`.
72
+ * Subclasses can override to return the block to a managed heap instead.
73
+ */
74
+ protected threadFree(proxy: ProxyThread, ptr: Native.NativePointer): Promise<void>;
75
+ /**
76
+ * Allocates memory for a string and writes it into the remote process via the captured thread.
77
+ * Null-terminates automatically.
78
+ *
79
+ * @param proxy The proxy for the captured thread.
80
+ * @param str String to encode and write.
81
+ * @param encoding Buffer encoding — defaults to `'utf16le'` (Windows wide string).
82
+ * @param opts Optional alloc options forwarded to `proxy.alloc()`.
83
+ */
84
+ allocString(proxy: ProxyThread, str: string, encoding?: BufferEncoding, opts?: AllocOptions): Promise<Native.NativePointer>;
85
+ /**
86
+ * Executes a function call on a captured thread using the Windows x64 calling convention.
87
+ * The thread must be parked at the sleep address (after inject()).
88
+ *
89
+ * Supports up to 4 parameters mapped to RCX, RDX, R8, R9.
90
+ * Returns the value from RAX after the function completes.
91
+ *
92
+ * @param thread The captured thread to execute on.
93
+ * @param target Address of the function to call.
94
+ * @param args Up to 4 arguments (RCX, RDX, R8, R9).
95
+ * @param timeoutMs Timeout in ms for waiting on the function return (default: 5000).
96
+ * @returns RAX value as NativePointer.
97
+ */
98
+ threadCall(thread: CapturedThread, target: Native.NativePointer | bigint, args?: Arg[], timeoutMs?: number): Promise<Native.NativePointer>;
99
+ /**
100
+ * Writes arbitrary data to the target process memory using hijacked memset calls.
101
+ * Decomposes the source buffer into runs of equal bytes and issues one
102
+ * `msvcrt!memset(dest + offset, value, runLength)` call per run.
103
+ *
104
+ * If the write range overlaps a registered read-only memory region, the
105
+ * overlapping portion is routed through writeMemorySafeBuffer (skips unchanged
106
+ * bytes) and the non-overlapping parts are written normally via recursive calls.
107
+ *
108
+ * @param dest Target address to write to.
109
+ * @param source The data to write.
110
+ */
111
+ writeMemory(proxy: ProxyThread, dest: Native.NativePointer | bigint, source: Buffer | Uint8Array): Promise<number>;
112
+ /**
113
+ * Writes data from a NativePointer source to the target process memory
114
+ * using hijacked memset calls. Reads the source pointer byte-by-byte into
115
+ * a local buffer first, then delegates to the standard decomposed memset.
116
+ *
117
+ * Does NOT check read-only memory regions — this is intended for writing
118
+ * data we don't already know the contents of.
119
+ *
120
+ * @param thread The captured thread to execute on.
121
+ * @param dest Target address to write to.
122
+ * @param source Source pointer to read from (in our process).
123
+ * @param size Number of bytes to write.
124
+ */
125
+ writeMemoryWithPointer(proxy: ProxyThread, dest: Native.NativePointer | bigint, source: Native.NativePointer, size: number): Promise<number>;
126
+ /**
127
+ * Safe write dispatcher: routes to the optimized variant based on `lastDest` type.
128
+ *
129
+ * @param dest Target address to write to.
130
+ * @param source The data to write.
131
+ * @param lastDest Either a snapshot Buffer of the previous state, or a single byte value
132
+ * representing a uniform fill (e.g. 0 means the region is all zeroes).
133
+ */
134
+ writeMemorySafe(proxy: ProxyThread, dest: Native.NativePointer, source: Buffer, lastDest: Buffer | number): Promise<number>;
135
+ /**
136
+ * Safe write against a uniform fill value.
137
+ * Skips bytes that already equal `fillByte`.
138
+ */
139
+ private writeMemorySafeUniform;
140
+ /**
141
+ * Safe write against a snapshot Buffer.
142
+ * Skips bytes that match the corresponding byte in `last`.
143
+ */
144
+ private writeMemorySafeBuffer;
145
+ }
146
+ //# sourceMappingURL=nthread.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nthread.d.ts","sourceRoot":"","sources":["../src/nthread.ts"],"names":[],"mappings":"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"}