hexcore-remill 0.1.1
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 -0
- package/README.md +101 -0
- package/binding.gyp +275 -0
- package/deps/README.md +20 -0
- package/index.d.ts +180 -0
- package/index.js +60 -0
- package/index.mjs +16 -0
- package/package.json +80 -0
- package/src/main.cpp +54 -0
- package/src/remill_wrapper.cpp +481 -0
- package/src/remill_wrapper.h +103 -0
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hexcore-remill",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "N-API bindings for Remill — lifts machine code to LLVM IR bitcode",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"module": "./index.mjs",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.mjs",
|
|
11
|
+
"require": "./index.js",
|
|
12
|
+
"types": "./index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"install": "prebuild-install -r napi || node-gyp rebuild",
|
|
17
|
+
"build": "node-gyp rebuild",
|
|
18
|
+
"build:debug": "node-gyp rebuild --debug",
|
|
19
|
+
"prebuild": "prebuildify --napi --strip",
|
|
20
|
+
"test": "node test/test.js",
|
|
21
|
+
"clean": "node-gyp clean"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"remill",
|
|
25
|
+
"lifter",
|
|
26
|
+
"llvm",
|
|
27
|
+
"llvm-ir",
|
|
28
|
+
"bitcode",
|
|
29
|
+
"binary-lifting",
|
|
30
|
+
"decompilation",
|
|
31
|
+
"reverse-engineering",
|
|
32
|
+
"binary-analysis",
|
|
33
|
+
"x86",
|
|
34
|
+
"x64",
|
|
35
|
+
"arm",
|
|
36
|
+
"arm64",
|
|
37
|
+
"aarch64",
|
|
38
|
+
"sparc",
|
|
39
|
+
"native",
|
|
40
|
+
"napi",
|
|
41
|
+
"async"
|
|
42
|
+
],
|
|
43
|
+
"author": "HikariSystem <hikarisystem@example.com>",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/LXrdKnowkill/hexcore-remill.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/LXrdKnowkill/hexcore-remill/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/LXrdKnowkill/hexcore-remill#readme",
|
|
53
|
+
"engines": {
|
|
54
|
+
"vscode": "^1.0.0",
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"activationEvents": [],
|
|
58
|
+
"files": [
|
|
59
|
+
"src/",
|
|
60
|
+
"deps/remill/include/",
|
|
61
|
+
"deps/remill/lib/",
|
|
62
|
+
"deps/llvm/include/",
|
|
63
|
+
"deps/llvm/lib/",
|
|
64
|
+
"prebuilds/",
|
|
65
|
+
"binding.gyp",
|
|
66
|
+
"index.js",
|
|
67
|
+
"index.mjs",
|
|
68
|
+
"index.d.ts",
|
|
69
|
+
"README.md"
|
|
70
|
+
],
|
|
71
|
+
"binary": {
|
|
72
|
+
"napi_versions": [8]
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"prebuildify": "^6.0.0",
|
|
76
|
+
"prebuild-install": "^7.1.0",
|
|
77
|
+
"node-addon-api": "^7.0.0",
|
|
78
|
+
"node-gyp": "^10.0.0"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/main.cpp
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HexCore Remill - N-API Entry Point
|
|
3
|
+
* Lifts machine code to LLVM IR bitcode
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) HikariSystem. All rights reserved.
|
|
6
|
+
* Licensed under MIT License.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include <napi.h>
|
|
10
|
+
#include "remill_wrapper.h"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Module constants — architecture name aliases for convenience.
|
|
14
|
+
*/
|
|
15
|
+
static Napi::Object CreateArchConstants(Napi::Env env) {
|
|
16
|
+
Napi::Object arch = Napi::Object::New(env);
|
|
17
|
+
arch.Set("X86", Napi::String::New(env, "x86"));
|
|
18
|
+
arch.Set("X86_AVX", Napi::String::New(env, "x86_avx"));
|
|
19
|
+
arch.Set("X86_AVX512", Napi::String::New(env, "x86_avx512"));
|
|
20
|
+
arch.Set("AMD64", Napi::String::New(env, "amd64"));
|
|
21
|
+
arch.Set("AMD64_AVX", Napi::String::New(env, "amd64_avx"));
|
|
22
|
+
arch.Set("AMD64_AVX512", Napi::String::New(env, "amd64_avx512"));
|
|
23
|
+
arch.Set("AARCH64", Napi::String::New(env, "aarch64"));
|
|
24
|
+
arch.Set("SPARC32", Napi::String::New(env, "sparc32"));
|
|
25
|
+
arch.Set("SPARC64", Napi::String::New(env, "sparc64"));
|
|
26
|
+
return arch;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Module constants — OS name aliases.
|
|
31
|
+
*/
|
|
32
|
+
static Napi::Object CreateOSConstants(Napi::Env env) {
|
|
33
|
+
Napi::Object os = Napi::Object::New(env);
|
|
34
|
+
os.Set("LINUX", Napi::String::New(env, "linux"));
|
|
35
|
+
os.Set("MACOS", Napi::String::New(env, "macos"));
|
|
36
|
+
os.Set("WINDOWS", Napi::String::New(env, "windows"));
|
|
37
|
+
os.Set("SOLARIS", Napi::String::New(env, "solaris"));
|
|
38
|
+
return os;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* N-API module initialization.
|
|
43
|
+
*/
|
|
44
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
45
|
+
RemillLifter::Init(env, exports);
|
|
46
|
+
|
|
47
|
+
exports.Set("ARCH", CreateArchConstants(env));
|
|
48
|
+
exports.Set("OS", CreateOSConstants(env));
|
|
49
|
+
exports.Set("version", Napi::String::New(env, "0.1.0"));
|
|
50
|
+
|
|
51
|
+
return exports;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
NODE_API_MODULE(hexcore_remill, Init)
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HexCore Remill - N-API Wrapper Implementation
|
|
3
|
+
* Lifts machine code to LLVM IR bitcode
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) HikariSystem. All rights reserved.
|
|
6
|
+
* Licensed under MIT License.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "remill_wrapper.h"
|
|
10
|
+
|
|
11
|
+
// MSVC doesn't define __x86_64__, so Remill's Name.h #error fires.
|
|
12
|
+
// Define REMILL_ARCH before including any Remill headers.
|
|
13
|
+
#if defined(_M_X64) && !defined(REMILL_ARCH)
|
|
14
|
+
#define REMILL_ARCH "amd64_avx"
|
|
15
|
+
#define REMILL_ON_AMD64 1
|
|
16
|
+
#define REMILL_ON_X86 0
|
|
17
|
+
#define REMILL_ON_AARCH64 0
|
|
18
|
+
#define REMILL_ON_AARCH32 0
|
|
19
|
+
#define REMILL_ON_SPARC64 0
|
|
20
|
+
#define REMILL_ON_SPARC32 0
|
|
21
|
+
#define REMILL_ON_PPC 0
|
|
22
|
+
#elif defined(_M_IX86) && !defined(REMILL_ARCH)
|
|
23
|
+
#define REMILL_ARCH "x86"
|
|
24
|
+
#define REMILL_ON_AMD64 0
|
|
25
|
+
#define REMILL_ON_X86 1
|
|
26
|
+
#define REMILL_ON_AARCH64 0
|
|
27
|
+
#define REMILL_ON_AARCH32 0
|
|
28
|
+
#define REMILL_ON_SPARC64 0
|
|
29
|
+
#define REMILL_ON_SPARC32 0
|
|
30
|
+
#define REMILL_ON_PPC 0
|
|
31
|
+
#endif
|
|
32
|
+
|
|
33
|
+
#include <remill/Arch/Arch.h>
|
|
34
|
+
#include <remill/Arch/Name.h>
|
|
35
|
+
#include <remill/BC/IntrinsicTable.h>
|
|
36
|
+
#include <remill/BC/Lifter.h>
|
|
37
|
+
#include <remill/BC/Util.h>
|
|
38
|
+
#include <remill/OS/OS.h>
|
|
39
|
+
|
|
40
|
+
#include <llvm/IR/LLVMContext.h>
|
|
41
|
+
#include <llvm/IR/Module.h>
|
|
42
|
+
#include <llvm/IR/Verifier.h>
|
|
43
|
+
#include <llvm/Support/raw_ostream.h>
|
|
44
|
+
#include <llvm/Transforms/Utils/Cloning.h>
|
|
45
|
+
|
|
46
|
+
#include <sstream>
|
|
47
|
+
#include <filesystem>
|
|
48
|
+
|
|
49
|
+
// Forward-declare Win32 functions to avoid #include <windows.h> which
|
|
50
|
+
// conflicts with Sleigh's CHAR token (ghidra::sleightokentype::CHAR
|
|
51
|
+
// vs winnt.h typedef char CHAR).
|
|
52
|
+
#ifdef _WIN32
|
|
53
|
+
extern "C" {
|
|
54
|
+
__declspec(dllimport) void* __stdcall GetModuleHandleA(const char*);
|
|
55
|
+
__declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void*, char*, unsigned long);
|
|
56
|
+
}
|
|
57
|
+
#endif
|
|
58
|
+
|
|
59
|
+
// Helper: resolve the semantics directory at runtime.
|
|
60
|
+
//
|
|
61
|
+
// Priority:
|
|
62
|
+
// 1. REMILL_SEMANTICS_DIR env var (explicit override)
|
|
63
|
+
// 2. Relative to the .node binary — works for both layouts:
|
|
64
|
+
// build/Release/hexcore_remill.node (dev build)
|
|
65
|
+
// prebuilds/win32-x64/hexcore_remill.node (packaged)
|
|
66
|
+
// Both are 2 directories below the extension root, so we need
|
|
67
|
+
// 3 parent_path() calls: file → containing dir → intermediate → root.
|
|
68
|
+
// 3. Relative to CWD as last resort (dev convenience)
|
|
69
|
+
// 4. Compile-time __FILE__ path (dev only)
|
|
70
|
+
//
|
|
71
|
+
// On Windows we use GetModuleHandleA("hexcore_remill.node") +
|
|
72
|
+
// GetModuleFileNameA to find the DLL path at runtime.
|
|
73
|
+
static std::filesystem::path GetSemanticsDir() {
|
|
74
|
+
const std::filesystem::path relSem =
|
|
75
|
+
std::filesystem::path("deps") / "remill" / "share" / "semantics";
|
|
76
|
+
|
|
77
|
+
// 1. Environment variable override
|
|
78
|
+
const char* envDir = std::getenv("REMILL_SEMANTICS_DIR");
|
|
79
|
+
if (envDir && envDir[0] != '\0') {
|
|
80
|
+
std::filesystem::path envPath(envDir);
|
|
81
|
+
if (std::filesystem::is_directory(envPath)) {
|
|
82
|
+
return envPath;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 2. Resolve from the .node binary location
|
|
87
|
+
#ifdef _WIN32
|
|
88
|
+
void* hMod = GetModuleHandleA("hexcore_remill.node");
|
|
89
|
+
if (hMod) {
|
|
90
|
+
char dllPath[260] = {0}; // MAX_PATH = 260
|
|
91
|
+
unsigned long len = GetModuleFileNameA(hMod, dllPath, 260);
|
|
92
|
+
if (len > 0 && len < 260) {
|
|
93
|
+
// dllPath example:
|
|
94
|
+
// .../extensions/hexcore-remill/build/Release/hexcore_remill.node
|
|
95
|
+
// .../extensions/hexcore-remill/prebuilds/win32-x64/hexcore_remill.node
|
|
96
|
+
//
|
|
97
|
+
// parent_path(1): removes filename → .../build/Release
|
|
98
|
+
// parent_path(2): removes Release → .../build
|
|
99
|
+
// parent_path(3): removes build → .../hexcore-remill (extension root)
|
|
100
|
+
auto extensionRoot = std::filesystem::path(dllPath)
|
|
101
|
+
.parent_path()
|
|
102
|
+
.parent_path()
|
|
103
|
+
.parent_path();
|
|
104
|
+
auto semDir = extensionRoot / relSem;
|
|
105
|
+
if (std::filesystem::is_directory(semDir)) {
|
|
106
|
+
return semDir;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
#endif
|
|
111
|
+
|
|
112
|
+
// 3. Fallback: relative to CWD (works when running from module root)
|
|
113
|
+
auto cwdSem = std::filesystem::current_path() / relSem;
|
|
114
|
+
if (std::filesystem::is_directory(cwdSem)) {
|
|
115
|
+
return cwdSem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 4. Last resort: compile-time path (dev only — won't work on other machines)
|
|
119
|
+
std::filesystem::path srcFile(__FILE__);
|
|
120
|
+
return srcFile.parent_path().parent_path() / relSem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// RemillLifter
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
Napi::Object RemillLifter::Init(Napi::Env env, Napi::Object exports) {
|
|
128
|
+
Napi::Function func = DefineClass(env, "RemillLifter", {
|
|
129
|
+
InstanceMethod("liftBytes", &RemillLifter::LiftBytes),
|
|
130
|
+
InstanceMethod("liftBytesAsync", &RemillLifter::LiftBytesAsync),
|
|
131
|
+
InstanceMethod("getArch", &RemillLifter::GetArch),
|
|
132
|
+
InstanceMethod("close", &RemillLifter::Close),
|
|
133
|
+
InstanceMethod("isOpen", &RemillLifter::IsOpen),
|
|
134
|
+
StaticMethod("getSupportedArchs", &RemillLifter::GetSupportedArchs),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
138
|
+
*constructor = Napi::Persistent(func);
|
|
139
|
+
env.SetInstanceData(constructor);
|
|
140
|
+
|
|
141
|
+
exports.Set("RemillLifter", func);
|
|
142
|
+
return exports;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
RemillLifter::RemillLifter(const Napi::CallbackInfo& info)
|
|
146
|
+
: Napi::ObjectWrap<RemillLifter>(info) {
|
|
147
|
+
|
|
148
|
+
Napi::Env env = info.Env();
|
|
149
|
+
|
|
150
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
151
|
+
Napi::TypeError::New(env,
|
|
152
|
+
"Expected architecture name string (e.g. 'amd64', 'x86', 'aarch64')")
|
|
153
|
+
.ThrowAsJavaScriptException();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
archName_ = info[0].As<Napi::String>().Utf8Value();
|
|
158
|
+
|
|
159
|
+
// Determine OS name — default to linux semantics for lifting
|
|
160
|
+
std::string osName = "linux";
|
|
161
|
+
if (info.Length() >= 2 && info[1].IsString()) {
|
|
162
|
+
osName = info[1].As<Napi::String>().Utf8Value();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create LLVM context
|
|
166
|
+
context_ = std::make_unique<llvm::LLVMContext>();
|
|
167
|
+
|
|
168
|
+
// Validate architecture name
|
|
169
|
+
auto archEnum = remill::GetArchName(archName_);
|
|
170
|
+
if (archEnum == remill::kArchInvalid) {
|
|
171
|
+
Napi::Error::New(env,
|
|
172
|
+
"Unsupported architecture: " + archName_ +
|
|
173
|
+
". Use RemillLifter.getSupportedArchs() for valid names.")
|
|
174
|
+
.ThrowAsJavaScriptException();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
auto osEnum = remill::GetOSName(osName);
|
|
179
|
+
|
|
180
|
+
// Arch::Get returns unique_ptr<const Arch>
|
|
181
|
+
arch_ = remill::Arch::Get(*context_, osEnum, archEnum);
|
|
182
|
+
if (!arch_) {
|
|
183
|
+
Napi::Error::New(env, "Failed to initialize Remill arch: " + archName_)
|
|
184
|
+
.ThrowAsJavaScriptException();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Load semantics module (contains instruction implementations as LLVM IR)
|
|
189
|
+
std::vector<std::filesystem::path> semDirs = { GetSemanticsDir() };
|
|
190
|
+
semanticsModule_ = remill::LoadArchSemantics(arch_.get(), semDirs);
|
|
191
|
+
if (!semanticsModule_) {
|
|
192
|
+
Napi::Error::New(env,
|
|
193
|
+
"Failed to load semantics module for arch: " + archName_)
|
|
194
|
+
.ThrowAsJavaScriptException();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create intrinsic table from the semantics module
|
|
199
|
+
intrinsics_ = std::make_unique<remill::IntrinsicTable>(semanticsModule_.get());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
RemillLifter::~RemillLifter() {
|
|
203
|
+
closed_ = true;
|
|
204
|
+
intrinsics_.reset();
|
|
205
|
+
semanticsModule_.reset();
|
|
206
|
+
arch_.reset();
|
|
207
|
+
context_.reset();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
Napi::Value RemillLifter::LiftBytes(const Napi::CallbackInfo& info) {
|
|
211
|
+
Napi::Env env = info.Env();
|
|
212
|
+
|
|
213
|
+
if (closed_) {
|
|
214
|
+
Napi::Error::New(env, "Lifter is closed").ThrowAsJavaScriptException();
|
|
215
|
+
return env.Undefined();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (info.Length() < 2) {
|
|
219
|
+
Napi::TypeError::New(env, "Expected (buffer, address)")
|
|
220
|
+
.ThrowAsJavaScriptException();
|
|
221
|
+
return env.Undefined();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Get the byte buffer
|
|
225
|
+
const uint8_t* bytes = nullptr;
|
|
226
|
+
size_t length = 0;
|
|
227
|
+
|
|
228
|
+
if (info[0].IsBuffer()) {
|
|
229
|
+
auto buf = info[0].As<Napi::Buffer<uint8_t>>();
|
|
230
|
+
bytes = buf.Data();
|
|
231
|
+
length = buf.Length();
|
|
232
|
+
} else if (info[0].IsTypedArray()) {
|
|
233
|
+
auto arr = info[0].As<Napi::Uint8Array>();
|
|
234
|
+
bytes = arr.Data();
|
|
235
|
+
length = arr.ByteLength();
|
|
236
|
+
} else {
|
|
237
|
+
Napi::TypeError::New(env, "First argument must be Buffer or Uint8Array")
|
|
238
|
+
.ThrowAsJavaScriptException();
|
|
239
|
+
return env.Undefined();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Get the base address
|
|
243
|
+
uint64_t address = 0;
|
|
244
|
+
if (info[1].IsNumber()) {
|
|
245
|
+
address = static_cast<uint64_t>(info[1].As<Napi::Number>().Int64Value());
|
|
246
|
+
} else if (info[1].IsBigInt()) {
|
|
247
|
+
bool lossless = false;
|
|
248
|
+
address = info[1].As<Napi::BigInt>().Uint64Value(&lossless);
|
|
249
|
+
} else {
|
|
250
|
+
Napi::TypeError::New(env, "Second argument must be number or BigInt (address)")
|
|
251
|
+
.ThrowAsJavaScriptException();
|
|
252
|
+
return env.Undefined();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
LiftResult result = DoLift(bytes, length, address);
|
|
256
|
+
return LiftResultToJS(env, result);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Napi::Value RemillLifter::LiftBytesAsync(const Napi::CallbackInfo& info) {
|
|
260
|
+
Napi::Env env = info.Env();
|
|
261
|
+
|
|
262
|
+
if (closed_) {
|
|
263
|
+
Napi::Error::New(env, "Lifter is closed").ThrowAsJavaScriptException();
|
|
264
|
+
return env.Undefined();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (info.Length() < 2) {
|
|
268
|
+
Napi::TypeError::New(env, "Expected (buffer, address)")
|
|
269
|
+
.ThrowAsJavaScriptException();
|
|
270
|
+
return env.Undefined();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Copy bytes into a vector for the worker thread
|
|
274
|
+
std::vector<uint8_t> bytesCopy;
|
|
275
|
+
if (info[0].IsBuffer()) {
|
|
276
|
+
auto buf = info[0].As<Napi::Buffer<uint8_t>>();
|
|
277
|
+
bytesCopy.assign(buf.Data(), buf.Data() + buf.Length());
|
|
278
|
+
} else if (info[0].IsTypedArray()) {
|
|
279
|
+
auto arr = info[0].As<Napi::Uint8Array>();
|
|
280
|
+
bytesCopy.assign(arr.Data(), arr.Data() + arr.ByteLength());
|
|
281
|
+
} else {
|
|
282
|
+
Napi::TypeError::New(env, "First argument must be Buffer or Uint8Array")
|
|
283
|
+
.ThrowAsJavaScriptException();
|
|
284
|
+
return env.Undefined();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
uint64_t address = 0;
|
|
288
|
+
if (info[1].IsNumber()) {
|
|
289
|
+
address = static_cast<uint64_t>(info[1].As<Napi::Number>().Int64Value());
|
|
290
|
+
} else if (info[1].IsBigInt()) {
|
|
291
|
+
bool lossless = false;
|
|
292
|
+
address = info[1].As<Napi::BigInt>().Uint64Value(&lossless);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
auto* worker = new LiftBytesWorker(env, this, std::move(bytesCopy), address);
|
|
296
|
+
auto promise = worker->GetDeferred().Promise();
|
|
297
|
+
worker->Queue();
|
|
298
|
+
return promise;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
Napi::Value RemillLifter::GetArch(const Napi::CallbackInfo& info) {
|
|
302
|
+
return Napi::String::New(info.Env(), archName_);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
Napi::Value RemillLifter::GetSupportedArchs(const Napi::CallbackInfo& info) {
|
|
306
|
+
Napi::Env env = info.Env();
|
|
307
|
+
Napi::Array result = Napi::Array::New(env);
|
|
308
|
+
|
|
309
|
+
const char* archs[] = {
|
|
310
|
+
"x86", "x86_avx", "x86_avx512",
|
|
311
|
+
"amd64", "amd64_avx", "amd64_avx512",
|
|
312
|
+
"aarch64",
|
|
313
|
+
"sparc32", "sparc64",
|
|
314
|
+
nullptr
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
uint32_t idx = 0;
|
|
318
|
+
for (const char** p = archs; *p; ++p) {
|
|
319
|
+
result.Set(idx++, Napi::String::New(env, *p));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Napi::Value RemillLifter::Close(const Napi::CallbackInfo& info) {
|
|
326
|
+
if (!closed_) {
|
|
327
|
+
closed_ = true;
|
|
328
|
+
intrinsics_.reset();
|
|
329
|
+
semanticsModule_.reset();
|
|
330
|
+
arch_.reset();
|
|
331
|
+
context_.reset();
|
|
332
|
+
}
|
|
333
|
+
return info.Env().Undefined();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
Napi::Value RemillLifter::IsOpen(const Napi::CallbackInfo& info) {
|
|
337
|
+
return Napi::Boolean::New(info.Env(), !closed_);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Internal: DoLift
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
LiftResult RemillLifter::DoLift(
|
|
345
|
+
const uint8_t* bytes, size_t length, uint64_t address) {
|
|
346
|
+
|
|
347
|
+
LiftResult result;
|
|
348
|
+
result.address = address;
|
|
349
|
+
result.bytesConsumed = 0;
|
|
350
|
+
result.success = false;
|
|
351
|
+
|
|
352
|
+
if (!arch_ || !semanticsModule_ || !intrinsics_) {
|
|
353
|
+
result.error = "Lifter not properly initialized";
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (length == 0) {
|
|
358
|
+
result.error = "Empty buffer";
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Clone the cached semantics module instead of reloading from disk.
|
|
363
|
+
// LoadArchSemantics reads ~50MB of .bc per call; CloneModule copies the
|
|
364
|
+
// in-memory IR in microseconds with zero disk I/O.
|
|
365
|
+
auto liftModule = llvm::CloneModule(*semanticsModule_);
|
|
366
|
+
if (!liftModule) {
|
|
367
|
+
result.error = "Failed to clone semantics module";
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
auto intrinsics = std::make_unique<remill::IntrinsicTable>(liftModule.get());
|
|
372
|
+
|
|
373
|
+
// DefaultLifter returns shared_ptr<OperandLifter>
|
|
374
|
+
auto lifter = arch_->DefaultLifter(*intrinsics);
|
|
375
|
+
|
|
376
|
+
// DefaultLifter always returns an InstructionLifterIntf-derived object.
|
|
377
|
+
// Use static_pointer_cast (dynamic_pointer_cast requires RTTI which is
|
|
378
|
+
// disabled by NAPI_DISABLE_CPP_EXCEPTIONS).
|
|
379
|
+
auto instLifter = std::static_pointer_cast<remill::InstructionLifterIntf>(lifter);
|
|
380
|
+
|
|
381
|
+
// Decode and lift each instruction
|
|
382
|
+
remill::Instruction inst;
|
|
383
|
+
uint64_t pc = address;
|
|
384
|
+
size_t offset = 0;
|
|
385
|
+
|
|
386
|
+
// Create a lifted function to hold the instructions
|
|
387
|
+
auto func = arch_->DeclareLiftedFunction(
|
|
388
|
+
"lifted_" + std::to_string(address), liftModule.get());
|
|
389
|
+
arch_->InitializeEmptyLiftedFunction(func);
|
|
390
|
+
|
|
391
|
+
auto block = &func->getEntryBlock();
|
|
392
|
+
|
|
393
|
+
// Create initial decoding context for this architecture
|
|
394
|
+
auto context = arch_->CreateInitialContext();
|
|
395
|
+
|
|
396
|
+
while (offset < length) {
|
|
397
|
+
std::string_view instrBytes(
|
|
398
|
+
reinterpret_cast<const char*>(bytes + offset), length - offset);
|
|
399
|
+
|
|
400
|
+
// DecodeInstruction requires DecodingContext as 4th parameter
|
|
401
|
+
if (!arch_->DecodeInstruction(pc, instrBytes, inst, context)) {
|
|
402
|
+
if (offset == 0) {
|
|
403
|
+
result.error = "Failed to decode instruction at 0x" +
|
|
404
|
+
std::to_string(address);
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
break; // Stop at first undecoded instruction
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// LiftIntoBlock with 2-arg overload (inst, block)
|
|
411
|
+
auto status = instLifter->LiftIntoBlock(inst, block, false);
|
|
412
|
+
if (status != remill::kLiftedInstruction) {
|
|
413
|
+
if (offset == 0) {
|
|
414
|
+
result.error = "Failed to lift instruction at 0x" +
|
|
415
|
+
std::to_string(pc);
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
offset += inst.bytes.size();
|
|
422
|
+
pc += inst.bytes.size();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
result.bytesConsumed = offset;
|
|
426
|
+
|
|
427
|
+
// Print the function IR to string
|
|
428
|
+
std::string irStr;
|
|
429
|
+
llvm::raw_string_ostream os(irStr);
|
|
430
|
+
func->print(os);
|
|
431
|
+
os.flush();
|
|
432
|
+
|
|
433
|
+
result.ir = irStr;
|
|
434
|
+
result.success = true;
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
Napi::Object RemillLifter::LiftResultToJS(
|
|
439
|
+
Napi::Env env, const LiftResult& result) {
|
|
440
|
+
|
|
441
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
442
|
+
obj.Set("success", Napi::Boolean::New(env, result.success));
|
|
443
|
+
obj.Set("ir", Napi::String::New(env, result.ir));
|
|
444
|
+
obj.Set("error", Napi::String::New(env, result.error));
|
|
445
|
+
obj.Set("address", Napi::Number::New(env,
|
|
446
|
+
static_cast<double>(result.address)));
|
|
447
|
+
obj.Set("bytesConsumed", Napi::Number::New(env,
|
|
448
|
+
static_cast<double>(result.bytesConsumed)));
|
|
449
|
+
return obj;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
// LiftBytesWorker
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
LiftBytesWorker::LiftBytesWorker(
|
|
457
|
+
Napi::Env env,
|
|
458
|
+
RemillLifter* lifter,
|
|
459
|
+
std::vector<uint8_t> bytes,
|
|
460
|
+
uint64_t address)
|
|
461
|
+
: Napi::AsyncWorker(env),
|
|
462
|
+
lifter_(lifter),
|
|
463
|
+
bytes_(std::move(bytes)),
|
|
464
|
+
address_(address),
|
|
465
|
+
deferred_(Napi::Promise::Deferred::New(env)) {}
|
|
466
|
+
|
|
467
|
+
void LiftBytesWorker::Execute() {
|
|
468
|
+
result_ = lifter_->DoLift(bytes_.data(), bytes_.size(), address_);
|
|
469
|
+
if (!result_.success) {
|
|
470
|
+
SetError(result_.error);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
void LiftBytesWorker::OnOK() {
|
|
475
|
+
Napi::Env env = Env();
|
|
476
|
+
deferred_.Resolve(lifter_->LiftResultToJS(env, result_));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
void LiftBytesWorker::OnError(const Napi::Error& error) {
|
|
480
|
+
deferred_.Reject(error.Value());
|
|
481
|
+
}
|