RISC-V Debug Module

Table of Contents

1. RISC-V Debug Module

https://github.com/riscv/riscv-debug-spec/blob/master/riscv-debug-stable.pdf

debug module (DM) spec 描述的是 riscv 如何支持 jtag.

spike 的相关代码涉及到几个不同的组件:

  1. remote bitbang, 用来与 openocd 通信
  2. dtm (debug transport module), 解析 jtag 数据
  3. dmi (debug module interface), dtm 解析出 jtag dr (data register) 后会转换为相应的 dmi 调用 (dmi_read, dmi_write)
  4. dm (debug module), 实现 dmi, 控制 hart 执行 debug 动作

1.1. overview

以一个简单的读寄存器的操作为例, 说明 dm 的过程:

  1. guest 每次 step 之后会通过 remote_bitbang 的 tick 函数看是否有 jtag 命令
  2. jtag_dtm 解析 remote_bitbang 收到的 jtag 命令. jtag 只有四根信号线, dtm 会用一个状态机来解析数据, 得到 jtag dr (data register) 数据
  3. dtm 解析到一个 dr, op 为 DMI_OP_WRITE, address 为 DM_DMCONTROL, value 为 DM_DMCONTROL_HALTREQ, 导致 processor step 时通过 enter_debug_mode 进行 debug mode, pc 跳转到 DEBUG_ROM_ENTRY
  4. jtag_dtm 解析另一个 dr, op 为 DMI_OP_WRITE, address 为 DM_COMMAND, value 为要执行的 command
  5. dm 通过 perform_abstract_command 执行这个 command, 发现它是 register access
  6. dm 执行 `读 x0` 的 abstract command 的方法是把 `sd x0, debug_data_start(zero)`指令写到 debug_abstract_start 处, debug mode 会执行到这段代码, 把 x0 的值写到了debug_data_start.
  7. dtm 解析另一个 dr, op 为 DMI_OP_READ, 且 address 为 DM_DATA0, 其中 DM_DATA0 对应 debug_data_start. dm 从 debug_data_start 读出数据, 放到 dr, 最后通过 jtag_dtm 的状态机把数据返回给 remote_bitbang
  8. jtag_dtm 解析到另一个 dr, DMI_OP_WRITE(DM_DMCONTROL, DM_DMCONTROL_RESUMEREQ), 表示要 resume, guest 会通过 debug_rom 中的 dret 指令退出 debug mode

整个过程的核心是:

  1. dtm 把 jtag dr 转换为 dmi_read/dmi_write
  2. dmi_write
    1. dm 执行 dmi_write(command) 时会把 command 对应的指令写入 debug_abstract
    2. guest 进入 debug mode 后会执行 debug_abstract 的代码, 结果写到 data0…
  3. dmi_read
    1. dm 响应 dmi_read(data0…), 返回 command 的结果

上面的例子是基于 abstract command 的做法, 实际上 dm 还支持 progbuf 的方式:

jtag 通过 dr 向 DM_PROGBUF0 直接写入代码, 从 DM_DATA0 读到结果.

1.2. remote bitbang

void sim_t::main():
  while (!done()):
    step(INTERLEAVE);
    if (remote_bitbang):
        remote_bitbang->tick();

void remote_bitbang_t::tick():
  if (client_fd > 0):
    /* 已经有 openocd 连接 */
    execute_commands();
  else:
    /* 非阻塞式的等待 openocd */
    this->accept();

