Skip to content

Commit 58cb327

Browse files
authored
i#2297: AARCH64: Implement mbr & cbr instrumentation (#7005)
Implement mbr & cbr instrumentation for AARCH64, previously only available for X86. For cbr instrumentation, there are three types of conditional branches on AARCH64: 1. conditional branch if (non) zero: generate code to compare the register against zero to compute the direction 2. test bit and branch if (non) zero: generate code to extract the bit out of the register (using ubfm) and then compare the bit against zero to compute the direction 3. conditional branch according to NZCV: generate code to compute the direction using csinc instruction Special care is implemented if the stolen register x28 is used as the operand, to avoid generating incorrect results. For mbr instrumentation, the target address is placed in the register, which is later passed to the clean call from the user. Additional instruction creation macros are added for ubfm and csinc instructions. Support for immediate operand is added to the INSTR_CREATE_eor macro. XINST_CREATE_slr_s is updated to use the new INSTR_CREATE_ubfm macro. Count-ctis test is now enabled due to cbr & mbr instrumentation available on AARCH64. It has been enhanced to validate the direction and target address of conditional branches to validate the instrumentation. The instrcalls and cbrtrace example clients are enabled as well. They are tested on real AARCH64 hardware. Additionally, a manually crafted dynamorio client can successfully capture the whole control flow transfer history with this pull reuqest. Fixes: #2297 (only for AARCH64, ARM not yet) and #2919.
1 parent 23b1971 commit 58cb327

File tree

6 files changed

+418
-15
lines changed

6 files changed

+418
-15
lines changed

api/samples/CMakeLists.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,14 @@ if (X86) # FIXME i#1551, i#1569, i#3544: port to ARM/AArch64/RISCV64
264264
# dr_insert_mbr_instrumentation is NYI
265265
add_sample_client(modxfer "modxfer.c;utils.c" "drmgr;drreg;drx")
266266
add_sample_client(modxfer_app2lib "modxfer_app2lib.c" "drmgr")
267+
add_sample_client(hot_bbcount "hot_bbcount.c" "drmgr;drreg;drbbdup;drx")
268+
endif (X86)
269+
if (X86 OR AARCH64)
270+
# dr_insert_mbr_instrumentation is NYI
267271
add_sample_client(instrcalls "instrcalls.c;utils.c" "drmgr;drsyms;drx")
268272
# dr_insert_cbr_instrument_ex is NYI
269273
add_sample_client(cbrtrace "cbrtrace.c;utils.c" "drmgr;drx")
270-
add_sample_client(hot_bbcount "hot_bbcount.c" "drmgr;drreg;drbbdup;drx")
271-
endif (X86)
274+
endif (X86 OR AARCH64)
272275
add_sample_client(wrap "wrap.c" "drmgr;drwrap")
273276
add_sample_client(signal "signal.c" "drmgr")
274277
add_sample_client(syscall "syscall.c" "drmgr")

core/ir/aarch64/instr_create_api.h

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,12 @@
463463
* they just need to know whether they need to preserve the app's flags, so maybe
464464
* we can just document that this may not write them.
465465
*/
466-
#define XINST_CREATE_slr_s(dc, d, rm_or_imm) \
467-
(opnd_is_reg(rm_or_imm) \
468-
? instr_create_1dst_2src(dc, OP_lsrv, d, d, rm_or_imm) \
469-
: instr_create_1dst_3src(dc, OP_ubfm, d, d, rm_or_imm, \
470-
reg_is_32bit(opnd_get_reg(d)) ? OPND_CREATE_INT(31) \
471-
: OPND_CREATE_INT(63)))
466+
#define XINST_CREATE_slr_s(dc, d, rm_or_imm) \
467+
(opnd_is_reg(rm_or_imm) \
468+
? instr_create_1dst_2src(dc, OP_lsrv, d, d, rm_or_imm) \
469+
: INSTR_CREATE_ubfm(dc, d, d, rm_or_imm, \
470+
reg_is_32bit(opnd_get_reg(d)) ? OPND_CREATE_INT(31) \
471+
: OPND_CREATE_INT(63)))
472472

