Skip to content

Commit 87280ad

Browse files
clauderadare
authored andcommitted
Optimize Capstone disassembly performance across the stack
Thread-safe per-handle O(1) lookup tables in Capstone X86: - Move lookup tables from static globals to cs_struct fields - Build tables in X86_global_init(), free in cs_close() - Add find_insn_h() and X86_insn_reg_{intel,att}_h() per-handle variants - Keep binary search fallback for decoder paths without handle access - Replace vsnprintf number formatting with fast custom formatters in SStream - Use memset for MCInst tied_op_idx initialization ARM plugin (plugin_cs.c): - Switch from allocating cs_disasm() to stack-based cs_disasm_iter() - Replace r_str_newf mnemonic construction with direct malloc+memcpy x86 plugin (plugin_cs.c): - Replace r_str_newf + r_str_replace (2 allocs) with single malloc + in-place memmove - Remove redundant per-instruction cs_option(CS_OPT_DETAIL) call - Inline cs_len_prefix_opcode() for branch penalty elimination Core disassembly (disasm.c): - Compute decode_mask once based on display settings (asm.emu, asm.cmt.esil) - Skip ESIL/OPEX generation when not needed for display - Use R_ARCH_OP_MASK_BASIC for color-only decode paths Analysis (fcn.c): - Remove R_ARCH_OP_MASK_ESIL from default analysis loop for non-ARM archs - ESIL generation only when architecture needs it for pattern matching https://claude.ai/code/session_01KDR9eBZ4vEAftFBQ2vuhmr
1 parent bd55139 commit 87280ad

5 files changed

Lines changed: 365 additions & 208 deletions

File tree