void remote_bitbang_t::execute_commands():
  while (1):
    uint8_t command = recv_buf[recv_start];
    // jtag 端的 remote bitbang 驱动把把命令编码成一系列的 01234..., 对应不同
    // pin 上的信号, 一个简单的命令需要编码成许多 set_pins 命令, 用来表示一系列
    // 的状态变化, 例如:
    // 状态1: 准备开始传递一个 dr;
    // 状态2: dr 的 bit ++ 是 x;
    // 状态2: dr 的 bit ++ 是 y;
    // 状态2: dr 的 bit ++ 是 z;
    // ...
    // 状态3: dr 传递完毕, 开始执行...
    switch (command):
      case 'B': /* fprintf(stderr, "*BLINK*\n"); */ break;
      case 'b': /* fprintf(stderr, "_______\n"); */ break;
      case 'r': tap->reset(); break;
      case '0': tap->set_pins(0, 0, 0); break;
      case '1': tap->set_pins(0, 0, 1); break;
      case '2': tap->set_pins(0, 1, 0); break;
      case '3': tap->set_pins(0, 1, 1); break;
      case '4': tap->set_pins(1, 0, 0); break;
      case '5': tap->set_pins(1, 0, 1); break;
      case '6': tap->set_pins(1, 1, 0); break;
      case '7': tap->set_pins(1, 1, 1); break;
      // tdo 表示 data out, 与 tdi 一样, 它一次只能表示一个 bit, 为了读出一个
      // dr,需要用多个 set_pins(用来对 dr 移位后得到一个 tdo) 和 tdo() 调用
      case 'R': send_buf[send_offset++] = tap->tdo() ? '1' : '0'; break;
      case 'Q': quit = true; break;
      default:

    unsigned sent = 0;
    while (sent < send_offset):
      ssize_t bytes = write(client_fd, send_buf + sent, send_offset);
      sent += bytes;

    recv_end = read(client_fd, recv_buf, buf_size);

其中 set_pins 部分对应的 openocd 端的 remote_bigbang driver 的代码:

static int remote_bitbang_write(int tck, int tms, int tdi) {
    char c = '0' + ((tck ? 0x4 : 0x0) | (tms ? 0x2 : 0x0) | (tdi ? 0x1 : 0x0));
    return remote_bitbang_queue(c, NO_FLUSH);
}

1.3. jtag_dtm

jtag_dtm 会把前面的 set_pins 等转换成 dmi_read, dmi_write 等, 其核心的逻辑是处理 set_pins 表示的状态机, 用来读取和发送 dr

void jtag_dtm_t::set_pins(bool tck, bool tms, bool tdi) {
    /* NOTE: set_pins 是一个状态机, 一个简单的通过它读取 dr 时状态的变化:
     * TEST_LOGIN_RESET -> RUN_TEST_IDLE -> SELECT_DR_SCAN -> CAPTURE_DR ->
     * SHIFT_DR -> SHIFT_DR -> SHIFT_DR -> ... -> EXIT1_DR -> UPDATE_DR ->
     * RUN_TEST_IDLE */
    const jtag_state_t next[16][2] = {
        /* TEST_LOGIC_RESET */ {RUN_TEST_IDLE, TEST_LOGIC_RESET},
        /* RUN_TEST_IDLE */ {RUN_TEST_IDLE, SELECT_DR_SCAN},
        /* SELECT_DR_SCAN */ {CAPTURE_DR, SELECT_IR_SCAN},
        /* CAPTURE_DR */ {SHIFT_DR, EXIT1_DR},
        /* SHIFT_DR */ {SHIFT_DR, EXIT1_DR},
        /* EXIT1_DR */ {PAUSE_DR, UPDATE_DR},
        /* PAUSE_DR */ {PAUSE_DR, EXIT2_DR},
        /* EXIT2_DR */ {SHIFT_DR, UPDATE_DR},
        /* UPDATE_DR */ {RUN_TEST_IDLE, SELECT_DR_SCAN},
        /* SELECT_IR_SCAN */ {CAPTURE_IR, TEST_LOGIC_RESET},
        /* CAPTURE_IR */ {SHIFT_IR, EXIT1_IR},
        /* SHIFT_IR */ {SHIFT_IR, EXIT1_IR},
        /* EXIT1_IR */ {PAUSE_IR, UPDATE_IR},
        /* PAUSE_IR */ {PAUSE_IR, EXIT2_IR},
        /* EXIT2_IR */ {SHIFT_IR, UPDATE_IR},
        /* UPDATE_IR */ {RUN_TEST_IDLE, SELECT_DR_SCAN}};

    if (!_tck && tck) {
        switch (_state) {
            /* NOTE: 由于 tdi 一次只能传输一个 bit, 所以通过多个 SHIFT_DR 才能拿
             * 到最终的 dr */
            case SHIFT_DR:
                dr >>= 1;
                dr |= (uint64_t)_tdi << (dr_length - 1);
                break;
        }
        _state = next[_state][_tms];

    } else {
        // Negative clock edge. TDO is updated.
        switch (_state) {
            /* ... */
            case CAPTURE_DR:
                capture_dr();
                break;
            case SHIFT_DR:
                _tdo = dr & 1;
                break;
            case UPDATE_DR:
                update_dr();
                break;
        }
    }
    _tck = tck;
    _tms = tms;
    _tdi = tdi;
}
  • update_dr 会把收到的 tdi 组合成 dr 后把它解析成 dmi_read 或 dmi_write 调用
  • capture_dr 会把 dmi_read 的结果 (dmi) 保存到 dr, 以便通过多个 tdo 发送出去

