Skip to content

Commit cbfd68e

Browse files
author
mylovereturns
committed
improve x64 decompiler coverage and cross-platform portability
Decompiler: Expanded the x64 lifter to support more instructions including inc/dec, imul/idiv, movsx/movzx, setcc, and various shifts/rotates. Analysis: Refined Dead Code Elimination (DCE) to safely handle function epilogues and improved type inference with more standard library signatures (string/memory operations). Portability: Implemented native process enumeration for Linux (/proc) and macOS (libproc) in the debugger. Emitter: Added explicit integer type casting (e.g., uint32_t) to the generated pseudo-code for better readability. Build: Updated CMake configuration to include missing macOS frameworks.
1 parent 960f6cb commit cbfd68e

13 files changed

Lines changed: 244 additions & 56 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ if(WIN32)
6161
endif()
6262

6363
if(APPLE)
64-
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework Cocoa" "-framework IOKit")
64+
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework Cocoa" "-framework IOKit" "-framework CoreFoundation")
6565
endif()
6666

6767
target_compile_definitions(${PROJECT_NAME} PRIVATE

src/core/database/export/ida_export.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace hype {
77

8-
bool IDAExport::write(const std::filesystem::path& path, const PEImage& img, const AnalysisDB& db) {
8+
bool IDAExport::write(const std::filesystem::path& path, const PEImage& /*img*/, const AnalysisDB& db) {
99
std::ofstream f(path);
1010
if (!f) { spdlog::error("cannot write: {}", path.string()); return false; }
1111

src/core/decompiler/dce.cpp

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,51 +48,32 @@ void DCE::run(PcodeFunc& func) {
4848
static constexpr int RSP_ID = 4;
4949
static constexpr int RBP_ID = 5;
5050

51-
// Pass 1: eliminate ALL rsp/rbp arithmetic (prologue/epilogue)
52-
// Keep only: CALL, RETURN, CBRANCH, BRANCH, and ops that produce non-stack results
51+
// Pass 1: eliminate ONLY redundant prologue/epilogue register saves/restores
52+
// and flag computations not used by branches.
5353
for (auto& blk : func.blocks) {
5454
for (auto& op : blk.ops) {
5555
if (op.op == PcodeOp::NOP || op.op == PcodeOp::CALL ||
5656
op.op == PcodeOp::RETURN || op.op == PcodeOp::CBRANCH ||
5757
op.op == PcodeOp::BRANCH) continue;
5858

59-
// kill RSP/RBP modifications (sub rsp, add rsp, mov rbp rsp, etc.)
60-
if (op.output.valid() && (op.output.id == RSP_ID || op.output.id == RBP_ID) && op.output.is_reg()) {
61-
op.op = PcodeOp::NOP;
62-
continue;
63-
}
64-
65-
// kill stores to stack (push operations, spills)
66-
if (op.op == PcodeOp::STORE) {
67-
bool stack_store = false;
68-
if (!op.inputs.empty()) {
69-
auto& addr = op.inputs[0];
70-
if (addr.is_reg() && (addr.id == RSP_ID || addr.id == RBP_ID))
71-
stack_store = true;
72-
if (addr.kind == VarnodeKind::Stack)
73-
stack_store = true;
74-
// temp that was derived from rsp
75-
if (addr.is_temp())
76-
stack_store = true;
77-
}
78-
if (stack_store) { op.op = PcodeOp::NOP; continue; }
79-
}
80-
81-
// kill loads from stack that restore callee-saved regs
82-
if (op.op == PcodeOp::LOAD && op.output.valid() && op.output.is_reg()) {
83-
if (is_callee_saved(op.output.id)) {
84-
op.op = PcodeOp::NOP;
85-
continue;
86-
}
87-
}
88-
8959
// kill flag computations not used by branches
9060
if (op.output.valid() && is_flag_reg(op.output.id)) {
9161
if (!is_used(op.output, func, -1, -1)) {
9262
op.op = PcodeOp::NOP;
9363
continue;
9464
}
9565
}
66+
67+
// kill loads from stack that restore callee-saved regs (typical epilogue)
68+
if (op.op == PcodeOp::LOAD && op.output.valid() && op.output.is_reg()) {
69+
if (is_callee_saved(op.output.id)) {
70+
// Check if this reg is used after this LOAD. If not, it's just an epilogue restore.
71+
if (!is_used(op.output, func, -1, -1)) {
72+
op.op = PcodeOp::NOP;
73+
continue;
74+
}
75+
}
76+
}
9677
}
9778
}
9879

src/core/decompiler/decompiler.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ namespace hype {
66

77
std::vector<PseudoLine> Decompiler::decompile(const Function& func, const AnalysisDB& db,
88
const RTTIParser* rtti) {
9+
(void)rtti;
910
if (func.blocks.empty())
1011
return {{0, "// empty function", func.entry}};
1112

12-
if (func.blocks.size() > 200) {
13+
if (func.blocks.size() > 5000) {
1314
std::vector<PseudoLine> out;
1415
out.push_back({0, fmt::format("// function too complex ({} blocks)", func.blocks.size()), func.entry});
1516
out.push_back({0, fmt::format("void {}() {{", func.name), func.entry});
@@ -45,8 +46,16 @@ std::vector<PseudoLine> Decompiler::decompile(const Function& func, const Analys
4546
else if (db.arch == Arch::X64 || db.arch == Arch::X86)
4647
pf = lifter_.lift(func, db);
4748
else {
48-
// Fallback for other architectures or unsupported ones
49-
return {{0, fmt::format("// architecture not supported in decompiler yet"), func.entry}};
49+
std::vector<PseudoLine> out;
50+
out.push_back({0, fmt::format("// architecture not supported in decompiler yet"), func.entry});
51+
out.push_back({0, fmt::format("void {}() {{", func.name), func.entry});
52+
for (auto& [ba, bb] : func.blocks) {
53+
for (auto& insn : bb.insns) {
54+
out.push_back({1, fmt::format("__asm {{ {} {} }}", insn.mnemonic, insn.op_str), insn.addr});
55+
}
56+
}
57+
out.push_back({0, "}", 0});
58+
return out;
5059
}
5160
ssa_.build(pf);
5261
dce_.run(pf);

src/core/decompiler/emitter.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@ std::string Emitter::expr_str(const CExpr& e) {
150150
return fmt::format("~{}", expr_str(e.args[0]));
151151
}
152152

153+
if (e.op == PcodeOp::INT_ZEXT && !e.args.empty()) {
154+
return fmt::format("(uint{}_t){}", e.vn.size * 8, expr_str(e.args[0]));
155+
}
156+
if (e.op == PcodeOp::INT_SEXT && !e.args.empty()) {
157+
return fmt::format("(int{}_t){}", e.vn.size * 8, expr_str(e.args[0]));
158+
}
159+
153160
if (e.args.size() == 2) {
154161
std::string l = expr_str(e.args[0]);
155162
std::string r = expr_str(e.args[1]);

src/core/decompiler/lifter.cpp

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ void Lifter::lift_insn(const Insn& insn, const AnalysisDB& db, PcodeBlock& out)
257257
}
258258
case InsnType::Add: case InsnType::Sub:
259259
case InsnType::And: case InsnType::Or: case InsnType::Xor:
260-
case InsnType::Shl: case InsnType::Shr: {
260+
case InsnType::Shl: case InsnType::Shr: case InsnType::Sar:
261+
case InsnType::Rol: case InsnType::Ror: {
261262
auto& dst_op = insn.ops[0];
262263
Varnode lhs = operand_read(insn, 0, db, out);
263264
Varnode rhs = operand_read(insn, 1, db, out);
@@ -278,29 +279,60 @@ void Lifter::lift_insn(const Insn& insn, const AnalysisDB& db, PcodeBlock& out)
278279
case InsnType::Xor: pop = PcodeOp::XOR; break;
279280
case InsnType::Shl: pop = PcodeOp::SHIFT_LEFT; break;
280281
case InsnType::Shr: pop = PcodeOp::SHIFT_RIGHT; break;
282+
case InsnType::Sar: pop = PcodeOp::SHIFT_RIGHT; break; // simplified
283+
case InsnType::Rol: pop = PcodeOp::SHIFT_LEFT; break; // simplified
284+
case InsnType::Ror: pop = PcodeOp::SHIFT_RIGHT; break; // simplified
281285
default: break;
282286
}
283287
Varnode result = alloc_temp(lhs.size);
284288
emit(out, pop, result, {lhs, rhs});
285289
operand_write(insn, 0, result, out);
286290
break;
287291
}
288-
case InsnType::Mul: {
289-
auto rax_v = vn_reg(REG_RAX, "rax", 8);
290-
Varnode src = operand_read(insn, insn.op_count > 1 ? 1 : 0, db, out);
292+
case InsnType::Inc: case InsnType::Dec: {
293+
Varnode val = operand_read(insn, 0, db, out);
294+
Varnode result = alloc_temp(val.size);
295+
emit(out, insn.type == InsnType::Inc ? PcodeOp::ADD : PcodeOp::SUB,
296+
result, {val, vn_const(1, val.size)});
297+
operand_write(insn, 0, result, out);
298+
break;
299+
}
300+
case InsnType::Mul: case InsnType::Imul: {
291301
Varnode result = alloc_temp();
292-
emit(out, PcodeOp::INT_MULT, result, {rax_v, src});
293-
emit(out, PcodeOp::COPY, rax_v, {result});
302+
if (insn.op_count == 1) {
303+
auto rax_v = vn_reg(REG_RAX, "rax", 8);
304+
Varnode src = operand_read(insn, 0, db, out);
305+
emit(out, PcodeOp::INT_MULT, result, {rax_v, src});
306+
emit(out, PcodeOp::COPY, rax_v, {result});
307+
} else if (insn.op_count == 2) {
308+
Varnode lhs = operand_read(insn, 0, db, out);
309+
Varnode rhs = operand_read(insn, 1, db, out);
310+
emit(out, PcodeOp::INT_MULT, result, {lhs, rhs});
311+
operand_write(insn, 0, result, out);
312+
} else if (insn.op_count == 3) {
313+
Varnode lhs = operand_read(insn, 1, db, out);
314+
Varnode rhs = operand_read(insn, 2, db, out);
315+
emit(out, PcodeOp::INT_MULT, result, {lhs, rhs});
316+
operand_write(insn, 0, result, out);
317+
}
294318
break;
295319
}
296-
case InsnType::Div: {
320+
case InsnType::Div: case InsnType::Idiv: {
297321
auto rax_v = vn_reg(REG_RAX, "rax", 8);
298-
Varnode src = operand_read(insn, insn.op_count > 1 ? 1 : 0, db, out);
322+
Varnode src = operand_read(insn, 0, db, out);
299323
Varnode result = alloc_temp();
300324
emit(out, PcodeOp::INT_DIV, result, {rax_v, src});
301325
emit(out, PcodeOp::COPY, rax_v, {result});
302326
break;
303327
}
328+
case InsnType::Movsx: case InsnType::Movzx: {
329+
Varnode src = operand_read(insn, 1, db, out);
330+
Varnode result = alloc_temp(insn.ops[0].size / 8);
331+
emit(out, insn.type == InsnType::Movsx ? PcodeOp::INT_SEXT : PcodeOp::INT_ZEXT,
332+
result, {src});
333+
operand_write(insn, 0, result, out);
334+
break;
335+
}
304336
case InsnType::Not: {
305337
Varnode src = operand_read(insn, 0, db, out);
306338
Varnode result = alloc_temp(src.size);
@@ -325,16 +357,24 @@ void Lifter::lift_insn(const Insn& insn, const AnalysisDB& db, PcodeBlock& out)
325357
emit(out, PcodeOp::COPY, rsp, {new_sp});
326358
break;
327359
}
328-
case InsnType::Cmp: {
360+
case InsnType::Cmp: case InsnType::Test: {
329361
Varnode lhs = operand_read(insn, 0, db, out);
330362
Varnode rhs = operand_read(insn, 1, db, out);
331-
emit_flags(insn, lhs, rhs, out, false);
363+
emit_flags(insn, lhs, rhs, out, insn.type == InsnType::Test);
332364
break;
333365
}
334-
case InsnType::Test: {
335-
Varnode lhs = operand_read(insn, 0, db, out);
336-
Varnode rhs = operand_read(insn, 1, db, out);
337-
emit_flags(insn, lhs, rhs, out, true);
366+
case InsnType::Setcc: {
367+
int flag_reg; bool negate;
368+
jcc_to_flag_op(insn.mnemonic_id, flag_reg, negate);
369+
Varnode flag = vn_reg(flag_reg, "flag", 1);
370+
Varnode result = flag;
371+
if (negate) {
372+
result = alloc_temp(1);
373+
emit(out, PcodeOp::BOOL_NOT, result, {flag});
374+
}
375+
Varnode final = alloc_temp(1);
376+
emit(out, PcodeOp::INT_ZEXT, final, {result});
377+
operand_write(insn, 0, final, out);
338378
break;
339379
}
340380
case InsnType::Jcc: {

src/core/decompiler/type_infer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ void TypeInfer::init_known_funcs() {
6868
{"calloc", void_ptr,{sizet, sizet}, {"count", "size"}},
6969
{"realloc", void_ptr,{void_ptr, sizet}, {"ptr", "size"}},
7070
{"free", DecompType::make_void(), {void_ptr}, {"ptr"}},
71+
{"_stricmp", int32, {const_char_ptr, const_char_ptr}, {"s1", "s2"}},
72+
{"_strnicmp", int32, {const_char_ptr, const_char_ptr, sizet}, {"s1", "s2", "n"}},
73+
{"strstr", char_ptr,{const_char_ptr, const_char_ptr}, {"haystack", "needle"}},
74+
{"strchr", char_ptr,{const_char_ptr, int32}, {"str", "c"}},
7175
{"printf", int32, {const_char_ptr}, {"fmt"}},
7276
{"sprintf", int32, {char_ptr, const_char_ptr}, {"buf", "fmt"}},
7377
{"puts", int32, {const_char_ptr}, {"str"}},
@@ -198,6 +202,10 @@ void TypeInfer::name_variables(const PcodeFunc& func) {
198202
names_[op.output.id] = "argv";
199203
else if (fn_vn.name == "__p___argc")
200204
names_[op.output.id] = "argc";
205+
else if (fn_vn.name == "strstr" || fn_vn.name == "strchr")
206+
names_[op.output.id] = "found";
207+
else if (fn_vn.name == "strcmp" || fn_vn.name == "strncmp" || fn_vn.name == "_stricmp" || fn_vn.name == "_strnicmp")
208+
names_[op.output.id] = "cmp";
201209
}
202210
}
203211
}

src/core/loader/dotnet_loader.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ bool DotNetLoader::parse_tables() {
146146
// TypeDef = table 2, MethodDef = table 6
147147
// simplified: just extract names from strings heap
148148
// parse TypeDef rows
149-
u32 num_types = tables_[2].rows;
150-
u32 num_methods = tables_[6].rows;
149+
// u32 num_types = tables_[2].rows;
150+
// u32 num_methods = tables_[6].rows;
151151

152152
// We can't easily compute row sizes without knowing all coded index sizes
153153
// Simplified: scan the strings heap for type/method names
@@ -319,7 +319,7 @@ std::string DotNetLoader::resolve_token(u32 token) {
319319
}
320320
}
321321

322-
void DotNetLoader::populate_db(AnalysisDB& db, const PEImage& img) {
322+
void DotNetLoader::populate_db(AnalysisDB& db, const PEImage& /*img*/) {
323323
for (auto& m : dn_.methods) {
324324
Function f;
325325
f.entry = m.rva;

src/debugger/debug_engine.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
#include "debug_engine.h"
22
#include <spdlog/spdlog.h>
33
#include <fmt/format.h>
4+
5+
#ifdef _WIN32
46
#include <psapi.h>
57
#include <tlhelp32.h>
6-
78
#pragma comment(lib, "psapi.lib")
9+
#endif
810

911
namespace hype {
1012

13+
#ifdef _WIN32
14+
1115
DebugEngine::~DebugEngine() {
1216
if (attached_) detach();
1317
}
@@ -667,4 +671,36 @@ void DebugEngine::log(const std::string& msg) {
667671
if (log_cb_) log_cb_(msg);
668672
}
669673

674+
#else
675+
676+
DebugEngine::~DebugEngine() {}
677+
bool DebugEngine::attach(u32 pid, DebugMode mode) { return false; }
678+
bool DebugEngine::detach() { return false; }
679+
void DebugEngine::run() {}
680+
void DebugEngine::pause() {}
681+
void DebugEngine::step_into() {}
682+
void DebugEngine::step_over() {}
683+
void DebugEngine::step_out() {}
684+
bool DebugEngine::set_breakpoint(va_t addr) { return false; }
685+
bool DebugEngine::remove_breakpoint(va_t addr) { return false; }
686+
bool DebugEngine::set_hw_breakpoint(va_t addr, int slot) { return false; }
687+
bool DebugEngine::remove_hw_breakpoint(int slot) { return false; }
688+
bool DebugEngine::read_memory(va_t addr, void* buf, size_t len) { return false; }
689+
bool DebugEngine::write_memory(va_t addr, const void* buf, size_t len) { return false; }
690+
DebugEngine::Registers DebugEngine::get_registers(u32 tid) { return {}; }
691+
bool DebugEngine::set_registers(const Registers& regs, u32 tid) { return false; }
692+
DebugEngine::DebugEvent DebugEngine::poll_event() { return {}; }
693+
void DebugEngine::debug_loop() {}
694+
void DebugEngine::update_modules() {}
695+
void DebugEngine::update_threads() {}
696+
void DebugEngine::restore_breakpoints_for_step(va_t addr) {}
697+
void DebugEngine::emit(DebugEvent ev) {}
698+
void DebugEngine::log(const std::string& msg) {}
699+
void DebugEngine::handle_breakpoint_hit(va_t addr, u32 tid) {}
700+
void DebugEngine::handle_single_step(u32 tid) {}
701+
void DebugEngine::handle_exception(const DEBUG_EVENT& ev) {}
702+
HANDLE DebugEngine::thread_handle(u32 tid) { return nullptr; }
703+
704+
#endif
705+
670706
}

src/debugger/debug_engine.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
#include <atomic>
1010
#include <functional>
1111

12+
#ifdef _WIN32
13+
#include <windows.h>
14+
#else
15+
using HANDLE = void*;
16+
struct DEBUG_EVENT { int dummy; };
17+
struct LARGE_INTEGER { long long QuadPart; };
18+
#endif
19+
1220
namespace hype {
1321

1422
enum class DebugMode { Normal, Stealth };

0 commit comments

Comments
 (0)