473473
/**
474474
* This platform-independent macro creates an instr_t for a nop instruction.
@@ -658,14 +658,49 @@
658658
instr_create_0dst_3src((dc), OP_tbnz, (pc), (reg), (imm))
659659
#define INSTR_CREATE_cmp(dc, rn, rm_or_imm) \
660660
INSTR_CREATE_subs(dc, OPND_CREATE_ZR(rn), rn, rm_or_imm)
661-
#define INSTR_CREATE_eor(dc, d, s) \
662-
INSTR_CREATE_eor_shift(dc, d, d, s, OPND_CREATE_INT8(DR_SHIFT_LSL), \
663-
OPND_CREATE_INT8(0))
661+
662+
/**
663+
* Creates an EOR instruction with one output and two inputs. For simplicity, the first
664+
* input reuses the output register.
665+
*
666+
* \param dc The void * dcontext used to allocate memory for the instr_t.
667+
* \param d The output register and the first input register.
668+
* \param s_or_imm The second input register or immediate.
669+
*/
670+
#define INSTR_CREATE_eor(dc, d, s_or_imm) \
671+
opnd_is_immed(s_or_imm) \
672+
? instr_create_1dst_2src(dc, OP_eor, d, d, s_or_imm) \
673+
: INSTR_CREATE_eor_shift(dc, d, d, s_or_imm, OPND_CREATE_INT8(DR_SHIFT_LSL), \
674+
OPND_CREATE_INT8(0))
664675
#define INSTR_CREATE_eor_shift(dc, rd, rn, rm, sht, sha) \
665676
instr_create_1dst_4src(dc, OP_eor, rd, rn, \
666677
opnd_create_reg_ex(opnd_get_reg(rm), 0, DR_OPND_SHIFTED), \
667678
opnd_add_flags(sht, DR_OPND_IS_SHIFT), sha)
668679

680+
/**
681+
* Creates a CSINC instruction with one output and three inputs.
682+
*
683+
* \param dc The void * dcontext used to allocate memory for the instr_t.
684+
* \param rd The output register.
685+
* \param rn The first input register.
686+
* \param rm The second input register.
687+
* \param cond The third input condition code.
688+
*/
689+
#define INSTR_CREATE_csinc(dc, rd, rn, rm, cond) \
690+
instr_create_1dst_3src(dc, OP_csinc, rd, rn, rm, cond)
691+
692+
/**
693+
* Creates a UBFM instruction with one output and three inputs.
694+
*
695+
* \param dc The void * dcontext used to allocate memory for the instr_t.
696+
* \param rd The output register.
697+
* \param rn The first input register.
698+
* \param immr The second input immediate.
699+
* \param imms The third input immediate.
700+
*/
701+
#define INSTR_CREATE_ubfm(dc, rd, rn, immr, imms) \
702+
instr_create_1dst_3src(dc, OP_ubfm, rd, rn, immr, imms)
703+
669704
#define INSTR_CREATE_ldp(dc, rt1, rt2, mem) \
670705
instr_create_2dst_1src(dc, OP_ldp, rt1, rt2, mem)
671706
#define INSTR_CREATE_ldr(dc, Rd, mem) instr_create_1dst_1src((dc), OP_ldr, (Rd), (mem))

core/lib/instrument.c

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6129,6 +6129,31 @@ dr_insert_mbr_instrumentation(void *drcontext, instrlist_t *ilist, instr_t *inst
61296129
#elif defined(RISCV64)
61306130
/* FIXME i#3544: Not implemented */
61316131
ASSERT_NOT_IMPLEMENTED(false);
6132+
#elif defined(AARCH64)
6133+
ptr_uint_t address;
6134+
opnd_t target;
6135+
CLIENT_ASSERT(drcontext != NULL,
6136+
"dr_insert_mbr_instrumentation: drcontext cannot be NULL");
6137+
address = (ptr_uint_t)instr_get_translation(instr);
6138+
CLIENT_ASSERT(address != 0,
6139+
"dr_insert_mbr_instrumentation: can't determine app address");
6140+
CLIENT_ASSERT(instr_is_mbr(instr),
6141+
"dr_insert_mbr_instrumentation must be applied to a mbr");
6142+
6143+
/* Retrieve target address. */
6144+
target = instr_get_target(instr);
6145+
6146+
dr_insert_clean_call_ex(
6147+
drcontext, ilist, instr, callee,
6148+
/* Many users will ask for mcontexts; some will set; it doesn't seem worth
6149+
* asking the user to pass in a flag: if they're using this they are not
6150+
* super concerned about overhead.
6151+
*/
6152+
DR_CLEANCALL_READS_APP_CONTEXT | DR_CLEANCALL_WRITES_APP_CONTEXT, 2,
6153+
/* Address of mbr is 1st param. */
6154+
OPND_CREATE_INTPTR(address),
6155+
/* Indirect target is 2nd param. */
6156+
target);
61326157
#endif /* X86/ARM/RISCV64 */
61336158
}
61346159

