iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
0
Software Development

系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!系列 第 21

Day 21: 深入淺出 RISC-V 源碼剖析 (6) - Polling & Debug(Halt) Reason

0. 前言

經過前面五篇的洗禮後,終於來到本系列最後一篇啦!!!
先幫各位複習一下前面那幾篇的連結:

大部分的應用&流程都已經剖析過,本篇算是總結?!
順便講講剩下的幾個東西! 讓我們輕鬆地結束吧!
  
  
  

1. Polling

OpenOCD在初始化Target的時候,同時也會註冊一個target_register_timer_callback(),主要的目的是定期去查詢Target的狀態(預設: 100ms一次),可以參考以下實作,
參考以下內容(src/target/target.c):

static int target_init(struct command_context *cmd_ctx)
{
    struct target *target;
    int retval;

    for (target = all_targets; target; target = target->next) {
        retval = target_init_one(cmd_ctx, target);
        if (ERROR_OK != retval)
            return retval;
    }

    if (!all_targets)
        return ERROR_OK;

    retval = target_register_user_commands(cmd_ctx);
    if (ERROR_OK != retval)
        return retval;

    ///譯註: 這裡註冊!
    retval = target_register_timer_callback(&handle_target,
            polling_interval, 1, cmd_ctx->interp);
    if (ERROR_OK != retval)
        return retval;

    return ERROR_OK;
}

再來handle_target()中,就會掃描所有註冊的Target,在未Disable之前,
則會呼叫各個Target中所註冊的Polling函式,請參考以下內容(src/target/target.c):

/* process target state changes */
static int handle_target(void *priv)
{
    ....前面省略

    /* Poll targets for state changes unless that's globally disabled.
     * Skip targets that are currently disabled.
     */

    ///譯註: 搜尋註冊中的所有Target
    for (struct target *target = all_targets;
            is_jtag_poll_safe() && target;
            target = target->next) {

        ///譯註: 跳過那些沒有被初始化成功的Target!
        if (!target_was_examined(target))
            continue;

        ///譯註: 跳過那些已經被Disable的Target
        if (!target->tap->enabled)
            continue;

        if (target->backoff.times > target->backoff.count) {
            /* do not poll this time as we failed previously */
            target->backoff.count++;
            continue;
        }
        target->backoff.count = 0;

        /* only poll target if we've got power and srst isn't asserted */
        if (!powerDropout && !srstAsserted) {
            /* polling may fail silently until the target has been examined */
           
            ///譯註: 這邊呼叫處理!
            retval = target_poll(target);


            ....中間省略
        }
    }

    return retval;
}

再來就是負責呼叫和處理各種Target的函式,請參考以下內容(src/target/target.c):

int target_poll(struct target *target)
{
    int retval;

    /* We can't poll until after examine */
    if (!target_was_examined(target)) {
        /* Fail silently lest we pollute the log */
        return ERROR_FAIL;
    }

    ///譯註: 呼叫各個Target中預先註冊好的函式來處理!
    retval = target->type->poll(target);
    if (retval != ERROR_OK)
        return retval;

    if (target->halt_issued) {
        if (target->state == TARGET_HALTED)
            target->halt_issued = false;
        else {
            int64_t t = timeval_ms() - target->halt_issued_time;
            if (t > DEFAULT_HALT_TIMEOUT) {
                target->halt_issued = false;
                LOG_INFO("Halt timed out, wake up GDB.");
                target_call_event_callbacks(target, TARGET_EVENT_GDB_HALT);
            }
        }
    }

    return ERROR_OK;
}

在各個Target中,已經會預先註冊好處理的流程!
所以這邊就簡單啦,直接呼叫target->type->poll()即可!

再來我們就進到RISC-V中!
首先讓我們先看一下上層處理的部分,請參考以下內容(src/target/riscv/riscv.c):

