Skip to content

Commit 56c5f74

Browse files
committed
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 a1bae73 commit 56c5f74

5 files changed

Lines changed: 365 additions & 209 deletions

File tree

libr/anal/fcn.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,9 @@ static int fcn_recurse(RAnal *anal, RAnalFunction *fcn, ut64 addr, ut64 len, int
853853
ra->cache_addr = UT64_MAX; // invalidate the cache
854854

855855
op = r_anal_op_new ();
856-
const ut32 opflags = R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_ESIL | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_HINT;
856+
// only request ESIL when needed (ARM uses it for pattern matching in analysis)
857+
const ut32 opflags = R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_VAL | R_ARCH_OP_MASK_HINT
858+
| (is_arm ? R_ARCH_OP_MASK_ESIL : 0);
857859
while (addrbytes * idx < maxlen) {
858860
if (!last_is_reg_mov_lea) {
859861
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
@@ -4692,22 +4692,42 @@ static inline bool is_valid_mnemonic(const char *m) {
46924692

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

libr/arch/p/x86/plugin_cs.c

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4140,12 +4140,8 @@ static void anop(RArchSession *a, RAnalOp *op, ut64 addr, const ut8 *buf, int le
41404140
}
41414141
}
41424142

4143-
static int cs_len_prefix_opcode(uint8_t *item) {
4144-
int i, len = 0;
4145-
for (i = 0; i < 4; i++) {
4146-
len += (item[i] != 0) ? 1 : 0;
4147-
}
4148-
return len;
4143+
static inline int cs_len_prefix_opcode(const uint8_t *item) {
4144+
return (item[0] != 0) + (item[1] != 0) + (item[2] != 0) + (item[3] != 0);
41494145
}
41504146

41514147
static bool plugin_changed(RArchSession *as) {
@@ -4191,7 +4187,7 @@ static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
41914187
#endif
41924188

41934189
op->cycles = 1; // aprox
4194-
cs_option (handle, CS_OPT_DETAIL, CS_OPT_ON);
4190+
// CS_OPT_DETAIL is already set during init, no need to set per-instruction
41954191
// capstone-next
41964192
#if USE_ITER_API
41974193
cs_detail insnack_detail = {{0}};
@@ -4222,22 +4218,37 @@ static bool decode(RArchSession *as, RAnalOp *op, RArchDecodeMask mask) {
42224218
op->size = 1;
42234219
} else {
42244220
if (mask & R_ARCH_OP_MASK_DISASM) {
4225-
op->mnemonic = r_str_newf ("%s%s%s",
4226-
insn->mnemonic,
4227-
insn->op_str[0]?" ":"",
4228-
insn->op_str);
4229-
if (op->mnemonic) {
4221+
// Build mnemonic string directly, avoiding r_str_newf + r_str_replace overhead.
4222+
// This is the hot path for disassembly output.
4223+
const char *mn = insn->mnemonic;
4224+
const char *ops = insn->op_str;
4225+
size_t mlen = strlen (mn);
4226+
size_t olen = ops[0] ? strlen (ops) : 0;
4227+
char *buf = malloc (mlen + 1 + olen + 1);
4228+
if (buf) {
4229+
memcpy (buf, mn, mlen);
4230+
if (olen) {
4231+
buf[mlen] = ' ';
4232+
memcpy (buf + mlen + 1, ops, olen + 1);
4233+
} else {
4234+
buf[mlen] = '\0';
4235+
}
4236+
// Remove "ptr " in-place (non-MASM syntax) instead of reallocating
42304237
if (as->config->syntax != R_ARCH_SYNTAX_MASM) {
4231-
op->mnemonic = r_str_replace (op->mnemonic, "ptr ", "", true);
4238+
char *p;
4239+
while ((p = strstr (buf, "ptr ")) != NULL) {
4240+
memmove (p, p + 4, strlen (p + 4) + 1);
4241+
}
42324242
}
42334243
if (as->config->syntax == R_ARCH_SYNTAX_JZ) {
4234-
if (r_str_startswith (op->mnemonic, "je ")) {
4235-
op->mnemonic[1] = 'z';
4236-
} else if (r_str_startswith (op->mnemonic, "jne ")) {
4237-
op->mnemonic[2] = 'z';
4244+
if (r_str_startswith (buf, "je ")) {
4245+
buf[1] = 'z';
4246+
} else if (r_str_startswith (buf, "jne ")) {
4247+
buf[2] = 'z';
42384248
}
42394249
}
42404250
}
4251+
op->mnemonic = buf;
42414252
}
42424253
op->nopcode = cs_len_prefix_opcode (insn->detail->x86.prefix)
42434254
+ 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

@@ -6683,7 +6689,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
66836689
// TODO: support in-the-middle-of-instruction too
66846690
r_anal_op_fini (&ds->analop);
66856691
if (r_anal_op (core->anal, &ds->analop, core->addr + core->print->cur,
6686-
buf + core->print->cur, (int)(len - core->print->cur), R_ARCH_OP_MASK_ALL)) {
6692+
buf + core->print->cur, (int)(len - core->print->cur), R_ARCH_OP_MASK_BASIC | R_ARCH_OP_MASK_HINT)) {
66876693
// TODO: check for ds->analop.type and ret
66886694
ds->dest = ds->analop.jump;
66896695
}
@@ -6798,7 +6804,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
67986804
r_asm_set_pc (core->rasm, ds->at);
67996805
ds_update_ref_lines (ds);
68006806
r_anal_op_fini (&ds->analop);
6801-
ret = r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), R_ARCH_OP_MASK_ALL);
6807+
ret = r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), ds->decode_mask);
68026808
if (ret < 1) {
68036809
ret = ds->analop.size;
68046810
inc = minopsz;
@@ -6914,7 +6920,7 @@ R_API int r_core_print_disasm(RCore *core, ut64 addr, ut8 *buf, int len, int cou
69146920
if (ds->analop.addr != ds->at) {
69156921
// TODO : check for error
69166922
r_anal_op_fini (&ds->analop);
6917-
r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), R_ARCH_OP_MASK_ALL);
6923+
r_anal_op (core->anal, &ds->analop, ds->at, ds_bufat (ds), ds_left (ds), ds->decode_mask);
69186924
}
69196925
if (ret < 1) {
69206926
r_strbuf_fini (&ds->analop.esil);
@@ -7369,7 +7375,7 @@ R_API int r_core_print_disasm_instructions_with_buf(RCore *core, ut64 address, u
73697375
const size_t delta = addrbytes * i;
73707376
r_anal_op_init (&ds->analop);
73717377
const int oret = r_anal_op (core->anal, &ds->analop, ds->at,
7372-
buf + delta, nb_bytes - delta, R_ARCH_OP_MASK_ALL);
7378+
buf + delta, nb_bytes - delta, ds->decode_mask);
73737379
ret = oret;
73747380
ds->oplen = ds->analop.size;
73757381
if (ret > 0) {
@@ -7719,7 +7725,7 @@ R_IPI int r_core_print_disasm_json_ipi(RCore *core, ut64 addr, ut8 *buf, int nb_
77197725

77207726
ds->has_description = false;
77217727
r_anal_op_fini (&ds->analop);
7722-
r_anal_op (core->anal, &ds->analop, at, buf + i, nb_bytes - i, R_ARCH_OP_MASK_ALL);
7728+
r_anal_op (core->anal, &ds->analop, at, buf + i, nb_bytes - i, ds->decode_mask);
77237729

77247730
if (ds->pseudo) {
77257731
char *res = r_asm_parse_pseudo (core->rasm, opstr);
@@ -8008,7 +8014,7 @@ R_API int r_core_print_disasm_all(RCore *core, ut64 addr, int l, int len, int mo
80088014
if (scr_color) {
80098015
RAnalOp aop;
80108016
RAnalFunction *f = fcnIn (ds, ds->vat, R_ANAL_FCN_TYPE_NULL);
8011-
r_anal_op (core->anal, &aop, addr, buf + i, l - i, R_ARCH_OP_MASK_ALL);
8017+
r_anal_op (core->anal, &aop, addr, buf + i, l - i, R_ARCH_OP_MASK_BASIC);
80128018
char *buf_asm = r_print_colorize_opcode (core->print, res? res: asmop.mnemonic,
80138019
core->cons->context->pal.reg, core->cons->context->pal.num, false, f ? f->addr : 0);
80148020
if (buf_asm) {

0 commit comments

Comments
 (0)