1.4. dm

dm 负责执行 dmi_read/dmi_write, read 和 write 时操作的是 Debug Module Registers, 例如:

  1. data0, …, data11
  2. dmcontrol
  3. command
  4. hartinfo
  5. progbuf0, …, progbuf15

spike 使用不同的 buffer 表示这些寄存器, 例如 data0 在 debug_data_start, progbuf0 在 debug_progbuf_start.

1.4.1. dmi_write

bool debug_module_t::dmi_write(unsigned address, uint32_t value) {
    /* NOTE: jtag 会通过读取 data0...获得 abstract command 或 progbuf 的输出, 因
     * 为它们会把结果写到 DM_DATA0... */
    if (address >= DM_DATA0 && address < DM_DATA0 + abstractcs.datacount) {
        unsigned i = address - DM_DATA0;
        write32(dmdata, address - DM_DATA0, value);
        return true;
    } else if (
        /* NOTE: progbuf 允许 jtags 直接指定需要在 debug_mode 执行的代码 */
        address >= DM_PROGBUF0 && address < DM_PROGBUF0 + config.progbufsize) {
        unsigned i = address - DM_PROGBUF0;

        write32(program_buffer, i, value);

        if (!abstractcs.busy && ((abstractauto.autoexecprogbuf >> i) & 1)) {
            perform_abstract_command();
        }
        return true;

    } else {
        switch (address) {
            case DM_DMCONTROL: {
                dmcontrol.haltreq = get_field(value, DM_DMCONTROL_HALTREQ);
                dmcontrol.resumereq = get_field(value, DM_DMCONTROL_RESUMEREQ);
                /* ... */
                for (unsigned i = 0; i < nprocs; i++) {
                    if (hart_selected(i)) {
                        processor_t *proc = processor(i);
                        if (proc) {
                            /* NOTE: halt_request 会导致 guest 在 while (1)
                             * step() 进行 debug_mode */
                            proc->halt_request = dmcontrol.haltreq
                                                     ? proc->HR_REGULAR
                                                     : proc->HR_NONE;
                            if (dmcontrol.resumereq) {
                                debug_rom_flags[i] |=
                                    (1 << DEBUG_ROM_FLAG_RESUME);
                                hart_state[i].resumeack = false;
                            }
                        }
                    }
                }
            }
                return true;

            case DM_COMMAND:
                /* NOTE: 读写寄存器是通过 command 进行的 */
                command = value;
                return perform_abstract_command();
            /* ... */
        }
    }
    return false;
}
1.4.1.1. perform_abstract_command

dm 执行 jtags 指令有两种方式:

  1. 通过解释 command
  2. 通过执行 progbuf

spike 实现上把 abstract command 转换成具体的指令让 debug_mode 去执行

