diff --git a/debuggers/openocd/openocd_wrapper.cc b/debuggers/openocd/openocd_wrapper.cc new file mode 100644 index 00000000..1dd8fe3d --- /dev/null +++ b/debuggers/openocd/openocd_wrapper.cc @@ -0,0 +1,732 @@ +#include "openocd_wrapper.hpp" + +extern "C" { + #include "config.h" + + #include "src/helper/system.h" + #include "src/helper/log.h" + #include "src/helper/time_support.h" + #include "src/helper/command.h" + #include "src/helper/configuration.h" + #include "src/helper/util.h" + #include "src/helper/ioutil.h" + + #include "src/jtag/jtag.h" + + #include "src/target/algorithm.h" + #include "src/target/breakpoints.h" + #include "src/target/register.h" + #include "src/target/target_type.h" + #include "src/target/target.h" + #include "src/target/cortex_a.h" + #include "src/target/arm_adi_v5.h" + #include "src/target/armv7a.h" + + #include "jimtcl/jim.h" + + extern struct command_context *setup_command_handler(Jim_Interp *interp); +} + +#include +#include +#include +#include "config/VariantConfig.hpp" +#include "sal/SALInst.hpp" +#include "sal/SALConfig.hpp" + +#include "util/Logger.hpp" + +using std::endl; +using std::cout; +using std::cerr; +using std::hex; +using std::dec; + +/* + * Flag for finishing main loop execution. + */ +static bool oocdw_exection_finished = false; + +/* + * Initially set target struct pointers for use in + * multiple functions. + */ +static struct target *target_a9; +static struct arm *arm_a9; +static struct target *target_m3; +static struct command_context *cmd_ctx; + +/* + * State variable for propagation of a coming horizontal hop. + * For correct execution of a horizontal hop, an additional + * single-step needs to be executed before resuming target + * execution. + * Variable is set while setting new halt_conditions on basis of + * current instruction and its memory access(es). + */ +static bool horizontal_step = false; + +/* + * As the normal loop execution is + * resume - wait for halt - trigger events and set next halt condition(s), + * single-stepping is different. It is accomplished by setting this state + * variable. + */ +static bool single_step_requested = false; + +// Timer related structures +#define P_MAX_TIMERS 32 +struct timer{ + bool inUse; // Timer slot is in-use (currently registered). + uint64_t timeToFire; // Time to fire next in microseconds + struct timeval time_begin; // Timer value at activation of timer + bool active; // 0=inactive, 1=active. + p_timer_handler_t funct; // A callback function for when the + // timer fires. + void *this_ptr; // The this-> pointer for C++ callbacks + // has to be stored as well. +#define PMaxTimerIDLen 32 + char id[PMaxTimerIDLen]; // String ID of timer. +}; + +struct timer timers[P_MAX_TIMERS]; + +fail::Logger LOG("OpenOCD", false); + +/** FORWARD DECLARATIONS **/ +static void update_timers(); +static struct watchpoint *getHaltingWatchpoint(); +static void getCurrentMemAccesses(struct halt_condition *accesses); +static uint32_t getCurrentPC(); + +/* + * Main entry and main loop. + * + * States: + * 0) Init + * 1) Reset + * 2) Wait for navigational commands from experiment + * 3) (Execute single-steps if requested) + * 3) Resume execution + * 4) Wait for halt + * 5) Fire event + * 6) update timers + * 7) goto 2 + */ +int main(int argc, char *argv[]) +{ + int ret; + + /* === INITIALIZATION === */ + + // Redirect output to logfile + FILE *file = fopen("oocd.log", "w"); + set_log_output(NULL, file); + + /* initialize commandline interface */ + cmd_ctx = setup_command_handler(NULL); + + if (util_init(cmd_ctx) != ERROR_OK) + return 1;//EXIT_FAILURE; + + if (ioutil_init(cmd_ctx) != ERROR_OK) + return 1;//EXIT_FAILURE; + + command_context_mode(cmd_ctx, COMMAND_CONFIG); + command_set_output_handler(cmd_ctx, configuration_output_handler, NULL); + + /* Start the executable meat that can evolve into thread in future. */ + //ret = openocd_thread(argc, argv, cmd_ctx); + + if (parse_cmdline_args(cmd_ctx, argc, argv) != ERROR_OK) + return EXIT_FAILURE; + + /*if (server_preinit() != ERROR_OK) + return EXIT_FAILURE;*/ + + // set path to configuration file + add_script_search_dir(OOCD_CONF_FILES_PATH); + add_config_command("script "OOCD_CONF_FILE_PATH); + + ret = parse_config_file(cmd_ctx); + if (ret != ERROR_OK) { + LOG << "Error in openocd configuration!\nFor more detailed information refer to oocd.log" << endl; + return EXIT_FAILURE; + } + + // Servers (gdb/telnet) are not being activated + /* ret = server_init(cmd_ctx); + if (ERROR_OK != ret) + return EXIT_FAILURE;*/ + + ret = command_run_line(cmd_ctx, (char*)"init"); + if (ret != ERROR_OK) { + LOG << "Error in openocd initialization!\nFor more detailed information refer to oocd.log" << endl; + return EXIT_FAILURE; + } + + // Find target cortex_a9 core 0 + target_a9 = get_target("omap4460.cpu"); + if (!target_a9) { + LOG << "FATAL ERROR: Target omap4460.cpu not found!" << endl; + return 1; + } + + // Find target cortex_m3 core 0 + target_m3 = get_target("omap4460.m30"); + if (!target_m3) { + LOG << "FATAL ERROR: Target omap4460.m30 not found!" << endl; + return 1; + } + + arm_a9 = target_to_arm(target_a9); + jtag_poll_set_enabled (false); + + LOG << "OpenOCD 0.7.0 for Fail* and Pandaboard initialized" << endl; + + for (int i=0; i MAIN LOOP === */ + + /* + * Initial reboot + */ + oocdw_reboot(); + + /* + * Switch to experiment for navigational instructions + */ + fail::simulator.startup(); + + while (!oocdw_exection_finished) { + + /* + * At this point the device should always be in halted state + * So single-stepping is done loop-wise, here. + * For having functional timers anyways, we need to update + * in every loop iteration + */ + while (single_step_requested) { + if (target_step(target_a9, 1, 0, 1)) { + LOG << "FATAL ERROR: Single-step could not be executed" << endl; + return 1; + } + /* + * Because this is a micro main-loop, we need to update + * timers. This loop can be executed several times (e.g. + * in tracing mode), so timers would be neglected otherwise. + */ + update_timers(); + uint32_t pc = getCurrentPC(); + + /* + * Reset request flag. Needs to be done before calling + * onBreakpoint, because otherwise it would reset a + * newly activated request after coroutine switch. + */ + single_step_requested = false; + + fail::simulator.onBreakpoint(NULL, pc, fail::ANY_ADDR); + } + + /* + * In the following loop stage it is assumed, that the target is + * running, so execution needs to be resumed, if the target is + * halted. + */ + if (target_a9->state == TARGET_HALTED) { + LOG << "Resume" << endl; + if (target_resume(target_a9, 1, 0, 1, 1)) { + LOG << "FATAL ERROR: Target could not be resumed!" << endl; + return 1; + } + } + + // Polling needs to be done to detect target halt state changes. + if (target_poll(target_a9)) { + LOG << "FATAL ERROR: Error polling after resume!" << endl; + return 1; + } + + // Check for halt and trigger event accordingly + if (target_a9->state == TARGET_HALTED) { + + uint32_t pc = getCurrentPC(); + + switch (target_a9->debug_reason) { + case DBG_REASON_WPTANDBKPT: + /* Fall through */ + case DBG_REASON_BREAKPOINT: + fail::simulator.onBreakpoint(NULL, pc, fail::ANY_ADDR); + if (target_a9->debug_reason == DBG_REASON_BREAKPOINT) { + break; + } + /* Potential fall through */ + case DBG_REASON_WATCHPOINT: + { + // ToDo: Replace with calls of every current memory access + struct watchpoint *wp = getHaltingWatchpoint(); + if (!wp) { + // ToDo: Determine address by interpreting instruction and register contents + LOG << "FATAL ERROR: Can't determine memory-access address of halt cause" << endl; + return 1; + } + + int iswrite; + switch (wp->rw) { + case WPT_READ: + iswrite = 0; + break; + case WPT_WRITE: + iswrite = 1; + break; + case WPT_ACCESS: + // ToDo: Can't tell if read or write + iswrite = 1; + break; + default: + LOG << "FATAL ERROR: Can't determine memory-access type of halt cause" << endl; + return 1; + break; + } + + fail::simulator.onMemoryAccess(NULL, wp->address, wp->length, iswrite, pc); + } + break; + case DBG_REASON_SINGLESTEP: + LOG << "FATAL ERROR: Single-step is handled in previous loop phase" << endl; + return 1; + break; + default: + LOG << "FATAL ERROR: Target halted in unexpected cpu state!" << endl; + break; + } + + /* + * Execute single-step if horizontal hop was detected + */ + if (target_a9->state == TARGET_HALTED && horizontal_step) { + if (target_step(target_a9, 1, 0, 1)) { + LOG << "FATAL ERROR: Single-step could not be executed!" << endl; + return 1; + } + // Reset horizontal hop flag + horizontal_step = false; + } + } + + // Update timers. Granularity will be coarse, because this is done after polling the device + update_timers(); + } + + /* === FINISHING UP === */ + unregister_all_commands(cmd_ctx, NULL); + + /* free commandline interface */ + command_done(cmd_ctx); + + adapter_quit(); + + fclose(file); + + LOG << "finished" << endl; + + /* + * Return to experiment flow to properly exit execution + */ + fail::simulator.resume(); + + return 0; +} + + + +void oocdw_set_halt_condition(struct halt_condition *hc) +{ + assert((target_a9->state == TARGET_HALTED) && "Target not halted"); + assert((hc != NULL) && "No halt condition defined"); + + horizontal_step = false; + + if (hc->type == HALT_TYPE_BP) { + if (breakpoint_add(target_a9, hc->address, + hc->addr_len, BKPT_HARD)) { + LOG << "FATAL ERROR: Breakpoint could not be set" << endl; + exit(-1); + } + + // Compare current pc with set breakpoint (potential horizontal hop) + if (hc->address == getCurrentPC()) { + horizontal_step = true; + } + } else if (hc->type == HALT_TYPE_SINGLESTEP) { + single_step_requested = true; + } else { + enum watchpoint_rw rw = WPT_ACCESS; + if (hc->type == HALT_TYPE_WP_READ) { + rw = WPT_READ; + } else if (hc->type == HALT_TYPE_WP_WRITE) { + rw = WPT_WRITE; + } else if (hc->type == HALT_TYPE_WP_READWRITE) { + rw = WPT_ACCESS; + } + + // No masking => value = 0; mask = 0xffffffff + // length const 1, because functional correct and smallest + // hit surface + if (watchpoint_add(target_a9, hc->address, hc->addr_len, + rw, 0x0, 0xffffffff)) { + LOG << "FATAL ERROR: Watchpoint could not be set" << endl; + exit(-1); + } + + // Compare current memory access events with set watchpoint + // (potential horizontal hop) + struct halt_condition mem_accesses [4]; + getCurrentMemAccesses(mem_accesses); + int i = 0; + while (mem_accesses[i].address) { + // Look for overlapping similar memory access + if (mem_accesses[i].type == hc->type) { + if (mem_accesses[i].address < hc->address) { + if (mem_accesses[i].address + mem_accesses[i].addr_len >= hc->address) { + horizontal_step = true; + } + } else { + if (hc->address + hc->addr_len >= mem_accesses[i].address) { + horizontal_step = true; + } + } + } + i++; + } + } +} + +void oocdw_delete_halt_condition(struct halt_condition *hc) +{ + assert((target_a9->state == TARGET_HALTED) && "Target not halted"); + assert((hc != NULL) && "No halt condition defined"); + + // Remove halt condition from pandaboard + if (hc->type == HALT_TYPE_BP) { + breakpoint_remove(target_a9, hc->address); + } else if (hc->type == HALT_TYPE_SINGLESTEP) { + // Do nothing. Single-stepping event hits one time and + // extinguishes itself automatically + } else if ((hc->type == HALT_TYPE_WP_READWRITE) || + (hc->type == HALT_TYPE_WP_READ) || + (hc->type == HALT_TYPE_WP_WRITE)) { + watchpoint_remove(target_a9, hc->address); + } +} + +void oocdw_halt_target() +{ + if (target_halt(target_a9)) { + LOG << "FATAL ERROR: Target could not be halted" << endl; + exit(-1); + } + + /* + * Wait for target to actually stop. + */ + long long then = timeval_ms(); + if (target_poll(target_a9)) { + LOG << "FATAL ERROR: Target polling failed" << endl; + exit(-1); + } + while (target_a9->state != TARGET_HALTED) { + if (target_poll(target_a9)) { + LOG << "FATAL ERROR: Target polling failed" << endl; + exit(-1); + } + if (timeval_ms() > then + 1000) { + LOG << "FATAL ERROR: Timeout waiting for target halt" << endl; + exit(-1); + } + } +} + + +/* + * As "reset halt" and "reset init" fail irregularly on the pandaboard, resulting in + * a device freeze, from which only a manual reset can recover the state, a different + * approach is used to reset the device and navigate to main entry. + * A "reset run" command is executed, which does not cause a device freeze. + * Afterwards the pandaboard is immediately halted, so the halt normally triggers + * in the initialization phase. Afterwards a breakpoint is set on the target + * instruction, so the device is set for navigation to the target dynamic instructions. + * + * 1) The indirection of navigating to the main entry is needed, because + * the first halt condition might be a watchpoint, which could also + * trigger in the binary loading phase. + * 2) Because it is not guaranteed, that the immediate halt is triggered + * before main entry, a "safety loop" is executed before the main + * entry. It is therefore necessary to first navigate into this loop + * and then jump over it by modifying the program counter. + */ +void oocdw_reboot() +{ + int retval, reboot_success = 0; + + while (!reboot_success) { + LOG << "Rebooting device" << endl; + reboot_success = 1; + + // If target is not halted, reset will result in freeze + if (target_a9->state != TARGET_HALTED) { + oocdw_halt_target(); + } + + /* + * The actual reset command executed by OpenOCD jimtcl-engine + */ + retval = Jim_Eval(cmd_ctx->interp, "reset"); + + retval = target_call_timer_callbacks_now(); + if (retval) { + LOG << "target_call_timer_callbacks_now() Error" << endl; + exit(-1); + } + + struct target *target; + for (target = all_targets; target; target = target->next) + target->type->check_reset(target); + + // Immediate halt after reset. + oocdw_halt_target(); + + uint32_t pc = buf_get_u32(arm_a9->pc->value, 0, 32); + if (pc < 0x8300006c || pc > 0x83000088) { + LOG << "NOT IN LOOP!!! PC: " << hex << pc << dec << std::endl; + } else { + LOG << "Stopped in loop. PC: " << hex << pc << dec << std::endl; + } + + // BP on entering main + struct halt_condition hc; + + // ToDo: Non static MAIN ENTRY + hc.address = 0x83000088; + hc.addr_len = 4; + hc.type = HALT_TYPE_BP; + oocdw_set_halt_condition(&hc); + + if (target_resume(target_a9, 1, 0, 1, 1)) { + LOG << "FATAL ERROR: Target could not be resumed" << endl; + exit(-1); + } + + long long then; + then = timeval_ms(); + while (target_a9->state != TARGET_HALTED) { + int retval = target_poll(target_a9); + if (retval != ERROR_OK) { + LOG << "FATAL ERROR: Target polling failed" << endl; + exit(-1); + } + // ToDo: Adjust timeout + if (timeval_ms() > then + 1000) { + LOG << "Error: Timeout waiting for main entry" << endl; + reboot_success = 0; + static int fail_counter = 1; + if (fail_counter++ > 4) { + LOG << "FATAL ERROR: Rebooting not possible" << endl; + exit(-1); + } + oocdw_halt_target(); + break; + } + } + // Remove temporary + oocdw_delete_halt_condition(&hc); + + if (reboot_success) { + // Jump over safety loop (set PC) + oocdw_write_reg(15, ARM_REGS_CORE, 0x8300008c); + } + } +} + +void oocdw_read_reg(uint32_t reg_num, enum arm_reg_group rg, uint32_t *data) +{ + assert((target_a9->state == TARGET_HALTED) && "Target not halted"); + + struct arm *arm = (struct arm*)(target_a9->arch_info); + if (rg == ARM_REGS_CORE) { + struct reg *reg = arm->core_cache->reg_list + reg_num; + + // Core registers + if (arm->read_core_reg(target_a9, reg, reg_num, arm->core_mode)) { + LOG << "FATAL ERROR: Could not read register " << reg_num << endl; + exit(-1); + } + + *data = *((uint32_t*)(reg->value)); + } else { + // ToDo: + // coprocessor registers + /* + * struct target *target, int cpnum, + uint32_t op1, uint32_t op2, + uint32_t CRn, uint32_t CRm, + uint32_t *value + */ + /*if (arm->mrc(...)) { + + }*/ + LOG << "FATAL ERROR: Accessing coprocessor registers not implemented yet." << endl; + exit(-1); + } +} + +void oocdw_write_reg(uint32_t reg_num, enum arm_reg_group rg, uint32_t data) +{ + assert((target_a9->state == TARGET_HALTED) && "Target not halted"); + + struct arm *arm = (struct arm*)(target_a9->arch_info); + if (rg == ARM_REGS_CORE) { + struct reg *reg = arm->core_cache->reg_list + reg_num; + + // Core registers + if (arm->write_core_reg(target_a9, reg, reg_num, arm->core_mode, data)) { + LOG << "FATAL ERROR: Could not write register " << reg_num << endl; + exit(-1); + } + } else { + // ToDo: + // coprocessor registers + /* + * struct target *target, int cpnum, + uint32_t op1, uint32_t op2, + uint32_t CRn, uint32_t CRm, + uint32_t value + */ + /*if (arm->mcr(...)) { + + + }*/ + LOG << "FATAL ERROR: Accessing coprocessor registers not implemented yet." << endl; + exit(-1); + } +} + +void oocdw_finish() +{ + oocdw_exection_finished = true; +} + +void oocdw_read_from_memory(uint32_t address, uint32_t chunk_size, + uint32_t chunk_num, uint8_t *data) +{ + if (target_read_memory(target_a9, address, chunk_size, chunk_num, data)) { + LOG << "FATAL ERROR: Reading from memory failed." << endl; + exit(-1); + } +} + +void oocdw_write_to_memory(uint32_t address, uint32_t chunk_size, + uint32_t chunk_num, uint8_t const *data, + bool cache_inval) +{ + struct target *write_target; + if (cache_inval) { + // A9 writes and invalidates + write_target = target_a9; + } else { + // M3 writes and does not invalidate + write_target = target_m3; + } + + if (target_write_memory(write_target, address, chunk_size, chunk_num, data)) { + LOG << "FATAL ERROR: Writing to memory failed." << endl; + exit(-1); + } +} + +int oocdw_register_timer(void *this_ptr, p_timer_handler_t funct, uint64_t useconds, + bool active, const char *id) +{ + for (int i=0; ipc->value, 0, 32); +} + +/* + * Returns all memory access events of current instruction in + * 0-terminated (0 in address field) array of max length. + * TODO implement analysis of current instruction in combination + * with register contents + */ +static void getCurrentMemAccesses(struct halt_condition *accesses) +{ + // ToDo: Get all 1-byte memory access events of current + // instruction. For now return empty array. + accesses[0].address = 0; +} + +/* + * If only one watchpoint is active, this checkpoint gets returned by + * this function + */ +static struct watchpoint *getHaltingWatchpoint() +{ + struct watchpoint *watchpoint = target_a9->watchpoints; + + if (watchpoint->next) { + // Multiple watchpoints activated? No single answer possible + return NULL; + } + return watchpoint; +} diff --git a/debuggers/openocd/openocd_wrapper.hpp.in b/debuggers/openocd/openocd_wrapper.hpp.in new file mode 100644 index 00000000..6042c36b --- /dev/null +++ b/debuggers/openocd/openocd_wrapper.hpp.in @@ -0,0 +1,141 @@ +#ifndef __OOCD_WRAPPER_H_ +#define __OOCD_WRAPPER_H_ + +#include + +#define OOCD_CONF_FILE_PATH "@OOCD_CONF_FILE_PATH@" +#define OOCD_CONF_FILES_PATH "@OOCD_CONF_FILES_PATH@" + +enum arm_reg_group { + ARM_REGS_CORE, + ARM_REGS_COPROCESSOR, +}; + +enum halt_type { + HALT_TYPE_BP, + HALT_TYPE_WP_READWRITE, + HALT_TYPE_WP_READ, + HALT_TYPE_WP_WRITE, + HALT_TYPE_SINGLESTEP, +}; + +struct halt_condition { + enum halt_type type; + uint32_t address; + uint32_t addr_len; +}; + +/* + * Read register value + * Reads the value of the register defined by \a regnum. + * @param reg_num the target register as defined in the ArmArchitecture + * @param rg Definition of register group of register defined by \a reg_num + * @param data pointer to data as return value + */ +void oocdw_read_reg(uint32_t reg_num, enum arm_reg_group rg, uint32_t *data); + +/* + * Write register value + * Writes the value of the register defined by \a regnum. + * @param reg_num the target register as defined in the ArmArchitecture + * @param rg Definition of register group of register defined by \a reg_num + * @param data data to be written + */ +void oocdw_write_reg(uint32_t reg_num, enum arm_reg_group rg, uint32_t data); + +/* + * Set a halt condition + * + * Halt conditions can be Break- and Watchpoints as well as single-steps + * @param hc Pointer to struct defining new halt condition + */ +void oocdw_set_halt_condition(struct halt_condition *hc); + +/* + * Remove a halt condition + * + * Halt conditions can be Break- and Watchpoints as well as single-steps + * @param hc Pointer to struct defining to be deleted halt condition + */ +void oocdw_delete_halt_condition(struct halt_condition *hc); + +/* + * Immediate target halt without sepcific target instruction + */ +void oocdw_halt_target(); + +/* + * Target reboot + * + * The target gets reset and will be navigated to a dynamic instruction + * right before main entry. Afterwards a new experiment can be executed. + */ +void oocdw_reboot(); + +/* + * Finish OpenOCD execution + * + * Function will be called by simulator.terminate() and will simply + * set a finish-flag. Afterwards a coroutine-switch must be called, so + * the actual finishing will be done in the OpenOCD-Wrapper coroutine. + * Before returning, another coroutine-switch is called, so the + * experiment is able to exit in desired state. + */ +void oocdw_finish(); + +/* + * Read data from pandaboard memory + * + * @param address Start address of memory region to be read + * @param chunk_size Size of chunks, which will be read + * @param chunk_num Number of chunks to be read + * @param data Pointer to read destination + */ +void oocdw_read_from_memory(uint32_t address, uint32_t chunk_size, + uint32_t chunk_num, uint8_t *data); + +/* + * Write data to pandaboard memory + * + * @param address Start address of memory region to be written + * @param chunk_size Size of chunks, which will be written + * @param chunk_num Number of chunks to be written + * @param data Pointer to read source + * @param cache_inval Defines if a write to memory should invalidate + * the associated cache line. + */ +void oocdw_write_to_memory(uint32_t address, uint32_t chunk_size, + uint32_t chunk_num, uint8_t const *data, + bool cache_inval); + +typedef void (*p_timer_handler_t)(void *); +/* + * Register new timer + * + * @param this_ptr This pointer for calling object method + * @param funct Callback to call if timer expired (object method) + * @param useconds Number of microseconds until timer expires + * @param active Sets if timer is initially active + * @param id String representation of timer + * @returns Timer index (ID) or -1 if no timer slot left + */ +int oocdw_register_timer(void *this_ptr, p_timer_handler_t funct, + uint64_t useconds, bool active, const char *id); + +/* + * Unregister timer + * + * @param timerID Timer index (ID), which defines the timer to be + * unregistered + */ +bool oocdw_unregisterTimer(unsigned timerID); + +/* + * Deactivate timer + * + * @param timerID Timer index (ID), which defines the timer to be + * deactivated. + */ +void oocdw_deactivate_timer(unsigned timer_index); + +#endif // __OOCD_WRAPPER_H_