* Print a warning if config for VM was NULL. * Add warning if register is not added to VM due to overlap. * Update PPC register profile. * Add vector and float registers. As well as some control and system registers. * Enable to write values 4bit registers. * PPC: Uplift most common instructions. * Print warning if reserved SPR instruction is encountered. * Update PC/LR addresses and add ca32 writes. * Write cache needs to get flushed so load and store tests don't share the same memory. * Add missing T/F branch mnemonics. * Add Move to/from CR/CR0-7 * Fix TA address calculation for branch instructions. * Add branch tests for branch mnemonics. * Add XNOP * NOP cache touch instructions. * Add undocumented ATTN instruction to the not_implemented group. * Add isel instruction. * Implement CRCLR, CRSET, CROR. * Add CNTLZ instructions. * Add mcrf instructions. * Add inacive test for cmpb. * Add Load bytes reverse instructions. * Add test and add address alignment to dcbz. * Add eqv test * Correct DIV and MUL operations * Add div tests. * Add tests and correct MT/MFXER * Remove register ca32, ov32 * Remove explici setting of cr register because QEMU does not do it. Otherwise we get a mismatch in the trace * Simplify carry set for add and sub. Sub instructions are exclusivly defined with addition. Hence no sub case needed. * Unify BD and fix branch instructions. Fix: Check the single bit not the cr reg * Document Conditional branches and replace NOPs with EMPTY * Add mulli instruction to double word instructions. * Fix ca set for shift instructions: * ca value had to be determined before the shift happened. The wrong ca value was calculated if the src and target reg were the same. * Replace NOP wit EMPTY. * Use MSB isntead of SLT. * Brought fixup of ADD and SUB instructions: The add and sub instructions had several issues which let to incorrect execution. * The carry was incorrectly if three add operations happened (only the last add were checked, not both). * The carry was incorrectly set if the src and target register matched. * Same applies for the CR bit. * It was too complex. Several local variables were introduced for this. * Set result in local var, since it would change if src and target reg are the same. * Remove MTMSR and MFMSR since it is too complex and untestable currently. * Use unsigned int for shift. Otherwise the 0x1c shift produces a runtime error since 0xf is int as default. * Fix mtxer: Only write flag bits. * Let NOT_IMPLEMENTED macro return NULL. * Mark st[wd]cx and l[wd]cx as not implemented. * Increase dcache_line_size to 128 bytes. * Fix cntlz for ppc32. m was set incorrectly, since it is 0 not 32 for 32bit cpus. * Fix isel: Use op.crx reg instead of imm. * Unify helper function names: Prependnig `ppc_` mark as IPI * Add more "Move to SPR" cases. * MULLI opeartes only on double word on 64bit CPUs. * Most registers are now assigned the control register type and no longer show up in the ar command. * Fix xor if dest and src registers match by saving result in local var. * Fix BE/LE issue for Load BRX instructions. * Fix shifts: Use only lower 6bits of n. * User Pure local variables for ROT macros. * Fix rldimi instructions. * n was not inverted. * more than 6 bits of n could be used * Add 32bit emulateme tests. * Add 64bit emulateme tests. * Determine lg(v) in inline function. * Calculate CR bit in C not in the VM. * Check if `~mask = 0` and skip mask calculation if yes. * Check for `sh == 0` and skip rotations where possible. * Remove `la` instruction. `la` is a mnemonic for `addi`. * Remove SPR instructions which are not supported by QEMU or not traced yet. For most set/read SPR instructions QEMU segfaults. In case of SPR 1 (xer), 8 (lr) and 9 (ctr) the assembler resolves them to their mnemonics (mtxer, mtlr etc.). This means the code here is never reached. To test the get_xer code MFXER was added again. The rz-tracetests will fail for this instructions (due to missing ca32, ov32). But this case is covert in an issue.
310 lines
8.7 KiB
C
310 lines
8.7 KiB
C
// SPDX-FileCopyrightText: 2021 heersin <teablearcher@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_analysis.h>
|
|
|
|
/**
|
|
* \name Config and Init State
|
|
* @{
|
|
*/
|
|
|
|
static void var_state_free(void *e, void *user) {
|
|
RzAnalysisILInitStateVar *s = e;
|
|
if (!s) {
|
|
return;
|
|
}
|
|
rz_il_value_free(s->val);
|
|
}
|
|
|
|
RZ_API RzAnalysisILInitState *rz_analysis_il_init_state_new() {
|
|
RzAnalysisILInitState *r = RZ_NEW0(RzAnalysisILInitState);
|
|
if (!r) {
|
|
return NULL;
|
|
}
|
|
rz_vector_init(&r->vars, sizeof(RzAnalysisILInitStateVar), var_state_free, NULL);
|
|
return r;
|
|
}
|
|
|
|
RZ_API void rz_analysis_il_init_state_free(RzAnalysisILInitState *state) {
|
|
if (!state) {
|
|
return;
|
|
}
|
|
rz_vector_fini(&state->vars);
|
|
}
|
|
|
|
/**
|
|
* Set the value of the global variable called \p name name to \p val in the initial state \p state
|
|
*/
|
|
RZ_API void rz_analysis_il_init_state_set_var(RZ_NONNULL RzAnalysisILInitState *state,
|
|
RZ_NONNULL const char *name, RZ_NONNULL RZ_OWN RzILVal *val) {
|
|
rz_return_if_fail(state && name && val);
|
|
RzAnalysisILInitStateVar *v = rz_vector_push(&state->vars, NULL);
|
|
if (!v) {
|
|
rz_il_value_free(val);
|
|
return;
|
|
}
|
|
v->name = name;
|
|
v->val = val;
|
|
}
|
|
|
|
/**
|
|
* Create an IL config and initialize it with the given minimal mandatory info
|
|
*/
|
|
RZ_API RZ_OWN RzAnalysisILConfig *rz_analysis_il_config_new(ut32 pc_size, bool big_endian, ut32 mem_key_size) {
|
|
rz_return_val_if_fail(pc_size && mem_key_size, NULL);
|
|
RzAnalysisILConfig *r = RZ_NEW0(RzAnalysisILConfig);
|
|
if (!r) {
|
|
return NULL;
|
|
}
|
|
r->pc_size = pc_size;
|
|
r->big_endian = big_endian;
|
|
r->mem_key_size = mem_key_size;
|
|
rz_pvector_init(&r->labels, (RzPVectorFree)rz_il_effect_label_free);
|
|
return r;
|
|
}
|
|
|
|
RZ_API void rz_analysis_il_config_free(RzAnalysisILConfig *cfg) {
|
|
if (!cfg) {
|
|
return;
|
|
}
|
|
rz_pvector_fini(&cfg->labels);
|
|
free(cfg);
|
|
}
|
|
|
|
/**
|
|
* Add \p label to the IL config \p cfg to describe that it is globally available in a vm
|
|
*/
|
|
RZ_API void rz_analysis_il_config_add_label(RZ_NONNULL RzAnalysisILConfig *cfg, RZ_NONNULL RZ_OWN RzILEffectLabel *label) {
|
|
rz_return_if_fail(cfg && label);
|
|
rz_pvector_push(&cfg->labels, label);
|
|
}
|
|
|
|
/// @}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
/**
|
|
* \name Analysis IL VM
|
|
* @{
|
|
*/
|
|
|
|
static void setup_vm_from_config(RzAnalysis *analysis, RzAnalysisILVM *vm, RzAnalysisILConfig *cfg);
|
|
static void setup_vm_init_state(RzAnalysisILVM *vm, RZ_NULLABLE RzAnalysisILInitState *is, RZ_NULLABLE RzReg *reg);
|
|
|
|
/**
|
|
* Create and initialize an RzAnalysisILVM with the current arch/cpu/bits configuration and plugin
|
|
* \p init_state_reg optional RzReg to take variable values from, unless the plugin overrides them using RzAnalysisILInitState
|
|
* \return RzAnalysisRzil* a pointer to RzAnalysisILVM instance
|
|
*/
|
|
RZ_API RZ_OWN RzAnalysisILVM *rz_analysis_il_vm_new(RzAnalysis *a, RZ_NULLABLE RzReg *init_state_reg) {
|
|
rz_return_val_if_fail(a, NULL);
|
|
RzAnalysisILConfig *config = a->cur->il_config(a);
|
|
if (!config) {
|
|
return false;
|
|
}
|
|
RzAnalysisILVM *r = RZ_NEW0(RzAnalysisILVM);
|
|
if (!r) {
|
|
goto ruby_pool;
|
|
}
|
|
r->io_buf = rz_buf_new_with_io(&a->iob);
|
|
setup_vm_from_config(a, r, config);
|
|
if (!r->vm) {
|
|
rz_buf_free(r->io_buf);
|
|
free(r);
|
|
r = NULL;
|
|
goto ruby_pool;
|
|
}
|
|
setup_vm_init_state(r, config->init_state, init_state_reg);
|
|
ruby_pool:
|
|
rz_analysis_il_config_free(config);
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Frees an RzAnalysisILVM instance
|
|
*/
|
|
RZ_API void rz_analysis_il_vm_free(RZ_NULLABLE RzAnalysisILVM *vm) {
|
|
if (!vm) {
|
|
return;
|
|
}
|
|
rz_il_vm_free(vm->vm);
|
|
rz_il_reg_binding_free(vm->reg_binding);
|
|
rz_buf_free(vm->io_buf);
|
|
free(vm);
|
|
}
|
|
|
|
static bool setup_regs(RzAnalysis *a, RzAnalysisILVM *vm, RzAnalysisILConfig *cfg) {
|
|
if (!a->cur->get_reg_profile) {
|
|
return false;
|
|
}
|
|
// Explicitly use a new reg here!
|
|
// The a->reg might be changed by the user, but plugins expect exactly
|
|
// the register profile they supplied. Syncing will later adjust the register
|
|
// contents if necessary.
|
|
RzReg *reg = rz_reg_new();
|
|
if (!reg) {
|
|
return false;
|
|
}
|
|
char *profile = a->cur->get_reg_profile(a);
|
|
bool succ;
|
|
if (!profile) {
|
|
succ = false;
|
|
goto new_real;
|
|
}
|
|
succ = rz_reg_set_profile_string(reg, profile);
|
|
free(profile);
|
|
if (!succ) {
|
|
goto new_real;
|
|
}
|
|
if (cfg->reg_bindings) {
|
|
size_t count = 0;
|
|
while (cfg->reg_bindings[count]) {
|
|
count++;
|
|
}
|
|
vm->reg_binding = rz_il_reg_binding_exactly(reg, count, cfg->reg_bindings);
|
|
} else {
|
|
vm->reg_binding = rz_il_reg_binding_derive(reg);
|
|
}
|
|
if (!vm->reg_binding) {
|
|
succ = false;
|
|
goto new_real;
|
|
}
|
|
rz_il_vm_setup_reg_binding(vm->vm, vm->reg_binding);
|
|
new_real:
|
|
rz_reg_free(reg);
|
|
return succ;
|
|
}
|
|
|
|
static void setup_vm_from_config(RzAnalysis *analysis, RzAnalysisILVM *vm, RzAnalysisILConfig *cfg) {
|
|
vm->vm = rz_il_vm_new(0, cfg->pc_size, cfg->big_endian);
|
|
if (!vm->vm) {
|
|
return;
|
|
}
|
|
if (!setup_regs(analysis, vm, cfg)) {
|
|
rz_il_vm_free(vm->vm);
|
|
vm->vm = NULL;
|
|
return;
|
|
}
|
|
rz_il_vm_add_mem(vm->vm, 0, rz_il_mem_new(vm->io_buf, cfg->mem_key_size));
|
|
void **it;
|
|
rz_pvector_foreach (&cfg->labels, it) {
|
|
RzILEffectLabel *lbl = *it;
|
|
rz_il_vm_add_label(vm->vm, rz_il_effect_label_dup(lbl));
|
|
}
|
|
}
|
|
|
|
static void setup_vm_init_state(RzAnalysisILVM *vm, RZ_NULLABLE RzAnalysisILInitState *is, RZ_NULLABLE RzReg *reg) {
|
|
if (reg) {
|
|
rz_il_vm_sync_from_reg(vm->vm, vm->reg_binding, reg);
|
|
}
|
|
if (is) {
|
|
RzAnalysisILInitStateVar *v;
|
|
rz_vector_foreach(&is->vars, v) {
|
|
rz_il_vm_set_global_var(vm->vm, v->name, rz_il_value_dup(v->val));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the values of all variables in \p vm that are bound to registers and PC to the respective contents from \p reg.
|
|
*
|
|
* This is like the low-level `rz_il_vm_sync_from_reg()`, but uses the binding that is part of \p vm.
|
|
* See its documentation for details.
|
|
*/
|
|
RZ_API void rz_analysis_il_vm_sync_from_reg(RzAnalysisILVM *vm, RZ_NONNULL RzReg *reg) {
|
|
rz_return_if_fail(vm && reg);
|
|
rz_il_vm_sync_from_reg(vm->vm, vm->reg_binding, reg);
|
|
}
|
|
|
|
/**
|
|
* Set the values of all bound regs in \p reg to the respective variable or PC contents in \p vm.
|
|
*
|
|
* This is like the low-level `rz_il_vm_sync_to_reg()`, but uses the binding that is part of \p vm.
|
|
* See its documentation for details.
|
|
*
|
|
* \return whether the sync was cleanly applied without errors or adjustments
|
|
*/
|
|
RZ_API bool rz_analysis_il_vm_sync_to_reg(RzAnalysisILVM *vm, RZ_NONNULL RzReg *reg) {
|
|
rz_return_val_if_fail(vm && reg, false);
|
|
return rz_il_vm_sync_to_reg(vm->vm, vm->reg_binding, reg);
|
|
}
|
|
|
|
/**
|
|
* Perform a single step in the VM
|
|
*
|
|
* If given, this syncs the contents of \p reg into the vm.
|
|
* Then it disassembles an instruction at the program counter of the vm and executes it.
|
|
* Finally, if no error occured, the contents are optionally synced back to \p reg.
|
|
*
|
|
* \return and indicator for which error occured, if any
|
|
*/
|
|
RZ_API RzAnalysisILStepResult rz_analysis_il_vm_step(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisILVM *vm, RZ_NULLABLE RzReg *reg) {
|
|
rz_return_val_if_fail(analysis && vm, false);
|
|
RzAnalysisPlugin *cur = analysis->cur;
|
|
if (!cur || !analysis->read_at) {
|
|
return RZ_ANALYSIS_IL_STEP_RESULT_NOT_SET_UP;
|
|
}
|
|
|
|
if (reg) {
|
|
rz_analysis_il_vm_sync_from_reg(vm, reg);
|
|
}
|
|
ut64 addr = rz_bv_to_ut64(vm->vm->pc);
|
|
|
|
ut8 code[32] = { 0 };
|
|
analysis->read_at(analysis, addr, code, sizeof(code));
|
|
RzAnalysisOp op = { 0 };
|
|
int r = rz_analysis_op(analysis, &op, addr, code, sizeof(code), RZ_ANALYSIS_OP_MASK_IL | RZ_ANALYSIS_OP_MASK_HINT);
|
|
RzILOpEffect *ilop = r < 0 ? NULL : op.il_op;
|
|
|
|
RzAnalysisILStepResult res;
|
|
if (ilop) {
|
|
bool succ = rz_il_vm_step(vm->vm, ilop, addr + (op.size > 0 ? op.size : 1));
|
|
res = succ ? RZ_ANALYSIS_IL_STEP_RESULT_SUCCESS : RZ_ANALYSIS_IL_STEP_IL_RUNTIME_ERROR;
|
|
if (reg) {
|
|
rz_analysis_il_vm_sync_to_reg(vm, reg);
|
|
}
|
|
} else {
|
|
res = RZ_ANALYSIS_IL_STEP_INVALID_OP;
|
|
}
|
|
|
|
rz_analysis_op_fini(&op);
|
|
return res;
|
|
}
|
|
|
|
/// @}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
/**
|
|
* \name Global, user-faced VM setup
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* (Re)initialize the global user-faced vm
|
|
* \return whether the init succeeded
|
|
*/
|
|
RZ_API bool rz_analysis_il_vm_setup(RzAnalysis *analysis) {
|
|
rz_return_val_if_fail(analysis, false);
|
|
rz_analysis_il_vm_cleanup(analysis);
|
|
if (!analysis->cur || !analysis->cur->il_config) {
|
|
RZ_LOG_WARN("Could not set up VM. Analysis plugin or RZIL config was NULL.\n");
|
|
return false;
|
|
}
|
|
analysis->il_vm = rz_analysis_il_vm_new(analysis, analysis->reg);
|
|
if (analysis->il_vm) {
|
|
// rz_analysis_il_vm_new merges the contents of analysis->reg with the plugin's optional RzAnalysisILInitState
|
|
// Now sync the merged state back:
|
|
rz_il_vm_sync_to_reg(analysis->il_vm->vm, analysis->il_vm->reg_binding, analysis->reg);
|
|
}
|
|
return !!analysis->il_vm;
|
|
}
|
|
|
|
/**
|
|
* Destroy the global user-faced vm
|
|
*/
|
|
RZ_API void rz_analysis_il_vm_cleanup(RzAnalysis *analysis) {
|
|
rz_return_if_fail(analysis);
|
|
rz_analysis_il_vm_free(analysis->il_vm);
|
|
analysis->il_vm = NULL;
|
|
}
|
|
|
|
/// @}
|