bool debug_module_t::perform_abstract_command() {
    // register access
    unsigned size = get_field(command, AC_ACCESS_REGISTER_AARSIZE);
    bool write = get_field(command, AC_ACCESS_REGISTER_WRITE);
    unsigned regno = get_field(command, AC_ACCESS_REGISTER_REGNO);

    unsigned i = 0;
    if (get_field(command, AC_ACCESS_REGISTER_TRANSFER)) {
        if (regno >= 0x1000 && regno < 0x1020) {
            unsigned regnum = regno - 0x1000;
            switch (size) {
                case 2:
                    if (write)
                        /* NOTE: 针对 read/write regnum, 在 debug_abstract 处开
                         * 始生成了对应的 lw/sw 指令, 且结果写在
                         * debug_data_start, debug_data_start 对应的实际就是
                         * data0 (因为 debug_module 本身是一个 mem_t, 参考它的
                         * load/store 函数) */
                        write32(
                            debug_abstract, i++,
                            lw(regnum, ZERO, debug_data_start));
                    else
                        write32(
                            debug_abstract, i++,
                            sw(regnum, ZERO, debug_data_start));
                    break;
                /* ... */
                default:
                    abstractcs.cmderr = CMDERR_NOTSUP;
                    return true;
            }
            /* ... */
        }
        /* ... */
    }
    if (get_field(command, AC_ACCESS_REGISTER_POSTEXEC)) {
        /* NOTE: 这里会生成指令跳到 debug_progbuf, 以支持 progbuf 功能, 且 jtag
         * 需要保证 progbuf 最后需要以 ebreak 结束 */
        write32(
            debug_abstract, i,
            jal(ZERO, debug_progbuf_start - debug_abstract_start - 4 * i));
        i++;
    } else {
        /* NOTE: ebreak 会导致 debug_abstract 执行完后跳转到 debug_rom 的 entry
         * 以重新开始 loop */
        write32(debug_abstract, i++, ebreak());
    }
    debug_rom_flags[dmcontrol.hartsel] |= 1 << DEBUG_ROM_FLAG_GO;
    return true;
}

1.4.2. dmi_read

dmi_read 相对 dmi_write 比较简单, 它用来读取 dm register, 特别的, 通过读取 DM_HARTINFO 可以得到 data0 的地址, 以便写 progbuf 时可以指定这个地址, 后续可以通过 dmi_read 读 data0 从而得到 progbuf 的结果

1.4.3. example

使用 dmi_write 和 dmi_read 查看寄存器的例子:

测试环境参考 https://github.com/riscv-software-src/riscv-isa-sim#debugging-with-gdb

gdb:

(gdb) info reg
ra             0x0      0x0
sp             0x0      0x0
gp             0x0      0x0
tp             0x0      0x0
t0             0x10110000       269549568
t1             0x0      0
t2             0x0      0
fp             0x0      0x0
s1             0x0      0
a0             0x0      0
a1             0x1020   4128
a2             0x0      0
a3             0x0      0
a4             0x0      0
a5             0x10110000       269549568

dm:

dmi_write(0x17, 0x321005)
dmi_read(0x16) -> 0x2000002
dmi_read(0x5) -> 0x0
dmi_read(0x4) -> 0x10110000
...
dmi_write(0x17, 0x32100f)
dmi_read(0x16) -> 0x2000002
dmi_read(0x5) -> 0x0
dmi_read(0x4) -> 0x10110000
  • 0x17 是 DM_COMMAND
  • 0x321005 表示要读取 gpr 且 regno 为 5, 对应 t0 (x5)
  • 0x32100f 表示要读取 gpr 且 regno 为 15, 对应 a5 (x15)
  • 0x5 是读取 data1, 0x4 是读取 data0, 因为 perform_abstract_command 会把结果写到 data0… 中

1.4.4. debug_mode

dmi_write 时会生成指令写在 debug_abstract 处, 会通过 debug_mode 跳转到这里

1.4.4.1. debug_abstract_start
debug_module_t::debug_module_t(sim_t *sim, const debug_module_config_t &config)
    : {
    /* ... */
    write32(
        debug_rom_whereto, 0,
        jal(ZERO, debug_abstract_start - DEBUG_ROM_WHERETO));
    reset();
}