int riscv_openocd_poll(struct target *target)
{
    LOG_DEBUG("polling all harts");
    int triggered_hart = -1;
    if (riscv_rtos_enabled(target)) {
        
        ....RTOS的部分省略!

    } else {
        ////譯註: 針對單一個Hart呼叫riscv_poll_hart()來處理!
        if (riscv_poll_hart(target, riscv_current_hartid(target)) == 0)
            return ERROR_OK;

        triggered_hart = riscv_current_hartid(target);
        LOG_DEBUG("  hart %d halted", triggered_hart);
    }


    ....後面Halt處理省略


    target->state = TARGET_HALTED;
    target_call_event_callbacks(target, TARGET_EVENT_HALTED);
    return ERROR_OK;
}

這邊很簡單,單純的呼叫riscv_poll_hart()來處理,所以接下來就是來關心一下riscv_poll_hart()的流程,請參考以下內容(src/target/riscv/riscv.c):

static int riscv_poll_hart(struct target *target, int hartid)
{
    RISCV_INFO(r);
    riscv_set_current_hartid(target, hartid);

    LOG_DEBUG("polling hart %d, target->state=%d (TARGET_HALTED=%d)", hartid, target->state, TARGET_HALTED);

    /* If OpenOCD this we're running but this hart is halted then it's time
     * to raise an event. */
    ///譯註: 判斷Hart是否在Halt的階段
    if (target->state != TARGET_HALTED && riscv_is_halted(target)) {
        LOG_DEBUG("  triggered a halt");
        r->on_halt(target);
        return 1;
    }

    return 0;
}

這邊就是簡單的呼叫riscv_is_halted()來判斷Hart目前的狀態,請參考以下內容(src/target/riscv/riscv.c):

bool riscv_is_halted(struct target *target)
{
    RISCV_INFO(r);
    assert(r->is_halted);
    return r->is_halted(target);
}

這邊終於呼叫到0.13的底層來處理is_halted(),
所以我們來看看核心的處理部分,請參考以下內容(src/target/riscv/riscv-013.c):

static bool riscv013_is_halted(struct target *target)
{
    uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
    if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL))
        LOG_ERROR("hart %d is unavailiable", riscv_current_hartid(target));
    if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT))
        LOG_ERROR("hart %d doesn't exist", riscv_current_hartid(target));
    return get_field(dmstatus, DMI_DMSTATUS_ALLHALTED);
}

基本上就是從$dmstatus中挖出allhalted這個欄位來看看,忘記的話可以參考「」中"2.2 0x11 dmstatus: Debug Module Status"的說明!
  
  
  

2. Debug Reason

本節主要是依據上節的內容作延伸,當Target進入Halted的時候,
OpenOCD就會負責判斷原因!! 並在適當的時候做出處理或是告知使用者!

所以我們先來回顧一下,剛剛介紹過得riscv_openocd_poll()
請參考以下內容(src/target/riscv/riscv.c):

int riscv_openocd_poll(struct target *target)
{
    ....前面介紹過了,省略

    target->state = TARGET_HALTED;

    ///譯註: 呼叫riscv_halt_reason()
    switch (riscv_halt_reason(target, triggered_hart)) {
    case RISCV_HALT_BREAKPOINT:
        target->debug_reason = DBG_REASON_BREAKPOINT;
        break;
    case RISCV_HALT_INTERRUPT:
        target->debug_reason = DBG_REASON_DBGRQ;
        break;
    case RISCV_HALT_SINGLESTEP:
        target->debug_reason = DBG_REASON_SINGLESTEP;
        break;
    case RISCV_HALT_UNKNOWN:
        target->debug_reason = DBG_REASON_UNDEFINED;
        break;
    }

    if (riscv_rtos_enabled(target)) {
        target->rtos->current_threadid = triggered_hart + 1;
        target->rtos->current_thread = triggered_hart + 1;
    }

    target->state = TARGET_HALTED;
    target_call_event_callbacks(target, TARGET_EVENT_HALTED);
    return ERROR_OK;
}