@@ -6367,7 +6392,169 @@ dr_insert_cbr_instrumentation_help(void *drcontext, instrlist_t *ilist, instr_t
63676392
#elif defined(RISCV64)
63686393
/* FIXME i#3544: Not implemented */
63696394
ASSERT_NOT_IMPLEMENTED(false);
6370-
#endif /* X86/ARM/RISCV64 */
6395+
#elif defined(AARCH64)
6396+
dcontext_t *dcontext = (dcontext_t *)drcontext;
6397+
ptr_uint_t address, target;
6398+
reg_id_t dir = DR_REG_NULL;
6399+
reg_id_t flags = DR_REG_NULL;
6400+
reg_id_t temp = DR_REG_X0;
6401+
bool temp_used = false;
6402+
int opc;
6403+
CLIENT_ASSERT(drcontext != NULL,
6404+
"dr_insert_cbr_instrumentation: drcontext cannot be NULL");
6405+
address = (ptr_uint_t)instr_get_translation(instr);
6406+
CLIENT_ASSERT(address != 0,
6407+
"dr_insert_cbr_instrumentation: can't determine app address");
6408+
CLIENT_ASSERT(instr_is_cbr(instr),
6409+
"dr_insert_cbr_instrumentation must be applied to a cbr");
6410+
target = (ptr_uint_t)opnd_get_pc(instr_get_target(instr));
6411+
6412+
/* Compute branch direction. */
6413+
opc = instr_get_opcode(instr);
6414+
if (opc == OP_cbnz || opc == OP_cbz) {
6415+
/* XXX: which is faster, additional conditional branch or cmp + csinc? */
6416+
opnd_t reg_op = instr_get_src(instr, 1);
6417+
reg_id_t reg = opnd_get_reg(reg_op);
6418+
/* If the register is stolen, we need to read the actual value first. */
6419+
if (reg_is_stolen(reg)) {
6420+
/* Save old value of temp register to SPILL_SLOT_3. */
6421+
dr_save_reg(dcontext, ilist, instr, temp, SPILL_SLOT_3);
6422+
/* Read actual register value if stolen */
6423+
dr_insert_get_stolen_reg_value(dcontext, ilist, instr, temp);
6424+
/* Use temp register to access actual register value. */
6425+
temp_used = true;
6426+
reg = reg_resize_to_opsz(temp, reg_get_size(reg));
6427+
reg_op = opnd_create_reg(reg);
6428+
}
6429+
6430+
/* Use dir register to compute direction. */
6431+
dir = (reg_to_pointer_sized(reg) == DR_REG_X0) ? DR_REG_X1 : DR_REG_X0;
6432+
/* Save old value of dir register to SPILL_SLOT_1. */
6433+
dr_save_reg(dcontext, ilist, instr, dir, SPILL_SLOT_1);
6434+
/* Use flags register to save nzcv. */
6435+
flags = (reg_to_pointer_sized(reg) == DR_REG_X2) ? DR_REG_X3 : DR_REG_X2;
6436+
/* Save old value of flags register to SPILL_SLOT_2. */
6437+
dr_save_reg(dcontext, ilist, instr, flags, SPILL_SLOT_2);
6438+
/* Save flags to flags register. */
6439+
dr_save_arith_flags_to_reg(dcontext, ilist, instr, flags);
6440+
6441+
/* Compare reg against zero. */
6442+
instr_t *cmp = INSTR_CREATE_cmp(dcontext, reg_op, OPND_CREATE_INT(0));
6443+
MINSERT(ilist, instr, cmp);
6444+
/* Compute branch direction. */
6445+
opnd_t dir_op = opnd_create_reg(dir);
6446+
instr_t *cset = INSTR_CREATE_csinc(
6447+
dcontext, dir_op, OPND_CREATE_ZR(dir_op), OPND_CREATE_ZR(dir_op),
6448+
opnd_create_cond(opc == OP_cbnz ? DR_PRED_EQ : DR_PRED_NE));
6449+
MINSERT(ilist, instr, cset);
6450+
} else if (opc == OP_tbnz || opc == OP_tbz) {
6451+
opnd_t reg_op = instr_get_src(instr, 1);
6452+
reg_id_t reg = opnd_get_reg(reg_op);
6453+
reg_id_t dir_same_width = DR_REG_NULL;
6454+
/* If the register is stolen, we need to read the actual value first. */
6455+
if (reg_is_stolen(reg)) {
6456+
/* Save old value of temp register to SPILL_SLOT_3. */
6457+
dr_save_reg(dcontext, ilist, instr, temp, SPILL_SLOT_3);
6458+
/* Read actual register value if stolen */
6459+
dr_insert_get_stolen_reg_value(dcontext, ilist, instr, temp);
6460+
/* Use temp register to access actual register value. */
6461+
temp_used = true;
6462+
reg = reg_resize_to_opsz(temp, reg_get_size(reg));
6463+
reg_op = opnd_create_reg(reg);
6464+
}
6465+
6466+
/* Use dir register to compute direction. */
6467+
dir = (reg_to_pointer_sized(reg) == DR_REG_X0) ? DR_REG_X1 : DR_REG_X0;
6468+
dir_same_width = reg_resize_to_opsz(dir, reg_get_size(reg));
6469+
/* Save old value of dir register to SPILL_SLOT_1. */
6470+
dr_save_reg(dcontext, ilist, instr, dir, SPILL_SLOT_1);
6471+
6472+
/* Extract tst_bit from reg. */
6473+
int tst_bit = opnd_get_immed_int(instr_get_src(instr, 2));
6474+
opnd_t dir_same_width_op = opnd_create_reg(dir_same_width);
6475+
instr_t *ubfm =
6476+
INSTR_CREATE_ubfm(dcontext, dir_same_width_op, reg_op,
6477+
OPND_CREATE_INT(tst_bit), OPND_CREATE_INT(tst_bit));
6478+
MINSERT(ilist, instr, ubfm);
6479+
6480+
/* Invert result if tbz. */
6481+
if (opc == OP_tbz) {
6482+
instr_t *eor =
6483+
INSTR_CREATE_eor(dcontext, dir_same_width_op, OPND_CREATE_INT(1));
6484+
MINSERT(ilist, instr, eor);
6485+
}
6486+
} else if (opc == OP_bcond) {
6487+
/* Use dir register to compute direction. */
6488+
dir = SCRATCH_REG0;
6489+
/* Save old value of dir register to SPILL_SLOT_1. */
6490+
dr_save_reg(dcontext, ilist, instr, dir, SPILL_SLOT_1);
6491+
/* Compute branch direction. */
6492+
dr_pred_type_t pred = instr_get_predicate(instr);
6493+
opnd_t dir_op = opnd_create_reg(dir);
6494+
instr_t *cset = INSTR_CREATE_csinc(
6495+
dcontext, dir_op, OPND_CREATE_ZR(dir_op), OPND_CREATE_ZR(dir_op),
6496+
opnd_create_cond(instr_invert_predicate(pred)));
6497+
MINSERT(ilist, instr, cset);
6498+
} else {
6499+
CLIENT_ASSERT(false, "unknown conditional branch type");
6500+
return;
6501+
}
6502+
6503+
if (has_fallthrough) {
6504+
ptr_uint_t fallthrough = address + instr_length(drcontext, instr);
6505+
CLIENT_ASSERT(fallthrough > address, "wrong fallthrough address");
6506+
dr_insert_clean_call_ex(
6507+
drcontext, ilist, instr, callee,
6508+
/* Many users will ask for mcontexts; some will set; it doesn't seem worth
6509+
* asking the user to pass in a flag: if they're using this they are not
6510+
* super concerned about overhead.
6511+
*/
6512+
DR_CLEANCALL_READS_APP_CONTEXT | DR_CLEANCALL_WRITES_APP_CONTEXT, 5,
6513+
/* Address of cbr is 1st parameter. */
6514+
OPND_CREATE_INTPTR(address),
6515+
/* Target is 2nd parameter. */
6516+
OPND_CREATE_INTPTR(target),
6517+
/* Fall-through is 3rd parameter. */
6518+
OPND_CREATE_INTPTR(fallthrough),
6519+
/* Branch direction is 4th parameter. */
6520+
opnd_create_reg(dir),
6521+
/* User defined data is 5th parameter. */
6522+
opnd_is_null(user_data) ? OPND_CREATE_INT32(0) : user_data);
6523+
} else {
6524+
dr_insert_clean_call_ex(
6525+
drcontext, ilist, instr, callee,
6526+
/* Many users will ask for mcontexts; some will set; it doesn't seem worth
6527+
* asking the user to pass in a flag: if they're using this they are not
6528+
* super concerned about overhead.
6529+
*/
6530+
DR_CLEANCALL_READS_APP_CONTEXT | DR_CLEANCALL_WRITES_APP_CONTEXT, 3,
6531+
/* Address of cbr is 1st parameter. */
6532+
OPND_CREATE_INTPTR(address),
6533+
/* Target is 2nd parameter. */
6534+
OPND_CREATE_INTPTR(target),
6535+
/* Branch direction is 3rd parameter. */
6536+
opnd_create_reg(dir));
6537+
}
6538+
6539+
/* Restore state */
6540+
if (opc == OP_cbnz || opc == OP_cbz) {
6541+
/* Restore arith flags. */
6542+
dr_restore_arith_flags_from_reg(dcontext, ilist, instr, flags);
6543+
/* Restore old value of flags register. */
6544+
dr_restore_reg(dcontext, ilist, instr, flags, SPILL_SLOT_2);
6545+
/* Restore old value of dir register. */
6546+
dr_restore_reg(dcontext, ilist, instr, dir, SPILL_SLOT_1);
6547+
} else if (opc == OP_bcond || opc == OP_tbnz || opc == OP_tbz) {
6548+
/* Restore old value of dir register. */
6549+
dr_restore_reg(dcontext, ilist, instr, dir, SPILL_SLOT_1);
6550+
} else {
6551+
CLIENT_ASSERT(false, "unknown conditional branch type");
6552+
}
6553+
if (temp_used) {
6554+
/* Restore old value of temp register. */
6555+
dr_restore_reg(dcontext, ilist, instr, temp, SPILL_SLOT_3);
6556+
}
6557+
#endif /* X86/ARM/RISCV64/AARCH64 */
63716558
}
63726559