/* NOTE: 其中 debug_rom_whereto 对应 DEBUG_ROM_WHERETO (0x300) 地址的数据 */
/* NOTE: 而 0x300 在 debug_rom.S 对应的 whereto 这个符号  */
bool debug_module_t::load(reg_t addr, size_t len, uint8_t *bytes) {
    /* ... */
    if (addr >= DEBUG_ROM_WHERETO && (addr + len) <= (DEBUG_ROM_WHERETO + 4)) {
        memcpy(bytes, debug_rom_whereto + addr - DEBUG_ROM_WHERETO, len);
        return true;
    }
    /* ... */
    return false;
}
1.4.4.2. enter_debug_mode
void processor_t::step(size_t n) {
    if (!state.debug_mode) {
        /* NOTE: DM_DMCONTROL_HALTREQ  */
        if (halt_request == HR_REGULAR) {
            enter_debug_mode(DCSR_CAUSE_DEBUGINT);
        }
        /* ... */
    }
    /* execute_insn */
}

void processor_t::enter_debug_mode(uint8_t cause) {
    state.debug_mode = true;
    state.dcsr->write_cause_and_prv(cause, state.prv);
    set_privilege(PRV_M);
    /* NOTE: 在退出 debug_mode 时需要用到 dpc */
    state.dpc->write(state.pc);
    /* NOTE: DEBUG_ROM_ENTRY 是 debug_rom.S 中的 entry */
    state.pc = DEBUG_ROM_ENTRY;
}
1.4.4.3. debug_rom.S

debug_rom.S 是 enter_debug_mode 时使用的 rom, 它会跳转到 debug_abstract_start:

    .option norvc
    .global entry
    .global exception

    # NOTE: entry 地址是 DEBUG_ROM_ENTRY, 它是 enter_debug_mode 时指定的 pc
entry:
    jal zero, _entry
resume:
    jal zero, _resume

_entry:
    fence
    csrw CSR_DSCRATCH, s0  // Save s0 to allow signaling MHARTID
entry_loop:
    csrr s0, CSR_MHARTID
    sw   s0, DEBUG_ROM_HALTED(zero)
    lbu  s0, DEBUG_ROM_FLAGS(s0) // 1 byte flag per hart. Only one hart advances here.
    andi s0, s0, (1 << DEBUG_ROM_FLAG_GO)
    # NOTE: going 会执行 abstract command
    bnez s0, going
    # ...
    jal  zero, entry_loop

going:
    # ...
    # NOTE: whereto 里的指令是 debug_module_t 初始化时写入的 jal debug_abstract_start
    jalr zero, zero, %lo(whereto)

_resume:
    # ...
    #NOTE: jtag 的 DM_DMCONTROL_RESUMEREQ 最终会调用到这里, 通过 dret 退出 debug mode
    dret

另外, 当 debug_abstract (及 progbuf) 执行完以后, 其最后一个指令必然是 ebreak, spike 针对 ebreak 有特殊的处理:

/* ebreak.h: */
throw trap_breakpoint(STATE.v, pc);

/* processor.cc: */
void processor_t::take_trap(trap_t& t, reg_t epc) {
    if (state.debug_mode) {
        if (t.cause() == CAUSE_BREAKPOINT) {
            /* NOTE: debug_abstract 或 progbuf 执行完以后重新开始 debug_rom 的
             * loop */
            state.pc = DEBUG_ROM_ENTRY;
        } else {
            state.pc = DEBUG_ROM_TVEC;
        }
        return;
    }
    /* ... */
}

1.5. debug with debug module

1.5.1. access register

读写寄存器需要使用 abstract command 或 progbuf, 参数/结果使用 data0 中, 通过 write(data0)/dmi_read(data0) 提供参数/返回结果