這邊主要是接續在剛剛riscv_poll_hart()後面,當判斷Hart進入Halted時,才會進入到這個檢測得流程中,
然後呼叫riscv_halt_reason()來判斷原因,讓我們繼續看下去,
請參考以下內容(src/target/riscv/riscv.c):

enum riscv_halt_reason riscv_halt_reason(struct target *target, int hartid)
{
    RISCV_INFO(r);
    riscv_set_current_hartid(target, hartid);
    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart is not halted!");
        return RISCV_HALT_UNKNOWN;
    }
    return r->halt_reason(target);
}

這邊就是很單純的呼叫底層的r->halt_reason()來判斷,所以.... 可以直接跳過介紹!
然後就來到最核心的地方riscv013_halt_reason()
請參考以下內容(src/target/riscv/riscv-013.c):

static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
{
    uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
    switch (get_field(dcsr, CSR_DCSR_CAUSE)) {
    case CSR_DCSR_CAUSE_SWBP:
    case CSR_DCSR_CAUSE_TRIGGER:
        return RISCV_HALT_BREAKPOINT;
    case CSR_DCSR_CAUSE_STEP:
        return RISCV_HALT_SINGLESTEP;
    case CSR_DCSR_CAUSE_DEBUGINT:
    case CSR_DCSR_CAUSE_HALT:
        return RISCV_HALT_INTERRUPT;
    }

    LOG_ERROR("Unknown DCSR cause field: %x", (int)get_field(dcsr, CSR_DCSR_CAUSE));
    LOG_ERROR("  dcsr=0x%016lx", (long)dcsr);
    return RISCV_HALT_UNKNOWN;
}

這邊主要用到$dcsr中的cause欄位,可以參考「Day 11: RISC-V Debug Introduction」中"#5.1 0x7b0 dcsr: Debug Control and Status"的相關說明!

主要我們將原因分成以下三類:

  • BREAKPOINT: 不管是Software breakpoint、Hardware breakpoint或是Watchpoint等等
  • SINGLESTEP: 執行Single Step 造成的
  • INTERRUPT: 不管是Debugger要求的或是其他系統中的Interrupt

這邊就是簡單的判斷cause欄位,然後回傳對應的原因代號給上層!

最後回來看一下riscv_openocd_poll()處理的流程

    switch (riscv_halt_reason(target, triggered_hart)) {
    case RISCV_HALT_BREAKPOINT:
        target->debug_reason = DBG_REASON_BREAKPOINT;
        break;
    case RISCV_HALT_INTERRUPT:
        target->debug_reason = DBG_REASON_DBGRQ;
        break;
    case RISCV_HALT_SINGLESTEP:
        target->debug_reason = DBG_REASON_SINGLESTEP;
        break;
    case RISCV_HALT_UNKNOWN:
        target->debug_reason = DBG_REASON_UNDEFINED;
        break;
    }

    ....中間省略

    target->state = TARGET_HALTED;
    target_call_event_callbacks(target, TARGET_EVENT_HALTED);

這邊就是把原因放回去OpenOCD Target資料結構中的debug_reason,
最後呼叫OpenOCD系統的callback去做後續處理target_call_event_callbacks()!
  
  
  

99. 結語

以上大概就是簡單介紹的這邊,將整個系列的"深入淺出 RISC-V 源碼剖析"畫下一個句點!
然後鐵人賽就結束了,沒東西可以寫....

明天將會帶一個簡單的Lab,操作OpenOCD在RISC-V 64bit的平台上做個簡單的DEMO!
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd
  3. Day 11: RISC-V Debug Introduction

上一篇
Day 20: 深入淺出 RISC-V 源碼剖析 (5) - Insert/Remove Trigger
下一篇
Day 22: [Lab] 深入淺出 RISC-V 源碼剖析 (Final) - 執行 & Log分析
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30

尚未有邦友留言

立即登入留言