libr/anal/fcn.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,9 @@ static int fcn_recurse(RAnal *anal, RAnalFunction *fcn, ut64 addr, ut64 len, int
854854
RAnalOp op_storage;
855855
r_anal_op_init (&op_storage);
856856
op = &op_storage;
857-
const ut32 opflags = R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_ESIL | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_HINT;
857+
// only request ESIL when needed (ARM uses it for pattern matching in analysis)
858+
const ut32 opflags = R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_HINT
859+
| (is_arm ? R_ARCH_OP_MASK_ESIL : 0);
858860
while (addrbytes * idx < maxlen) {
859861
if (!last_is_reg_mov_lea) {
860862
last_reg_mov_lea_name = NULL;

libr/arch/p/arm/plugin_cs.c

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4693,22 +4693,42 @@ static inline bool is_valid_mnemonic(const char *m) {
46934693

46944694
static int analop(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf, int len, RAnalOpMask mask) {
46954695
csh *cs_handle = cs_handle_for_session (as);
4696-
cs_insn *insn = NULL;
46974696
op->size = (as->config->bits == 16)? 2: 4;
46984697
op->addr = addr;
46994698
if (!buf) {
47004699
buf = op->bytes;
47014700
len = op->size;
47024701
}
4703-
int n = cs_disasm (*cs_handle, (ut8*)buf, len, addr, 1, &insn);
4704-
if (n > 0 && is_valid_mnemonic (insn->mnemonic)) {
4702+
// Use cs_disasm_iter (iterator API) instead of cs_disasm to avoid
4703+
// per-instruction malloc/free overhead. Stack-allocate the insn struct.
4704+
cs_detail detail_buf = {{0}};
4705+
cs_insn insn_buf = {0};
4706+
insn_buf.detail = &detail_buf;
4707+
cs_insn *insn = &insn_buf;
4708+
const uint8_t *code_ptr = (const uint8_t *)buf;
4709+
size_t code_remaining = len;
4710+
uint64_t iter_addr = addr;
4711+
bool ok = cs_disasm_iter (*cs_handle, &code_ptr, &code_remaining, &iter_addr, insn);
4712+
if (ok && is_valid_mnemonic (insn->mnemonic)) {
47054713
if (mask & R_ARCH_OP_MASK_DISASM) {
47064714
free (op->mnemonic);
4707-
op->mnemonic = r_str_newf ("%s%s%s",
4708-
insn->mnemonic,
4709-
insn->op_str[0]? " ": "",
4710-
insn->op_str);
4711-
r_str_replace_char (op->mnemonic, '#', '\x00');
4715+
if (insn->op_str[0]) {
4716+
// Build mnemonic string directly, avoiding r_str_newf overhead
4717+
size_t mlen = strlen (insn->mnemonic);
4718+
size_t olen = strlen (insn->op_str);
4719+
char *mn = malloc (mlen + 1 + olen + 1);
4720+
if (mn) {
4721+
memcpy (mn, insn->mnemonic, mlen);
4722+
mn[mlen] = ' ';
4723+
memcpy (mn + mlen + 1, insn->op_str, olen + 1);
4724+
r_str_replace_char (mn, '#', '\x00');
4725+
op->mnemonic = mn;
4726+
} else {
4727+
op->mnemonic = NULL;
4728+
}
4729+
} else {
4730+
op->mnemonic = strdup (insn->mnemonic);
4731+
}
47124732
}
47134733
//bool thumb = cs_insn_group (cs_handle, insn, ARM_GRP_THUMB);
47144734
bool thumb = as->config->bits == 16;
@@ -4735,9 +4755,7 @@ static int analop(RArchSession *as, RAnalOp *op, ut64 addr, const ut8 *buf, int
47354755
if (mask & R_ARCH_OP_MASK_VAL) {
47364756
op_fillval (as, op, *cs_handle, insn, as->config->bits);
47374757
}
4738-
cs_free (insn, n);
47394758
} else {
4740-
cs_free (insn, n);
47414759
op->size = 4;
47424760
op->type = R_ANAL_OP_TYPE_ILL;
47434761
if (!buf || len < 4) {

libr/arch/p/x86/plugin_cs.c

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4148,12 +4148,8 @@ static void anop(RArchSession *a, RAnalOp *op, ut64 addr, const ut8 *buf, int le
41484148
}
41494149
}
41504150

4151-
static int cs_len_prefix_opcode(uint8_t *item) {
4152-
int i, len = 0;
4153-
for (i = 0; i < 4; i++) {
4154-
len += (item[i] != 0) ? 1 : 0;
4155-
}
4156-
return len;
4151+
static inline int cs_len_prefix_opcode(const uint8_t *item) {
4152+
return (item[0] != 0) + (item[1] != 0) + (item[2] != 0) + (item[3] != 0);
41574153
}
41584154

41594155
static bool plugin_changed(RArchSession *as) {
@@ -4194,6 +4190,7 @@ static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
41944190
#endif
41954191

41964192
op->cycles = 1; // aprox
4193+
// CS_OPT_DETAIL is already set during init, no need to set per-instruction
41974194
// capstone-next
41984195
#if USE_ITER_API
41994196
cs_detail insnack_detail = {{0}};
@@ -4224,22 +4221,37 @@ static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
42244221
op->size = 1;
42254222
} else {
42264223
if (mask & R_ARCH_OP_MASK_DISASM) {
4227-
op->mnemonic = r_str_newf ("%s%s%s",
4228-
insn->mnemonic,
4229-
insn->op_str[0]?" ":"",
4230-
insn->op_str);
4231-
if (op->mnemonic) {
4224+
// Build mnemonic string directly, avoiding r_str_newf + r_str_replace overhead.
4225+
// This is the hot path for disassembly output.
4226+
const char *mn = insn->mnemonic;
4227+
const char *ops = insn->op_str;
4228+
size_t mlen = strlen (mn);
4229+
size_t olen = ops[0] ? strlen (ops) : 0;
4230+
char *buf = malloc (mlen + 1 + olen + 1);
4231+
if (buf) {
4232+
memcpy (buf, mn, mlen);
4233+
if (olen) {
4234+
buf[mlen] = ' ';
4235+
memcpy (buf + mlen + 1, ops, olen + 1);
4236+
} else {
4237+
buf[mlen] = '\0';
4238+
}
4239+
// Remove "ptr " in-place (non-MASM syntax) instead of reallocating
42324240
if (as->config->syntax != R_ARCH_SYNTAX_MASM) {
4233-
op->mnemonic = r_str_replace (op->mnemonic, "ptr ", "", true);
4241+
char *p;
4242+
while ((p = strstr (buf, "ptr ")) != NULL) {
4243+
memmove (p, p + 4, strlen (p + 4) + 1);
4244+
}
42344245
}
42354246
if (as->config->syntax == R_ARCH_SYNTAX_JZ) {
4236-
if (r_str_startswith (op->mnemonic, "je ")) {
4237-
op->mnemonic[1] = 'z';
4238-
} else if (r_str_startswith (op->mnemonic, "jne ")) {
4239-
op->mnemonic[2] = 'z';
4247+
if (r_str_startswith (buf, "je ")) {
4248+
buf[1] = 'z';
4249+
} else if (r_str_startswith (buf, "jne ")) {
4250+
buf[2] = 'z';
42404251
}
42414252
}
42424253
}
4254+
op->mnemonic = buf;
42434255
}
42444256
op->nopcode = cs_len_prefix_opcode (insn->detail->x86.prefix)
42454257
+ cs_len_prefix_opcode (insn->detail->x86.opcode);

libr/core/disasm.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ typedef struct r_disasm_state_t {
143143
bool show_emu_write;
144144
bool show_optype;
145145
bool show_emu_ssa;
146+
ut32 decode_mask; // computed once based on display settings
146147
bool show_section;
147148
int show_section_col;
148149
bool show_section_perm;
@@ -976,6 +977,11 @@ static RDisasmState *ds_init(RCore *core) {
976977
// Prevent palette reload during disassembly to avoid UAF of cached color pointers
977978
ds->pal_batch_save = core->cons->context->pal_batch;
978979
core->cons->context->pal_batch = true;
980+
// compute decode mask once based on display settings to avoid unnecessary work
981+
ds->decode_mask = R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_HINT | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_DISASM;
982+
if (ds->show_emu || ds->show_cmt_esil || ds->show_emu_bb) {
983+
ds->decode_mask |= R_ARCH_OP_MASK_ESIL;
984+
}
979985
return ds;
980986
}
981987

@@ -6694,7 +6700,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
66946700
// TODO: support in-the-middle-of-instruction too
66956701
r_anal_op_fini (&ds->analop);
66966702
if (r_anal_op (core->anal, &ds->analop, core->addr + core->print->cur,
6697-
buf + core->print->cur, (int)(len - core->print->cur), R_ARCH_OP_MASK_ALL)) {
6703+
buf + core->print->cur, (int)(len - core->print->cur), R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_HINT)) {
66986704
// TODO: check for ds->analop.type and ret
66996705
ds->dest = ds->analop.jump;
67006706
}
@@ -6809,7 +6815,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
68096815
r_asm_set_pc (core->rasm, ds->at);
68106816
ds_update_ref_lines (ds);
68116817
r_anal_op_fini (&ds->analop);
6812-
ret = r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), R_ARCH_OP_MASK_ALL);
6818+
ret = r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), ds->decode_mask);
68136819
if (ret < 1) {
68146820
ret = ds->analop.size;
68156821
inc = minopsz;
@@ -6926,7 +6932,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
69266932
if (ds->analop.addr != ds->at) {
69276933
// TODO : check for error
69286934
r_anal_op_fini (&ds->analop);
6929-
r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), R_ARCH_OP_MASK_ALL);
6935+
r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), ds->decode_mask);
69306936
}
69316937
if (ret < 1) {
69326938
r_strbuf_fini (&ds->analop.esil);
@@ -7381,7 +7387,7 @@ R_API int r_core_print_disasm_instructions_with_buf(RCore *core, ut64 address, u
73817387
const size_t delta = addrbytes * i;
73827388
r_anal_op_init (&ds->analop);
73837389
const int oret = r_anal_op (core->anal, &ds->analop, ds->at,
7384-
buf + delta, nb_bytes - delta, R_ARCH_OP_MASK_ALL);
7390+
buf + delta, nb_bytes - delta, ds->decode_mask);
73857391
ret = oret;
73867392
ds->oplen = ds->analop.size;
73877393
if (ret > 0) {
@@ -7731,7 +7737,7 @@ R_IPI int r_core_print_disasm_json_ipi(RCore *core, ut64 addr, ut8 *buf, int nb_
77317737

77327738
ds->has_description = false;
77337739
r_anal_op_fini (&ds->analop);
7734-
r_anal_op (core->anal, &ds->analop, at, buf + i, nb_bytes - i, R_ARCH_OP_MASK_ALL);
7740+
r_anal_op (core->anal, &ds->analop, at, buf + i, nb_bytes - i, ds->decode_mask);
77357741

77367742
if (ds->pseudo) {
77377743
char *res = r_asm_parse_pseudo (core->rasm, opstr);
@@ -8020,7 +8026,7 @@ R_API int r_core_print_disasm_all(RCore *core, ut64 addr, int l, int len, int mo
80208026
if (scr_color) {
80218027
RAnalOp aop;
80228028
RAnalFunction *f = fcnIn (ds, ds->vat, R_ANAL_FCN_TYPE_NULL);
8023-
r_anal_op (core->anal, &aop, addr, buf + i, l - i, R_ARCH_OP_MASK_ALL);
8029+
r_anal_op (core->anal, &aop, addr, buf + i, l - i, R_ARCH_OP_MASK_BASIC);
80248030
char *buf_asm = r_print_colorize_opcode (core->print, res? res: asmop.mnemonic,
80258031
core->cons->context->pal.reg, core->cons->context->pal.num, false, f ? f->addr : 0);
80268032
if (buf_asm) {

0 commit comments

Comments
 (0)