1.5.2. step

  1. 先通过 access register 向 dcsr 写入 DCSR_STEP, 表示需要单步执行.
  2. debug mode resume 时的 dret 会根据 dcsr 设置 state.STEP_STEPPING flag

    require(STATE.debug_mode);
    set_pc_and_serialize(STATE.dpc->read());
    p->set_privilege(STATE.dcsr->prv);
    
    /* We're not in Debug Mode anymore. */
    STATE.debug_mode = false;
    
    if (STATE.dcsr->step) STATE.single_step = STATE.STEP_STEPPING;
    
  3. processor step 时会根据这个 flag 重新进入 debug mode

    void processor_t::step(size_t n) {
        while (n > 0) {
            /* ... */
            if (unlikely(slow_path())) {
                // Main simulation loop, slow path.
                while (instret < n) {
                    if (unlikely(
                            !state.serialized &&
                            state.single_step == state.STEP_STEPPED)) {
                        state.single_step = state.STEP_NONE;
                        if (!state.debug_mode) {
                            enter_debug_mode(DCSR_CAUSE_STEP);
                            break;
                        }
                    }
    
                    /* NOTE: 通过 STEPPING, STEPPED 两个状态来支持
                     * step->debug->step->debug */
                    if (unlikely(state.single_step == state.STEP_STEPPING)) {
                        state.single_step = state.STEP_STEPPED;
                    }
    
                    /* ... */
                    pc = execute_insn(this, pc, fetch);
                }
            }
            /* ... */
        }
    }
    

例如:

# gdb:
0x0000000010110004 in main () at rot13.c:8
8           while (wait)
(gdb) p wait=1
$1 = 1
(gdb) si
[riscv.cpu] Found 4 triggers
0x0000000010110008      8           while (wait)
(gdb) si
8           while (wait)
(gdb) c
Continuing.

# dm
dmi_write(0x5, 0x0)
# NOTE: 0x4000b107 是写入的 dcsr 的值, 其 bit 2 为 1, 表示 step
dmi_write(0x4, 0x4000b107)
# NOTE: 7b0 是 dcsr
dmi_write(0x17, 0x3307b0)
resume hart 0
# ...
dmi_write(0x5, 0x0)
# NOTE: 0x4000b103 的 bit 2 为 0, 表示不再 step
dmi_write(0x4, 0x4000b103)
dmi_write(0x17, 0x3307b0)

另外, 通过 trigger 的 icount 看起来也可以实现 hw step

1.5.3. breakpoint

software breakpoint 通过 ebreak 支持, hardware breakpoint 通过 dm trigger 实现

1.5.3.1. ebreak

debug mode 下执行 ebreak (例如 debug_abstract 末尾自动插入的 ebreak) 会导致 debug mode 重新开始 loop.

普通模式下执行 ebreak 会导致 guest 进行 debug mode. 所以 debugger 可以通过修改指令为 ebreak 来设置 software break.

另外, gdb native debug 时 ebreak 需要触发 trap 而不是进入 debug mode (类似于 x86 的 int3, 参考 GDB Breakpoint), 这个通过 dcsr 可以配置

/* ebreak.h */
throw trap_breakpoint(STATE.v, pc);

/* processor.cc */

void processor_t::take_trap(trap_t& t, reg_t epc) {
    if (t.cause() == CAUSE_BREAKPOINT &&
        ((state.prv == PRV_M && state.dcsr->ebreakm) ||
         /* NOTE: ebreak 是进入 debug mode 还是作为 trap 是通过 dcsr 配置的,
          * 使用 jtags 时需要配置该项, 使用 gdb native debug 时则不能配置该项 */
         (state.prv == PRV_S && state.dcsr->ebreaks) ||
         (state.prv == PRV_U && state.dcsr->ebreaku))) {
        enter_debug_mode(DCSR_CAUSE_SWBP);
        return;
    }
    /* ... */
}
Backlinks

GDB Breakpoint (GDB Breakpoint > software breakpoint): 插入 software breakpoint 时, gdb 会把断点处的指令替换为会触发 trap 的指令, 例如 x86 上的 INT3 (0xcc), 以及 riscv 上的 ebreak. 同时记下原来的旧指令.