63736560
DR_API void

suite/tests/CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,13 +2514,15 @@ if (X86) # FIXME i#1551, i#1569: port to ARM and AArch64
25142514
client-interface/cleancall.c "" "-thread_private -prof_pcs -opt_cleancall 0" "")
25152515
endif (NOT X64)
25162516
endif (UNIX)
2517+
tobuild_ci(client.syscall client-interface/syscall.c "" "-no_follow_children" "")
2518+
tobuild_ci(client.count-bbs client-interface/count-bbs.c "" "" "")
2519+
endif (X86)
2520+
if (X86 OR AARCH64)
25172521
tobuild_ci(client.count-ctis client-interface/count-ctis.c "" "" "")
25182522
# check dr_insert_cbr_instrumentation with out-of-line clean call
25192523
torunonly_ci(client.count-ctis-noopt client.count-ctis client.count-ctis.dll
25202524
client-interface/count-ctis.c "" "-opt_cleancall 0" "")
2521-
tobuild_ci(client.syscall client-interface/syscall.c "" "-no_follow_children" "")
2522-
tobuild_ci(client.count-bbs client-interface/count-bbs.c "" "" "")
2523-
endif (X86)
2525+
endif (X86 OR AARCH64)
25242526
if (NOT RISCV64) # TODO i#3544: Port tests to RISC-V 64
25252527
tobuild_ci(client.cleancallparams client-interface/cleancallparams.c "" "" "")
25262528
tobuild_ci(client.app_inscount client-interface/app_inscount.c "" "" "")

0 commit comments

Comments
 (0)