hexcore-unicorn 1.2.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.
- package/README.md +307 -0
- package/binding.gyp +91 -0
- package/deps/unicorn/include/unicorn/arm.h +235 -0
- package/deps/unicorn/include/unicorn/arm64.h +393 -0
- package/deps/unicorn/include/unicorn/m68k.h +81 -0
- package/deps/unicorn/include/unicorn/mips.h +283 -0
- package/deps/unicorn/include/unicorn/platform.h +263 -0
- package/deps/unicorn/include/unicorn/ppc.h +434 -0
- package/deps/unicorn/include/unicorn/riscv.h +316 -0
- package/deps/unicorn/include/unicorn/s390x.h +200 -0
- package/deps/unicorn/include/unicorn/sparc.h +173 -0
- package/deps/unicorn/include/unicorn/tricore.h +174 -0
- package/deps/unicorn/include/unicorn/unicorn.h +1456 -0
- package/deps/unicorn/include/unicorn/x86.h +1681 -0
- package/deps/unicorn/unicorn-import.lib +0 -0
- package/deps/unicorn/unicorn.dll +0 -0
- package/index.d.ts +792 -0
- package/index.js +167 -0
- package/index.mjs +40 -0
- package/package.json +87 -0
- package/src/emu_async_worker.h +179 -0
- package/src/main.cpp +717 -0
- package/src/unicorn_wrapper.cpp +1536 -0
- package/src/unicorn_wrapper.h +379 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
#ifndef UNICORN_WRAPPER_H
|
|
6
|
+
#define UNICORN_WRAPPER_H
|
|
7
|
+
|
|
8
|
+
#include <napi.h>
|
|
9
|
+
#include <unicorn/unicorn.h>
|
|
10
|
+
#include <unordered_map>
|
|
11
|
+
#include <unordered_set>
|
|
12
|
+
#include <map>
|
|
13
|
+
#include <memory>
|
|
14
|
+
#include <vector>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <atomic>
|
|
17
|
+
|
|
18
|
+
// Forward declarations
|
|
19
|
+
struct HookData;
|
|
20
|
+
class UnicornContext;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* UnicornWrapper - N-API wrapper for Unicorn Engine
|
|
24
|
+
*
|
|
25
|
+
* HikariSystem HexCore - Unicorn Emulator Bindings
|
|
26
|
+
* Provides CPU emulation capabilities with hook support
|
|
27
|
+
*/
|
|
28
|
+
class UnicornWrapper : public Napi::ObjectWrap<UnicornWrapper> {
|
|
29
|
+
public:
|
|
30
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
31
|
+
static Napi::FunctionReference constructor;
|
|
32
|
+
|
|
33
|
+
UnicornWrapper(const Napi::CallbackInfo& info);
|
|
34
|
+
~UnicornWrapper();
|
|
35
|
+
|
|
36
|
+
// Get the engine handle (for internal use)
|
|
37
|
+
uc_engine* GetEngine() const { return engine_; }
|
|
38
|
+
bool IsClosed() const { return closed_; }
|
|
39
|
+
|
|
40
|
+
private:
|
|
41
|
+
uc_engine* engine_;
|
|
42
|
+
uc_arch arch_;
|
|
43
|
+
uc_mode mode_;
|
|
44
|
+
bool closed_;
|
|
45
|
+
std::atomic<bool> emulating_;
|
|
46
|
+
std::mutex hookMutex_;
|
|
47
|
+
|
|
48
|
+
// Map of active hooks: hook handle -> HookData
|
|
49
|
+
std::unordered_map<uc_hook, std::unique_ptr<HookData>> hooks_;
|
|
50
|
+
uc_hook nextHookId_;
|
|
51
|
+
|
|
52
|
+
// Native Breakpoints
|
|
53
|
+
std::unordered_set<uint64_t> breakpoints_;
|
|
54
|
+
uc_hook breakpointHookHandle_ = 0;
|
|
55
|
+
bool hasBreakpointHook_ = false;
|
|
56
|
+
|
|
57
|
+
// Shared Memory References (Keep buffers alive)
|
|
58
|
+
std::map<uint64_t, Napi::ObjectReference> mappedBuffers_;
|
|
59
|
+
|
|
60
|
+
// ============== Emulation Control ==============
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Start emulation
|
|
64
|
+
* @param begin - Start address
|
|
65
|
+
* @param until - End address (0 to run until error/hook stop)
|
|
66
|
+
* @param timeout - Timeout in microseconds (0 for no timeout)
|
|
67
|
+
* @param count - Number of instructions to execute (0 for unlimited)
|
|
68
|
+
*/
|
|
69
|
+
Napi::Value EmuStart(const Napi::CallbackInfo& info);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Start emulation asynchronously
|
|
73
|
+
* Returns a Promise that resolves when emulation completes
|
|
74
|
+
*/
|
|
75
|
+
Napi::Value EmuStartAsync(const Napi::CallbackInfo& info);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Stop emulation (can be called from hooks)
|
|
79
|
+
*/
|
|
80
|
+
Napi::Value EmuStop(const Napi::CallbackInfo& info);
|
|
81
|
+
|
|
82
|
+
// ============== Memory Operations ==============
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Map a memory region
|
|
86
|
+
* @param address - Start address (must be aligned to 4KB)
|
|
87
|
+
* @param size - Size in bytes (must be multiple of 4KB)
|
|
88
|
+
* @param perms - Memory permissions (PROT.READ | PROT.WRITE | PROT.EXEC)
|
|
89
|
+
*/
|
|
90
|
+
Napi::Value MemMap(const Napi::CallbackInfo& info);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Map a memory region with existing data
|
|
94
|
+
* @param address - Start address
|
|
95
|
+
* @param data - Buffer containing initial data
|
|
96
|
+
* @param perms - Memory permissions
|
|
97
|
+
*/
|
|
98
|
+
Napi::Value MemMapPtr(const Napi::CallbackInfo& info);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Unmap a memory region
|
|
102
|
+
*/
|
|
103
|
+
Napi::Value MemUnmap(const Napi::CallbackInfo& info);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Change memory permissions
|
|
107
|
+
*/
|
|
108
|
+
Napi::Value MemProtect(const Napi::CallbackInfo& info);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read memory
|
|
112
|
+
* @param address - Address to read from
|
|
113
|
+
* @param size - Number of bytes to read
|
|
114
|
+
* @returns Buffer containing the data
|
|
115
|
+
*/
|
|
116
|
+
Napi::Value MemRead(const Napi::CallbackInfo& info);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Write memory
|
|
120
|
+
* @param address - Address to write to
|
|
121
|
+
* @param data - Buffer containing data to write
|
|
122
|
+
*/
|
|
123
|
+
Napi::Value MemWrite(const Napi::CallbackInfo& info);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get list of mapped memory regions
|
|
127
|
+
* @returns Array of {begin, end, perms} objects
|
|
128
|
+
*/
|
|
129
|
+
Napi::Value MemRegions(const Napi::CallbackInfo& info);
|
|
130
|
+
|
|
131
|
+
// ============== Register Operations ==============
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Read a register value
|
|
135
|
+
* @param regId - Register ID (architecture-specific)
|
|
136
|
+
* @returns BigInt for 64-bit values, Number for smaller
|
|
137
|
+
*/
|
|
138
|
+
Napi::Value RegRead(const Napi::CallbackInfo& info);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Write a register value
|
|
142
|
+
* @param regId - Register ID
|
|
143
|
+
* @param value - Value to write (BigInt or Number)
|
|
144
|
+
*/
|
|
145
|
+
Napi::Value RegWrite(const Napi::CallbackInfo& info);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Read multiple registers at once
|
|
149
|
+
* @param regIds - Array of register IDs
|
|
150
|
+
* @returns Array of values
|
|
151
|
+
*/
|
|
152
|
+
Napi::Value RegReadBatch(const Napi::CallbackInfo& info);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Write multiple registers at once
|
|
156
|
+
* @param regIds - Array of register IDs
|
|
157
|
+
* @param values - Array of values
|
|
158
|
+
*/
|
|
159
|
+
Napi::Value RegWriteBatch(const Napi::CallbackInfo& info);
|
|
160
|
+
|
|
161
|
+
// ============== Hook Operations ==============
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Add a hook
|
|
165
|
+
* @param type - Hook type (HOOK.CODE, HOOK.MEM_READ, etc.)
|
|
166
|
+
* @param callback - JavaScript function to call
|
|
167
|
+
* @param begin - Start address (optional, default 1)
|
|
168
|
+
* @param end - End address (optional, default 0 = all addresses)
|
|
169
|
+
* @param extra - Extra argument for instruction hooks (optional)
|
|
170
|
+
* @returns Hook handle (number)
|
|
171
|
+
*/
|
|
172
|
+
Napi::Value HookAdd(const Napi::CallbackInfo& info);
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Remove a hook
|
|
176
|
+
* @param hookHandle - Handle returned by hookAdd
|
|
177
|
+
*/
|
|
178
|
+
Napi::Value HookDel(const Napi::CallbackInfo& info);
|
|
179
|
+
|
|
180
|
+
// ============== Native Breakpoints ==============
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Add a native breakpoint
|
|
184
|
+
* @param address - Address to break at
|
|
185
|
+
*/
|
|
186
|
+
Napi::Value BreakpointAdd(const Napi::CallbackInfo& info);
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Remove a native breakpoint
|
|
190
|
+
* @param address - Address to remove
|
|
191
|
+
*/
|
|
192
|
+
Napi::Value BreakpointDel(const Napi::CallbackInfo& info);
|
|
193
|
+
|
|
194
|
+
// ============== Context Operations ==============
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Save the current CPU context
|
|
198
|
+
* @returns UnicornContext object
|
|
199
|
+
*/
|
|
200
|
+
Napi::Value ContextSave(const Napi::CallbackInfo& info);
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Restore a previously saved context
|
|
204
|
+
* @param context - UnicornContext object
|
|
205
|
+
*/
|
|
206
|
+
Napi::Value ContextRestore(const Napi::CallbackInfo& info);
|
|
207
|
+
|
|
208
|
+
// ============== Snapshot Operations ==============
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Save full emulation state (Context + Memory)
|
|
212
|
+
* @return { context: Buffer, memory: [ { address, size, perms, data } ] }
|
|
213
|
+
*/
|
|
214
|
+
Napi::Value StateSave(const Napi::CallbackInfo& info);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Restore full emulation state
|
|
218
|
+
* @param state - The object returned by StateSave
|
|
219
|
+
*/
|
|
220
|
+
Napi::Value StateRestore(const Napi::CallbackInfo& info);
|
|
221
|
+
|
|
222
|
+
// ============== Query & Control ==============
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Query engine information
|
|
226
|
+
* @param queryType - QUERY.MODE, QUERY.PAGE_SIZE, QUERY.ARCH
|
|
227
|
+
* @returns Query result
|
|
228
|
+
*/
|
|
229
|
+
Napi::Value Query(const Napi::CallbackInfo& info);
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set engine option
|
|
233
|
+
* @param optType - Option type
|
|
234
|
+
* @param value - Option value
|
|
235
|
+
*/
|
|
236
|
+
Napi::Value CtlWrite(const Napi::CallbackInfo& info);
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get engine option
|
|
240
|
+
* @param optType - Option type
|
|
241
|
+
* @returns Option value
|
|
242
|
+
*/
|
|
243
|
+
Napi::Value CtlRead(const Napi::CallbackInfo& info);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Close the engine and free resources
|
|
247
|
+
*/
|
|
248
|
+
Napi::Value Close(const Napi::CallbackInfo& info);
|
|
249
|
+
|
|
250
|
+
// ============== Property Getters ==============
|
|
251
|
+
|
|
252
|
+
Napi::Value GetArch(const Napi::CallbackInfo& info);
|
|
253
|
+
Napi::Value GetMode(const Napi::CallbackInfo& info);
|
|
254
|
+
Napi::Value GetHandle(const Napi::CallbackInfo& info);
|
|
255
|
+
Napi::Value GetPageSize(const Napi::CallbackInfo& info);
|
|
256
|
+
|
|
257
|
+
// ============== Internal Helpers ==============
|
|
258
|
+
|
|
259
|
+
void ThrowUnicornError(Napi::Env env, uc_err err, const char* context = nullptr);
|
|
260
|
+
void CleanupHooks();
|
|
261
|
+
|
|
262
|
+
// Determine register size based on architecture and register ID
|
|
263
|
+
size_t GetRegisterSize(int regId);
|
|
264
|
+
|
|
265
|
+
// Check if register is 64-bit
|
|
266
|
+
bool Is64BitRegister(int regId);
|
|
267
|
+
|
|
268
|
+
public:
|
|
269
|
+
// Helper for checking breakpoints from static callback
|
|
270
|
+
bool IsBreakpointHit(uint64_t address) {
|
|
271
|
+
// No lock needed here as we only read, and it's called from the same thread as emulation
|
|
272
|
+
// strict consistency isn't critical for a breakpoint check
|
|
273
|
+
// (if we miss one cycle due to race during add, it's fine)
|
|
274
|
+
// But for correctness with the defined mutex:
|
|
275
|
+
// std::lock_guard<std::mutex> lock(hookMutex_);
|
|
276
|
+
// Locking every instruction IS expensive.
|
|
277
|
+
// Since emulation is single-threaded usually, and JS calls add/del from main thread,
|
|
278
|
+
// there IS a race if we add/del while running async.
|
|
279
|
+
// However, standard use case is add/del while paused.
|
|
280
|
+
// If async, we might need a lockless atomic check or read-copy-update.
|
|
281
|
+
// For now, avoiding lock for perf.
|
|
282
|
+
return breakpoints_.count(address) > 0;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* UnicornContext - Wrapper for saved CPU context
|
|
288
|
+
*/
|
|
289
|
+
class UnicornContext : public Napi::ObjectWrap<UnicornContext> {
|
|
290
|
+
public:
|
|
291
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
292
|
+
static Napi::FunctionReference constructor;
|
|
293
|
+
|
|
294
|
+
UnicornContext(const Napi::CallbackInfo& info);
|
|
295
|
+
~UnicornContext();
|
|
296
|
+
|
|
297
|
+
uc_context* GetContext() const { return context_; }
|
|
298
|
+
void SetContext(uc_context* ctx) { context_ = ctx; }
|
|
299
|
+
|
|
300
|
+
private:
|
|
301
|
+
uc_context* context_;
|
|
302
|
+
uc_engine* engine_; // Keep reference for proper cleanup
|
|
303
|
+
|
|
304
|
+
Napi::Value Free(const Napi::CallbackInfo& info);
|
|
305
|
+
Napi::Value GetSize(const Napi::CallbackInfo& info);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// ============== Hook Data Structures ==============
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Data passed to hook callbacks
|
|
312
|
+
* Uses ThreadSafeFunction for safe JS callback invocation
|
|
313
|
+
*/
|
|
314
|
+
struct HookData {
|
|
315
|
+
Napi::ThreadSafeFunction tsfn;
|
|
316
|
+
uc_hook handle;
|
|
317
|
+
int type;
|
|
318
|
+
UnicornWrapper* wrapper;
|
|
319
|
+
bool active;
|
|
320
|
+
|
|
321
|
+
HookData() : handle(0), type(0), wrapper(nullptr), active(true) {}
|
|
322
|
+
~HookData() {
|
|
323
|
+
if (tsfn) {
|
|
324
|
+
tsfn.Release();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Data structures for passing to JavaScript callbacks
|
|
330
|
+
struct CodeHookCallData {
|
|
331
|
+
uint64_t address;
|
|
332
|
+
uint32_t size;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
struct BlockHookCallData {
|
|
336
|
+
uint64_t address;
|
|
337
|
+
uint32_t size;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
struct MemHookCallData {
|
|
341
|
+
int type;
|
|
342
|
+
uint64_t address;
|
|
343
|
+
int size;
|
|
344
|
+
int64_t value;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
struct InterruptHookCallData {
|
|
348
|
+
uint32_t intno;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
struct InsnHookCallData {
|
|
352
|
+
uint64_t address;
|
|
353
|
+
uint32_t size;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
struct InvalidMemHookCallData {
|
|
357
|
+
int type;
|
|
358
|
+
uint64_t address;
|
|
359
|
+
int size;
|
|
360
|
+
int64_t value;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// ============== Hook Callback Functions ==============
|
|
364
|
+
|
|
365
|
+
void CodeHookCB(uc_engine* uc, uint64_t address, uint32_t size, void* user_data);
|
|
366
|
+
void BlockHookCB(uc_engine* uc, uint64_t address, uint32_t size, void* user_data);
|
|
367
|
+
void MemHookCB(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data);
|
|
368
|
+
void InterruptHookCB(uc_engine* uc, uint32_t intno, void* user_data);
|
|
369
|
+
void InsnHookCB(uc_engine* uc, void* user_data);
|
|
370
|
+
bool InvalidMemHookCB(uc_engine* uc, uc_mem_type type, uint64_t address, int size, int64_t value, void* user_data);
|
|
371
|
+
void BreakpointHookCB(uc_engine* uc, uint64_t address, uint32_t size, void* user_data);
|
|
372
|
+
|
|
373
|
+
// ============== Utility Functions ==============
|
|
374
|
+
|
|
375
|
+
Napi::Object CreateErrorObject(Napi::Env env, uc_err err);
|
|
376
|
+
const char* GetErrorMessage(uc_err err);
|
|
377
|
+
|
|
378
|
+
#endif // UNICORN_WRAPPER_H
|
|
379
|
+
|