From d6dae451821656d7676fec3ec515691d4c66e065 Mon Sep 17 00:00:00 2001 From: Tim Newsome Date: Mon, 22 Aug 2016 09:49:20 -0700 Subject: [PATCH] Implement address and data triggers. So far I only have testcases for instruction and data address. Not implemented is the mechanism that lets the debugger prevent a user program from using triggers at all. I'll be adding that soonish. The critical path is unchanged, but my experimenting shows the simulation is slowed down about 8% by this code. Reducing the size of trigger_match() (which is never called during my benchmark) fixes that, but making it not be inlined has no effect. I suspect the slowdown comes from cache alignment or something similar, and on a different CPU or after more code changes the speed will come back. --- riscv/encoding.h | 42 ++++++-- riscv/execute.cc | 34 +++++- riscv/gdbserver.cc | 262 ++++++++++++++++++++++++++++++++++++++------- riscv/gdbserver.h | 43 +++++++- riscv/insns/dret.h | 2 + riscv/mmu.cc | 20 +++- riscv/mmu.h | 55 ++++++++++ riscv/processor.cc | 85 ++++++++++++++- riscv/processor.h | 152 +++++++++++++++++++++++++- 9 files changed, 633 insertions(+), 62 deletions(-) diff --git a/riscv/encoding.h b/riscv/encoding.h index 641954a..e00972e 100644 --- a/riscv/encoding.h +++ b/riscv/encoding.h @@ -57,6 +57,32 @@ #define DCSR_CAUSE_STEP 4 #define DCSR_CAUSE_HALT 5 +#define MCONTROL_SELECT (1<<19) +#define MCONTROL_ACTION (0x7f<<12) +#define MCONTROL_CHAIN (1<<11) +#define MCONTROL_MATCH (0xf<<7) +#define MCONTROL_M (1<<6) +#define MCONTROL_H (1<<5) +#define MCONTROL_S (1<<4) +#define MCONTROL_U (1<<3) +#define MCONTROL_EXECUTE (1<<2) +#define MCONTROL_STORE (1<<1) +#define MCONTROL_LOAD (1<<0) + +#define MCONTROL_ACTION_NONE 0 +#define MCONTROL_ACTION_DEBUG_EXCEPTION 1 +#define MCONTROL_ACTION_DEBUG_MODE 2 +#define MCONTROL_ACTION_TRACE_START 3 +#define MCONTROL_ACTION_TRACE_STOP 4 +#define MCONTROL_ACTION_TRACE_EMIT 5 + +#define MCONTROL_MATCH_EQUAL 0 +#define MCONTROL_MATCH_NAPOT 1 +#define MCONTROL_MATCH_GE 2 +#define MCONTROL_MATCH_LT 3 +#define MCONTROL_MATCH_MASK_LOW 4 +#define MCONTROL_MATCH_MASK_HIGH 5 + #define MIP_SSIP (1 << IRQ_S_SOFT) #define MIP_HSIP (1 << IRQ_H_SOFT) #define MIP_MSIP (1 << IRQ_M_SOFT) @@ -690,10 +716,10 @@ #define CSR_MSCYCLE_DELTA 0x704 #define CSR_MSTIME_DELTA 0x705 #define CSR_MSINSTRET_DELTA 0x706 -#define CSR_TDRSELECT 0x7a0 -#define CSR_TDRDATA1 0x7a1 -#define CSR_TDRDATA2 0x7a2 -#define CSR_TDRDATA3 0x7a3 +#define CSR_TSELECT 0x7a0 +#define CSR_TDATA0 0x7a1 +#define CSR_TDATA1 0x7a2 +#define CSR_TDATA2 0x7a3 #define CSR_DCSR 0x7b0 #define CSR_DPC 0x7b1 #define CSR_DSCRATCH 0x7b2 @@ -999,10 +1025,10 @@ DECLARE_CSR(muinstret_delta, CSR_MUINSTRET_DELTA) DECLARE_CSR(mscycle_delta, CSR_MSCYCLE_DELTA) DECLARE_CSR(mstime_delta, CSR_MSTIME_DELTA) DECLARE_CSR(msinstret_delta, CSR_MSINSTRET_DELTA) -DECLARE_CSR(tdrselect, CSR_TDRSELECT) -DECLARE_CSR(tdrdata1, CSR_TDRDATA1) -DECLARE_CSR(tdrdata2, CSR_TDRDATA2) -DECLARE_CSR(tdrdata3, CSR_TDRDATA3) +DECLARE_CSR(tselect, CSR_TSELECT) +DECLARE_CSR(tdata0, CSR_TDATA0) +DECLARE_CSR(tdata1, CSR_TDATA1) +DECLARE_CSR(tdata2, CSR_TDATA2) DECLARE_CSR(dcsr, CSR_DCSR) DECLARE_CSR(dpc, CSR_DPC) DECLARE_CSR(dscratch, CSR_DSCRATCH) diff --git a/riscv/execute.cc b/riscv/execute.cc index 20567af..3d5625f 100644 --- a/riscv/execute.cc +++ b/riscv/execute.cc @@ -51,6 +51,13 @@ static reg_t execute_insn(processor_t* p, reg_t pc, insn_fetch_t fetch) return npc; } +void processor_t::update_slow_path() +{ + slow_path = debug || state.single_step != state.STEP_NONE || state.dcsr.cause; + if (slow_path) + return; +} + // fetch/decode/execute loop void processor_t::step(size_t n) { @@ -90,8 +97,7 @@ void processor_t::step(size_t n) { take_interrupt(); - // When we might single step, use the slow loop instead of the fast one. - if (unlikely(debug || state.single_step != state.STEP_NONE || state.dcsr.cause)) + if (unlikely(slow_path)) { while (instret < n) { @@ -150,6 +156,30 @@ miss: take_trap(t, pc); n = instret; } + catch (trigger_matched_t& t) + { + if (mmu->matched_trigger) { + // This exception came from the MMU. That means the instruction hasn't + // fully executed yet. We start it again, but this time it won't throw + // an exception because matched_trigger is already set. (All memory + // instructions are idempotent so restarting is safe.) + + insn_fetch_t fetch = mmu->load_insn(pc); + pc = execute_insn(this, pc, fetch); + advance_pc(); + + delete mmu->matched_trigger; + mmu->matched_trigger = NULL; + } + assert(state.mcontrol[t.index].action != ACTION_NONE); + switch (state.mcontrol[t.index].action) { + case ACTION_DEBUG_MODE: + enter_debug_mode(DCSR_CAUSE_HWBP); + break; + default: + assert(0); + } + } state.minstret += instret; n -= instret; diff --git a/riscv/gdbserver.cc b/riscv/gdbserver.cc index c561390..9774b30 100644 --- a/riscv/gdbserver.cc +++ b/riscv/gdbserver.cc @@ -1036,6 +1036,129 @@ class collect_translation_info_op_t : public operation_t reg_t pte_addr; }; +class hardware_breakpoint_insert_op_t : public operation_t +{ + public: + hardware_breakpoint_insert_op_t(gdbserver_t& gdbserver, + hardware_breakpoint_t bp) : + operation_t(gdbserver), state(STATE_START), bp(bp) {}; + + void write_new_index_program() + { + gs.dr_write_load(0, S0, SLOT_DATA1); + gs.dr_write32(1, csrw(S0, CSR_TSELECT)); + gs.dr_write32(2, csrr(S0, CSR_TSELECT)); + gs.dr_write_store(3, S0, SLOT_DATA1); + gs.dr_write_jump(4); + gs.dr_write(SLOT_DATA1, bp.index); + } + + bool perform_step(unsigned int step) + { + switch (state) { + case STATE_START: + bp.index = 0; + write_new_index_program(); + state = STATE_CHECK_INDEX; + break; + + case STATE_CHECK_INDEX: + if (gs.dr_read(SLOT_DATA1) != bp.index) { + // We've exhausted breakpoints without finding an appropriate one. + gs.send_packet("E58"); + return true; + } + + gs.dr_write32(0, csrr(S0, CSR_TDATA0)); + gs.dr_write_store(1, S0, SLOT_DATA0); + gs.dr_write_jump(2); + state = STATE_CHECK_MCONTROL; + break; + + case STATE_CHECK_MCONTROL: + { + reg_t mcontrol = gs.dr_read(SLOT_DATA0); + unsigned int type = mcontrol >> (gs.xlen - 4); + if (type == 0) { + // We've exhausted breakpoints without finding an appropriate one. + gs.send_packet("E58"); + return true; + } + + if (type == 2 && + get_field(mcontrol, MCONTROL_ACTION) == MCONTROL_ACTION_NONE) { + // Found an unused trigger. + gs.dr_write_load(0, S0, SLOT_DATA1); + gs.dr_write32(1, csrw(S0, CSR_TDATA0)); + gs.dr_write_jump(2); + mcontrol = set_field(0, MCONTROL_ACTION, MCONTROL_ACTION_DEBUG_MODE); + mcontrol = set_field(mcontrol, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL); + mcontrol = set_field(mcontrol, MCONTROL_M, 1); + mcontrol = set_field(mcontrol, MCONTROL_H, 1); + mcontrol = set_field(mcontrol, MCONTROL_S, 1); + mcontrol = set_field(mcontrol, MCONTROL_U, 1); + mcontrol = set_field(mcontrol, MCONTROL_EXECUTE, bp.execute); + mcontrol = set_field(mcontrol, MCONTROL_LOAD, bp.load); + mcontrol = set_field(mcontrol, MCONTROL_STORE, bp.store); + gs.dr_write(SLOT_DATA1, mcontrol); + state = STATE_WRITE_ADDRESS; + } else { + bp.index++; + write_new_index_program(); + state = STATE_CHECK_INDEX; + } + } + break; + + case STATE_WRITE_ADDRESS: + { + gs.dr_write_load(0, S0, SLOT_DATA1); + gs.dr_write32(1, csrw(S0, CSR_TDATA1)); + gs.dr_write_jump(2); + gs.dr_write(SLOT_DATA1, bp.vaddr); + gs.set_interrupt(0); + gs.send_packet("OK"); + + gs.hardware_breakpoints.insert(bp); + + return true; + } + } + + gs.set_interrupt(0); + return false; + } + + private: + enum { + STATE_START, + STATE_CHECK_INDEX, + STATE_CHECK_MCONTROL, + STATE_WRITE_ADDRESS + } state; + hardware_breakpoint_t bp; +}; + +class hardware_breakpoint_remove_op_t : public operation_t +{ + public: + hardware_breakpoint_remove_op_t(gdbserver_t& gdbserver, + hardware_breakpoint_t bp) : + operation_t(gdbserver), bp(bp) {}; + + bool perform_step(unsigned int step) { + gs.dr_write32(0, addi(S0, ZERO, bp.index)); + gs.dr_write32(1, csrw(S0, CSR_TSELECT)); + gs.dr_write32(2, csrw(ZERO, CSR_TDATA0)); + gs.dr_write_jump(3); + gs.set_interrupt(0); + return true; + } + + private: + hardware_breakpoint_t bp; +}; + ////////////////////////////// gdbserver itself gdbserver_t::gdbserver_t(uint16_t port, sim_t *sim) : @@ -1641,64 +1764,121 @@ void gdbserver_t::handle_extended(const std::vector &packet) extended_mode = true; } +void gdbserver_t::software_breakpoint_insert(reg_t vaddr, unsigned int size) +{ + fence_i_required = true; + add_operation(new collect_translation_info_op_t(*this, vaddr, size)); + unsigned char* inst = new unsigned char[4]; + if (size == 2) { + inst[0] = C_EBREAK & 0xff; + inst[1] = (C_EBREAK >> 8) & 0xff; + } else { + inst[0] = EBREAK & 0xff; + inst[1] = (EBREAK >> 8) & 0xff; + inst[2] = (EBREAK >> 16) & 0xff; + inst[3] = (EBREAK >> 24) & 0xff; + } + + software_breakpoint_t bp = { + .vaddr = vaddr, + .size = size + }; + software_breakpoints[vaddr] = bp; + add_operation(new memory_read_op_t(*this, bp.vaddr, bp.size, + software_breakpoints[bp.vaddr].instruction)); + add_operation(new memory_write_op_t(*this, bp.vaddr, bp.size, inst)); +} + +void gdbserver_t::software_breakpoint_remove(reg_t vaddr, unsigned int size) +{ + fence_i_required = true; + add_operation(new collect_translation_info_op_t(*this, vaddr, size)); + + software_breakpoint_t found_bp = software_breakpoints[vaddr]; + unsigned char* instruction = new unsigned char[4]; + memcpy(instruction, found_bp.instruction, 4); + add_operation(new memory_write_op_t(*this, found_bp.vaddr, + found_bp.size, instruction)); + software_breakpoints.erase(vaddr); +} + +void gdbserver_t::hardware_breakpoint_insert(const hardware_breakpoint_t &bp) +{ + add_operation(new hardware_breakpoint_insert_op_t(*this, bp)); +} + +void gdbserver_t::hardware_breakpoint_remove(const hardware_breakpoint_t &bp) +{ + hardware_breakpoint_t found = *hardware_breakpoints.find(bp); + add_operation(new hardware_breakpoint_remove_op_t(*this, found)); +} + void gdbserver_t::handle_breakpoint(const std::vector &packet) { - // insert: Z type,addr,kind - // remove: z type,addr,kind + // insert: Z type,addr,length + // remove: z type,addr,length + + // type: 0 - software breakpoint, 1 - hardware breakpoint, 2 - write + // watchpoint, 3 - read watchpoint, 4 - access watchpoint; addr is address; + // length is in bytes. For a software breakpoint, length specifies the size + // of the instruction to be patched. For hardware breakpoints and watchpoints + // length specifies the memory region to be monitored. To avoid potential + // problems with duplicate packets, the operations should be implemented in + // an idempotent way. - software_breakpoint_t bp; bool insert = (packet[1] == 'Z'); std::vector::const_iterator iter = packet.begin() + 2; - int type = consume_hex_number(iter, packet.end()); + gdb_breakpoint_type_t type = static_cast( + consume_hex_number(iter, packet.end())); if (*iter != ',') return send_packet("E50"); iter++; - bp.address = consume_hex_number(iter, packet.end()); + reg_t address = consume_hex_number(iter, packet.end()); if (*iter != ',') return send_packet("E51"); iter++; - bp.size = consume_hex_number(iter, packet.end()); + unsigned int size = consume_hex_number(iter, packet.end()); // There may be more options after a ; here, but we don't support that. if (*iter != '#') return send_packet("E52"); - if (type != 0) { - // Only software breakpoints are supported. - return send_packet(""); - } - - if (bp.size != 2 && bp.size != 4) { - return send_packet("E53"); - } - - fence_i_required = true; - add_operation(new collect_translation_info_op_t(*this, bp.address, bp.size)); - if (insert) { - unsigned char* swbp = new unsigned char[4]; - if (bp.size == 2) { - swbp[0] = C_EBREAK & 0xff; - swbp[1] = (C_EBREAK >> 8) & 0xff; - } else { - swbp[0] = EBREAK & 0xff; - swbp[1] = (EBREAK >> 8) & 0xff; - swbp[2] = (EBREAK >> 16) & 0xff; - swbp[3] = (EBREAK >> 24) & 0xff; - } + switch (type) { + case GB_SOFTWARE: + if (size != 2 && size != 4) { + return send_packet("E53"); + } + if (insert) { + software_breakpoint_insert(address, size); + } else { + software_breakpoint_remove(address, size); + } + break; - breakpoints[bp.address] = new software_breakpoint_t(bp); - add_operation(new memory_read_op_t(*this, bp.address, bp.size, - breakpoints[bp.address]->instruction)); - add_operation(new memory_write_op_t(*this, bp.address, bp.size, swbp)); + case GB_HARDWARE: + case GB_WRITE: + case GB_READ: + case GB_ACCESS: + { + hardware_breakpoint_t bp = { + .vaddr = address, + .size = size + }; + bp.load = (type == GB_READ || type == GB_ACCESS); + bp.store = (type == GB_WRITE || type == GB_ACCESS); + bp.execute = (type == GB_HARDWARE || type == GB_ACCESS); + if (insert) { + hardware_breakpoint_insert(bp); + // Insert might fail if there's no space, so the insert operation will + // send its own OK (or not). + return; + } else { + hardware_breakpoint_remove(bp); + } + } + break; - } else { - software_breakpoint_t *found_bp; - found_bp = breakpoints[bp.address]; - unsigned char* instruction = new unsigned char[4]; - memcpy(instruction, found_bp->instruction, 4); - add_operation(new memory_write_op_t(*this, found_bp->address, - found_bp->size, instruction)); - breakpoints.erase(bp.address); - delete found_bp; + default: + return send_packet("E56"); } return send_packet("OK"); diff --git a/riscv/gdbserver.h b/riscv/gdbserver.h index f0e7fca..1e3966c 100644 --- a/riscv/gdbserver.h +++ b/riscv/gdbserver.h @@ -48,10 +48,27 @@ public: // Class to track software breakpoints that we set. class software_breakpoint_t { -public: - reg_t address; - unsigned int size; - unsigned char instruction[4]; + public: + reg_t vaddr; + unsigned int size; + unsigned char instruction[4]; +}; + +class hardware_breakpoint_t +{ + public: + reg_t vaddr; + unsigned int size; + unsigned int index; + bool load, store, execute; +}; + +struct hardware_breakpoint_compare_t { + bool operator()(const hardware_breakpoint_t& a, const hardware_breakpoint_t& b) const { + if (a.vaddr != b.vaddr) + return a.vaddr < b.vaddr; + return a.size < b.size; + } }; class gdbserver_t; @@ -111,6 +128,14 @@ static const unsigned int slot_offset32[] = {0, 4, 5, DEBUG_RAM_SIZE/4 - 1}; static const unsigned int slot_offset64[] = {0, 4, 6, DEBUG_RAM_SIZE/4 - 2}; static const unsigned int slot_offset128[] = {0, 4, 8, DEBUG_RAM_SIZE/4 - 4}; +typedef enum { + GB_SOFTWARE = 0, + GB_HARDWARE = 1, + GB_WRITE = 2, + GB_READ = 3, + GB_ACCESS = 4, +} gdb_breakpoint_type_t; + class gdbserver_t { public: @@ -124,6 +149,11 @@ public: void handle_packet(const std::vector &packet); void handle_interrupt(); + void software_breakpoint_remove(reg_t vaddr, unsigned int size); + void software_breakpoint_insert(reg_t vaddr, unsigned int size); + void hardware_breakpoint_remove(const hardware_breakpoint_t &bp); + void hardware_breakpoint_insert(const hardware_breakpoint_t &bp); + void handle_breakpoint(const std::vector &packet); void handle_continue(const std::vector &packet); void handle_extended(const std::vector &packet); @@ -199,6 +229,9 @@ public: unsigned int xlen; + std::set + hardware_breakpoints; + private: sim_t *sim; int socket_fd; @@ -212,7 +245,7 @@ private: // but it isn't, we need to tell gdb about it. bool running; - std::map breakpoints; + std::map software_breakpoints; // Read pending data from the client. void read(); diff --git a/riscv/insns/dret.h b/riscv/insns/dret.h index 35c19cb..51eaa17 100644 --- a/riscv/insns/dret.h +++ b/riscv/insns/dret.h @@ -7,3 +7,5 @@ STATE.dcsr.cause = 0; if (STATE.dcsr.step) STATE.single_step = STATE.STEP_STEPPING; + +p->update_slow_path(); diff --git a/riscv/mmu.cc b/riscv/mmu.cc index 4b7166f..0b60713 100644 --- a/riscv/mmu.cc +++ b/riscv/mmu.cc @@ -5,7 +5,11 @@ #include "processor.h" mmu_t::mmu_t(sim_t* sim, processor_t* proc) - : sim(sim), proc(proc) + : sim(sim), proc(proc), + check_triggers_fetch(false), + check_triggers_load(false), + check_triggers_store(false), + matched_trigger(NULL) { flush_tlb(); } @@ -102,9 +106,17 @@ void mmu_t::refill_tlb(reg_t vaddr, reg_t paddr, access_type type) reg_t idx = (vaddr >> PGSHIFT) % TLB_ENTRIES; reg_t expected_tag = vaddr >> PGSHIFT; - if (tlb_load_tag[idx] != expected_tag) tlb_load_tag[idx] = -1; - if (tlb_store_tag[idx] != expected_tag) tlb_store_tag[idx] = -1; - if (tlb_insn_tag[idx] != expected_tag) tlb_insn_tag[idx] = -1; + if ((tlb_load_tag[idx] & ~TLB_CHECK_TRIGGERS) != expected_tag) + tlb_load_tag[idx] = -1; + if ((tlb_store_tag[idx] & ~TLB_CHECK_TRIGGERS) != expected_tag) + tlb_store_tag[idx] = -1; + if ((tlb_insn_tag[idx] & ~TLB_CHECK_TRIGGERS) != expected_tag) + tlb_insn_tag[idx] = -1; + + if ((check_triggers_fetch && type == FETCH) || + (check_triggers_load && type == LOAD) || + (check_triggers_store && type == STORE)) + expected_tag |= TLB_CHECK_TRIGGERS; if (type == FETCH) tlb_insn_tag[idx] = expected_tag; else if (type == STORE) tlb_store_tag[idx] = expected_tag; diff --git a/riscv/mmu.h b/riscv/mmu.h index 0652d47..614ffc4 100644 --- a/riscv/mmu.h +++ b/riscv/mmu.h @@ -30,6 +30,19 @@ struct icache_entry_t { insn_fetch_t data; }; +class trigger_matched_t +{ + public: + trigger_matched_t(int index, + trigger_operation_t operation, reg_t address, reg_t data) : + index(index), operation(operation), address(address), data(data) {} + + int index; + trigger_operation_t operation; + reg_t address; + reg_t data; +}; + // this class implements a processor's port into the virtual memory system. // an MMU and instruction cache are maintained for simulator performance. class mmu_t @@ -46,6 +59,15 @@ public: reg_t vpn = addr >> PGSHIFT; \ if (likely(tlb_load_tag[vpn % TLB_ENTRIES] == vpn)) \ return *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); \ + if (unlikely(tlb_load_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { \ + type##_t data = *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); \ + if (!matched_trigger) { \ + matched_trigger = trigger_exception(OPERATION_LOAD, addr, data); \ + if (matched_trigger) \ + throw *matched_trigger; \ + } \ + return data; \ + } \ type##_t res; \ load_slow_path(addr, sizeof(type##_t), (uint8_t*)&res); \ return res; \ @@ -71,6 +93,14 @@ public: reg_t vpn = addr >> PGSHIFT; \ if (likely(tlb_store_tag[vpn % TLB_ENTRIES] == vpn)) \ *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr) = val; \ + else if (unlikely(tlb_store_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { \ + if (!matched_trigger) { \ + matched_trigger = trigger_exception(OPERATION_STORE, addr, val); \ + if (matched_trigger) \ + throw *matched_trigger; \ + } \ + *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr) = val; \ + } \ else \ store_slow_path(addr, sizeof(type##_t), (const uint8_t*)&val); \ } @@ -150,6 +180,9 @@ private: // implement a TLB for simulator performance static const reg_t TLB_ENTRIES = 256; + // If a TLB tag has TLB_CHECK_TRIGGERS set, then the MMU must check for a + // trigger match before completing an access. + static const reg_t TLB_CHECK_TRIGGERS = 1L<<63; char* tlb_data[TLB_ENTRIES]; reg_t tlb_insn_tag[TLB_ENTRIES]; reg_t tlb_load_tag[TLB_ENTRIES]; @@ -173,9 +206,31 @@ private: reg_t vpn = addr >> PGSHIFT; if (likely(tlb_insn_tag[vpn % TLB_ENTRIES] == vpn)) return (uint16_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); + if (unlikely(tlb_insn_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { + uint16_t* ptr = (uint16_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); + int match = proc->trigger_match(OPERATION_EXECUTE, addr, *ptr); + if (match >= 0) + throw trigger_matched_t(match, OPERATION_EXECUTE, addr, *ptr); + return ptr; + } return fetch_slow_path(addr); } + inline trigger_matched_t *trigger_exception(trigger_operation_t operation, + reg_t address, reg_t data) + { + int match = proc->trigger_match(operation, address, data); + if (match == -1) + return NULL; + return new trigger_matched_t(match, operation, address, data); + } + + bool check_triggers_fetch; + bool check_triggers_load; + bool check_triggers_store; + // The exception describing a matched trigger, or NULL. + trigger_matched_t *matched_trigger; + friend class processor_t; }; diff --git a/riscv/processor.cc b/riscv/processor.cc index 0a7912b..a9be334 100644 --- a/riscv/processor.cc +++ b/riscv/processor.cc @@ -118,6 +118,12 @@ void state_t::reset() pc = DEFAULT_RSTVEC; mtvec = DEFAULT_MTVEC; load_reservation = -1; + tselect = 0; + for (unsigned int i = 0; i < num_triggers; i++) { + mcontrol[i].type = 2; + mcontrol[i].action = ACTION_NONE; + tdata1[i] = 0; + } } void processor_t::set_debug(bool value) @@ -154,6 +160,7 @@ void processor_t::raise_interrupt(reg_t which) throw trap_t(((reg_t)1 << (max_xlen-1)) | which); } +// Count number of contiguous 0 bits starting from the LSB. static int ctz(reg_t val) { int res = 0; @@ -196,6 +203,7 @@ void processor_t::enter_debug_mode(uint8_t cause) state.dpc = state.pc; state.pc = DEBUG_ROM_START; //debug = true; // TODO + update_slow_path(); } void processor_t::take_trap(trap_t& t, reg_t epc) @@ -378,6 +386,30 @@ void processor_t::set_csr(int which, reg_t val) case CSR_MSCRATCH: state.mscratch = val; break; case CSR_MCAUSE: state.mcause = val; break; case CSR_MBADADDR: state.mbadaddr = val; break; + case CSR_TSELECT: state.tselect = val; break; + case CSR_TDATA0: + if (state.tselect < state.num_triggers) { + mcontrol_t *mc = &state.mcontrol[state.tselect]; + mc->select = get_field(val, MCONTROL_SELECT); + mc->action = (mcontrol_action_t) get_field(val, MCONTROL_ACTION); + mc->chain = get_field(val, MCONTROL_CHAIN); + mc->match = (mcontrol_match_t) get_field(val, MCONTROL_MATCH); + mc->m = get_field(val, MCONTROL_M); + mc->h = get_field(val, MCONTROL_H); + mc->s = get_field(val, MCONTROL_S); + mc->u = get_field(val, MCONTROL_U); + mc->execute = get_field(val, MCONTROL_EXECUTE); + mc->store = get_field(val, MCONTROL_STORE); + mc->load = get_field(val, MCONTROL_LOAD); + // Assume we're here because of csrw. + trigger_updated(); + } + break; + case CSR_TDATA1: + if (state.tselect < state.num_triggers) { + state.tdata1[state.tselect] = val; + } + break; case CSR_DCSR: state.dcsr.prv = get_field(val, DCSR_PRV); state.dcsr.step = get_field(val, DCSR_STEP); @@ -481,7 +513,36 @@ reg_t processor_t::get_csr(int which) case CSR_MTVEC: return state.mtvec; case CSR_MEDELEG: return state.medeleg; case CSR_MIDELEG: return state.mideleg; - case CSR_TDRSELECT: return 0; + case CSR_TSELECT: return state.tselect; + case CSR_TDATA0: + if (state.tselect < state.num_triggers) { + reg_t v = 0; + mcontrol_t *mc = &state.mcontrol[state.tselect]; + v = set_field(v, 0xfL << (xlen-4), mc->type); + v = set_field(v, 0x3fL << (xlen-10), mc->maskmax); + v = set_field(v, MCONTROL_SELECT, mc->select); + v = set_field(v, MCONTROL_ACTION, mc->action); + v = set_field(v, MCONTROL_CHAIN, mc->chain); + v = set_field(v, MCONTROL_MATCH, mc->match); + v = set_field(v, MCONTROL_M, mc->m); + v = set_field(v, MCONTROL_H, mc->h); + v = set_field(v, MCONTROL_S, mc->s); + v = set_field(v, MCONTROL_U, mc->u); + v = set_field(v, MCONTROL_EXECUTE, mc->execute); + v = set_field(v, MCONTROL_STORE, mc->store); + v = set_field(v, MCONTROL_LOAD, mc->load); + return v; + } else { + return 0; + } + break; + case CSR_TDATA1: + if (state.tselect < state.num_triggers) { + return state.tdata1[state.tselect]; + } else { + return 0; + } + break; case CSR_DCSR: { uint32_t v = 0; @@ -612,3 +673,25 @@ bool processor_t::store(reg_t addr, size_t len, const uint8_t* bytes) return false; } } + +void processor_t::trigger_updated() +{ + mmu->flush_tlb(); + mmu->check_triggers_fetch = false; + mmu->check_triggers_load = false; + mmu->check_triggers_store = false; + + for (unsigned i = 0; i < state.num_triggers; i++) { + if (state.mcontrol[i].action == ACTION_NONE) + continue; + if (state.mcontrol[i].execute) { + mmu->check_triggers_fetch = true; + } + if (state.mcontrol[i].load) { + mmu->check_triggers_load = true; + } + if (state.mcontrol[i].store) { + mmu->check_triggers_store = true; + } + } +} diff --git a/riscv/processor.h b/riscv/processor.h index 090ebe7..ab3af22 100644 --- a/riscv/processor.h +++ b/riscv/processor.h @@ -43,17 +43,56 @@ typedef struct uint8_t cause; } dcsr_t; +typedef enum +{ + ACTION_NONE = MCONTROL_ACTION_NONE, + ACTION_DEBUG_EXCEPTION = MCONTROL_ACTION_DEBUG_EXCEPTION, + ACTION_DEBUG_MODE = MCONTROL_ACTION_DEBUG_MODE, + ACTION_TRACE_START = MCONTROL_ACTION_TRACE_START, + ACTION_TRACE_STOP = MCONTROL_ACTION_TRACE_STOP, + ACTION_TRACE_EMIT = MCONTROL_ACTION_TRACE_EMIT +} mcontrol_action_t; + +typedef enum +{ + MATCH_EQUAL = MCONTROL_MATCH_EQUAL, + MATCH_NAPOT = MCONTROL_MATCH_NAPOT, + MATCH_GE = MCONTROL_MATCH_GE, + MATCH_LT = MCONTROL_MATCH_LT, + MATCH_MASK_LOW = MCONTROL_MATCH_MASK_LOW, + MATCH_MASK_HIGH = MCONTROL_MATCH_MASK_HIGH +} mcontrol_match_t; + +typedef struct +{ + uint8_t type; + uint8_t maskmax; + bool select; + mcontrol_action_t action; + bool chain; + mcontrol_match_t match; + bool m; + bool h; + bool s; + bool u; + bool execute; + bool store; + bool load; +} mcontrol_t; + // architectural state of a RISC-V hart struct state_t { void reset(); + static const int num_triggers = 4; + reg_t pc; regfile_t XPR; regfile_t FPR; // control and status registers - reg_t prv; + reg_t prv; // TODO: Can this be an enum instead? reg_t mstatus; reg_t mepc; reg_t mbadaddr; @@ -76,6 +115,9 @@ struct state_t reg_t dpc; reg_t dscratch; dcsr_t dcsr; + reg_t tselect; + mcontrol_t mcontrol[num_triggers]; + reg_t tdata1[num_triggers]; uint32_t fflags; uint32_t frm; @@ -97,6 +139,21 @@ struct state_t #endif }; +typedef enum { + OPERATION_EXECUTE, + OPERATION_STORE, + OPERATION_LOAD, +} trigger_operation_t; + +// Count number of contiguous 1 bits starting from the LSB. +static int cto(reg_t val) +{ + int res = 0; + while ((val & 1) == 1) + val >>= 1, res++; + return res; +} + // this class represents one processor in a RISC-V machine. class processor_t : public abstract_device_t { @@ -132,6 +189,97 @@ public: // When true, display disassembly of each instruction that's executed. bool debug; + void update_slow_path(); + + // Return the index of a trigger that matched, or -1. + inline int trigger_match(trigger_operation_t operation, reg_t address, reg_t data) + { + if (state.dcsr.cause) + return -1; + + bool chain_ok = false; + + for (unsigned int i = 0; i < state.num_triggers; i++) { + if (state.mcontrol[i].action == ACTION_NONE || + (operation == OPERATION_EXECUTE && !state.mcontrol[i].execute) || + (operation == OPERATION_STORE && !state.mcontrol[i].store) || + (operation == OPERATION_LOAD && !state.mcontrol[i].load) || + (state.prv == PRV_M && !state.mcontrol[i].m) || + (state.prv == PRV_H && !state.mcontrol[i].h) || + (state.prv == PRV_S && !state.mcontrol[i].s) || + (state.prv == PRV_U && !state.mcontrol[i].u)) { + goto next; + } + + reg_t value; + if (state.mcontrol[i].select) { + value = data; + } else { + value = address; + } + + // We need this because in 32-bit mode sometimes the PC bits get sign + // extended. + if (xlen == 32) { + value &= 0xffffffff; + } + + switch (state.mcontrol[i].match) { + case MATCH_EQUAL: + if (value != state.tdata1[i]) + goto next; + break; + case MATCH_NAPOT: + { + reg_t mask = ~((1 << cto(state.tdata1[i])) - 1); + if ((value & mask) != (state.tdata1[i] & mask)) + goto next; + } + break; + case MATCH_GE: + if (value < state.tdata1[i]) + goto next; + break; + case MATCH_LT: + if (value >= state.tdata1[i]) + goto next; + break; + case MATCH_MASK_LOW: + { + reg_t mask = state.tdata1[i] >> (xlen/2); + if ((value & mask) != (state.tdata1[i] & mask)) + goto next; + } + break; + case MATCH_MASK_HIGH: + { + reg_t mask = state.tdata1[i] >> (xlen/2); + if (((value >> (xlen/2)) & mask) != (state.tdata1[i] & mask)) + goto next; + } + break; + } + + if (state.mcontrol[i].chain && !chain_ok) { + goto next; + } + + // We got here, so this trigger matches. But if the next trigger has + // chain set, then we can't perform the action. + if (i+1 < state.num_triggers && state.mcontrol[i+1].chain) { + chain_ok = true; + continue; + } else { + return i; + } + +next: + chain_ok = false; + } + return -1; + } + + void trigger_updated(); private: sim_t* sim; @@ -146,6 +294,8 @@ private: std::string isa_string; bool histogram_enabled; bool halt_on_reset; + // When true, take the slow simulation path. + bool slow_path; std::vector instructions; std::map pc_histogram; -- 2.30.2