1.5.3.2. trigger

通过操作 tselect, tdata1, tdata2 等 csr 来设置 trigger 的 action, match 等信息. 然后 mmu 在访存时会处理这些 trigger

inline tlb_entry_t translate_insn_addr(reg_t addr) {
    /* ... */
    if (tlb_insn_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS)) {
        /* ... */
        triggers::action_t action;
        /* NOTE: trigger 是否与 addr match */
        auto match = proc->TM.memory_access_match(
            &action, triggers::OPERATION_EXECUTE, addr, from_target(*ptr));
        if (match != triggers::MATCH_NONE) {
            throw triggers::matched_t(
                triggers::OPERATION_EXECUTE, addr, from_target(*ptr), action);
        }
    }
    return result;
}

void processor_t::step(size_t n) {
    while (n > 0) {
        size_t instret = 0;
        reg_t pc = state.pc;
        mmu_t* _mmu = mmu;

        try {
            /* execute_insn... */
        } catch (triggers::matched_t& t) {
            switch (t.action) {
                case triggers::ACTION_DEBUG_MODE:
                    enter_debug_mode(DCSR_CAUSE_HWBP);
                    break;
                    /* ... */
            }
        }
    }
}

例如:

# gdb
(gdb) hbreak *0x10110000
(gdb) hbreak *0x10110004

# dm
# 通过 abstract command 写 tselect 寄存器 (7a0), 选择 trigger 0
dmi_write(0x5, 0x0)
dmi_write(0x4, 0x0)
dmi_write(0x17, 0x3307a0)

# 通过 abstract command 写 tdata0 (7a1)
dmi_read(0x16) -> 0x2000002
dmi_write(0x5, 0x28000000)
dmi_write(0x4, 0x105c)
dmi_write(0x17, 0x3307a1)

# 通过 abstract command 写 tdata1 (7a2), 写入的是要 break 的地址
dmi_write(0x5, 0x0)
dmi_write(0x4, 0x10110000)
dmi_write(0x17, 0x3307a2)

# 通过 abstract command 写 tselect 寄存器 (7a0), 选择 trigger 1
dmi_write(0x5, 0x0)
dmi_write(0x4, 0x1)
dmi_write(0x17, 0x3307a0)

# 通过 abstract command 写 tdata0 (7a1)
dmi_write(0x5, 0x28000000)
dmi_write(0x4, 0x105c)
dmi_write(0x17, 0x3307a1)

# 通过 abstract command 写 tdata1 (7a2), 写入的是要 break 的地址
dmi_write(0x5, 0x0)
dmi_write(0x4, 0x10110004)
dmi_write(0x17, 0x3307a2)
Backlinks

GDB Breakpoint (GDB Breakpoint > hardware breakpoint): 参考 riscv trigger

1.5.4. debug module vs. gdb native debug

debug module 是做为 external debugger (jtag) 的 stub 来使用的. gdb native debug 使用 ptrace, 并不依赖 debug module:

  1. access register 使用 kernel stack 里保存的上下文
  2. step 和 breakpoint 通常是软件实现的, 例如修改内存插入 ebreak 指令. 有时可以使用 hw step 和 hw breakpoint 支持. 例如, 通过配置 tdata 的 action 字段, RISC-V 的 trigger 可以触发 trap (而不进入 debug mode), 从而给 native debug 提供 hw breakpoint 支持.

Backlinks

GDB Remote Serial Protocal (GDB Remote Serial Protocal > Overview): RSP 的 server 端运行在被调试的设备上, server 可以在遵守 RSP 协议的基础上自由开发, 例如 qemu 和 openocd 自带的 server. 如果被调试的设备本身就有 gdb 的支持, 则可以 使用 gdb 自带的 gdbserver, 例如 android 上的 gdbserver

Spike (Spike > debug module): debug module

Author: [email protected]
Date: 2023-03-20 Mon 11:41
Last updated: 2023-10-13 Fri 15:17

知识共享许可协议