iT邦幫忙

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

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

Day 20: 深入淺出 RISC-V 源碼剖析 (5) - Insert/Remove Trigger

0. 前言

經過上一篇摧殘之後,剩下應該就還好了~~!!
終於來到第20天,鐵人賽開始準備最後的倒數!

今天主要是剖析一下RISC-V架構中如何將新增Watchpoint和Breakpoint,並把它們移除!!

讓我們開始吧!
  
  
  

1. Enumerate Triggers

在「Day 13: 了解Trigger Module的神秘面紗(下)~~!」中其實已經有稍微說明到如何去評估(偵測)、初始化RISC-V Debug System中的Trigger Module,這邊會再稍微深入剖析!

參考以下內容(src/target/riscv/riscv.c):

int riscv_enumerate_triggers(struct target *target)
{
    RISCV_INFO(r);

    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;

        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid,
                GDB_REGNO_TSELECT);

        for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
            r->trigger_count[hartid] = t;

            riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
            uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TSELECT);
            /* Mask off the top bit, which is used as tdrmode in old
             * implementations. */
            tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
            if (tselect_rb != t)
                break;
            uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TDATA1);
            int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
            switch (type) {
                case 1:
                    /* On these older cores we don't support software using
                     * triggers. */
                    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
                case 2:
                    if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
                        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;
            }
        }

        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);

        LOG_INFO("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
    }

    return ERROR_OK;
}

主要分成以下幾個步驟:

  1. 對$tselect寫入目前選擇Trigger的編號
  2. 重新讀回$tselect,確認該Trigger是否存在
  3. 讀取$tdata1,確認Trigger支援的功能
  4. 若type為0,表示該編號之後的Trigger都不存在,結束初始化
  5. 重複整個流程,直到所有Trigger都被檢驗過

首先是Step 1. 對$tselect寫入目前選擇Trigger的編號:

riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);

riscv_set_register_on_hart() 這個函式已經在「Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access」中的"3.1 Write Registers by Abstract Command"中特別解釋過了!

根據目前的設計:

#define RISCV_MAX_TRIGGERS 32
for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {

}

單一個Hart所支援的Trigger Module數量上限是"32"

再來是Step 2. 重新讀回$tselect,確認該Trigger是否存在,
然後很重要的一個點就是要把$tselect讀回來看看,看看跟寫入是否一致:

            uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TSELECT);
            ....
            if (tselect_rb != t)
                break;

發現不一致的話,就表示這個點開始的Trigger皆不存在,因為Trigger的編號必須保證"連續",因此一旦發現不同的話,就表示可以去評估下一個Hart!

如果通過檢測,表示這個Trigger存在,接下來就是Step 3. 讀取$tdata1,確認Trigger支援的功能,去評估這個Trigger:

uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
                    GDB_REGNO_TDATA1);
            int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));

依照目前架構的設計type主要有三種:

  • type = 1: SiFive自己的"address match trigger",不在本文的討論範圍內
  • type = 2: 當作"Address Match Trigger"用
  • type = 3: 當作"Instruction Count Trigger"用,不過這部分還沒有實做出來!

我們主要是需要type = 2的那些Trigger,方便我們當作Hardware的Breakpoint和Watchpoint使用!

在type = 2中,這邊多加了一個判斷:

if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
                        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
                    break;

主要是評估該Trigger的dmode是否有被拉起來,讓這個Trigger僅限於Debug Mode下才能使用,如果發現dmode被設定成1,則將整個$tdata1寫0,作清空!

根據整體架構的設計,在Machine Mode中也可以使用該Trigger!

最後重複以上流程,直到所有Hart中的所有Trigger都被檢驗過!
  
  
  

1. Breakpoint

這是Debugger最重要的的功能之一,主要是負責讓程式運行至指定的位置後停下來,
等待進一步的處理!

基本上Breakpoint分稱兩種:

  • Software Breakpoint
  • Hardware Breakpoint
    前者的數量可以有無限多個(!?),後者則依照各家平台設計的不同,提供不同數量的支援!
      
      

1.1 新增Breakpoint 進入點

一樣先從上層的進入點開始剖析!
參考以下內容(src/target/riscv/riscv.c):

int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint)
{
    if (breakpoint->type == BKPT_SOFT) {
        ///譯註: 新增Software Breakpoint

        if (target_read_memory(target, breakpoint->address, breakpoint->length, 1,
                    breakpoint->orig_instr) != ERROR_OK) {
            LOG_ERROR("Failed to read original instruction at 0x%" TARGET_PRIxADDR,
                    breakpoint->address);
            return ERROR_FAIL;
        }

        int retval;
        if (breakpoint->length == 4)
            retval = target_write_u32(target, breakpoint->address, ebreak());
        else
            retval = target_write_u16(target, breakpoint->address, ebreak_c());
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write %d-byte breakpoint instruction at 0x%"
                    TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
            return ERROR_FAIL;
        }

    } else if (breakpoint->type == BKPT_HARD) {
        ///譯註: 新增Hardware Breakpoint

        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);
        int result = add_trigger(target, &trigger);
        if (result != ERROR_OK)
            return result;

    } else {
        LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    breakpoint->set = true;

    return ERROR_OK;
}

可以看到OpenOCD在負責處理GDB重送過來的要求後,會把幾本的資料放入struct breakpoint中,基本定義如下:

參考以下內容(src/target/breakpoint.h):

struct breakpoint {
    target_addr_t address;
    uint32_t asid;
    int length;
    enum breakpoint_type type;
    int set;
    uint8_t *orig_instr;
    struct breakpoint *next;
    uint32_t unique_id;
    int linked_BRP;
};

裡面幾本上包含以下資訊:

  • type: 種類,分成Software breakpoint和Hardware breakpoint
  • address: 要插入Breakpoint的目標位置
  • length: 該行指令的長度
  • unique_id: OpenOCD會幫Breakpoint和Watchpoint編一個獨一無二的號,方便區分處理!

剩下資料會在後面章節中談到!

在這個進入點最重要的是,就是區分Software breakpoint和Hardware breakpoint,
然後分別做出對應的處理!
  
  

1.2 Software breakpoint

Software breakpoint最基本的觀念就是在該行原先的指令中,用ebreak指令來取代
好了講完了~~!

讓我們剖析一下這部分要如何處理,簡單的流程如下:

  1. 讀取原先位置的指令
  2. 寫入對應的ebreak

很簡單,然後看實作的部分:
參考以下內容(src/target/riscv/riscv.c):

        if (target_read_memory(target, breakpoint->address, breakpoint->length, 1,
                    breakpoint->orig_instr) != ERROR_OK) {
            LOG_ERROR("Failed to read original instruction at 0x%" TARGET_PRIxADDR,
                    breakpoint->address);
            return ERROR_FAIL;
        }

        int retval;
        if (breakpoint->length == 4)
            retval = target_write_u32(target, breakpoint->address, ebreak());
        else
            retval = target_write_u16(target, breakpoint->address, ebreak_c());
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write %d-byte breakpoint instruction at 0x%"
                    TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
            return ERROR_FAIL;
        }

首先是Step 1. 讀取原先位置的指令:

target_read_memory(target, breakpoint->address, breakpoint->length, 1,
                    breakpoint->orig_instr)

在這邊用用到breakpoint資料結構中的address和length,並用我們在上篇「Day 19: 深入淺出 RISC-V 源碼剖析 (4) - Memory Access」所提到的Memory Access的方式,將原先指令從記憶體中讀出!
並將其放入breakpoint資料結構中的"orig_instr"中,方便我們日後再移除這個Software breakpoint的時候,可以把原先的指令還原回去!

再來是Step 2. 寫入對應的ebreak:

int retval;
        if (breakpoint->length == 4)
            retval = target_write_u32(target, breakpoint->address, ebreak());
        else
            retval = target_write_u16(target, breakpoint->address, ebreak_c());
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write %d-byte breakpoint instruction at 0x%"
                    TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
            return ERROR_FAIL;
        }

這部分就是要依照原先指令的長度,加入對應的ebreak指令:

  • ebreak: for 32bits
  • c.ebreak: for 16bits

並將其覆蓋回原先指令的位置!
  
  

1.3 Hardware breakpoint

在進入點中,參考以下內容(src/target/riscv/riscv.c):

        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);
        int result = add_trigger(target, &trigger);
        if (result != ERROR_OK)
            return result;

就是很簡單的呼叫trigger_from_breakpoint()add_trigger()來處理!
而這個處理的過程在「Day 13: 了解Trigger Module的神秘面紗(下)~~!」中也有稍微剖析過!

首先是trigger_from_breakpoint(),主要負責準備好Trigger新增時所需要的資料,
參考以下內容(src/target/riscv/riscv.c):

static void trigger_from_breakpoint(struct trigger *trigger,
        const struct breakpoint *breakpoint)
{
    trigger->address = breakpoint->address;
    trigger->length = breakpoint->length;
    trigger->mask = ~0LL;
    trigger->read = false;
    trigger->write = false;
    trigger->execute = true;    ///譯註: 這行很重要,用來標示這是個Breakpoint
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = breakpoint->unique_id;
}

先把Trigger需要的資料準備好,分成以下幾種:

  • address: 目標位置
  • length: 目標指令的長度

由於Breakpoint是在指令"執行"的時候停下來,因此需要設定Trigger的以下三個欄位:

  • read = false: 不需要在讀取時暫停
  • write = false: 不需要在寫入時暫停
  • execute = true: 需要再執行的時候停下!!

最後,為了方便處理Trigger對應Breakpoint的關係,這邊也在Trigger中記錄breakpoint的unique_id:

    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = breakpoint->unique_id;

  
  

1.4 Add Trigger

準備好資料後,最後是呼叫add_trigger(),將這個Trigger加入,
參考以下內容(src/target/riscv/riscv.c):

static int add_trigger(struct target *target, struct trigger *trigger)
{
    RISCV_INFO(r);

    /* In RTOS mode, we need to set the same trigger in the same slot on every
     * hart, to keep up the illusion that each hart is a thread running on the
     * same core. */

    /* Otherwise, we just set the trigger on the one hart this target deals
     * with. */

    riscv_reg_t tselect[RISCV_MAX_HARTS];

    int first_hart = -1;
    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        if (first_hart < 0)
            first_hart = hartid;
        tselect[hartid] = riscv_get_register_on_hart(target, hartid,
                GDB_REGNO_TSELECT);
    }
    assert(first_hart >= 0);

    unsigned int i;
    for (i = 0; i < r->trigger_count[first_hart]; i++) {
        if (r->trigger_unique_id[i] != -1)
            continue;

        riscv_set_register_on_hart(target, first_hart, GDB_REGNO_TSELECT, i);

        uint64_t tdata1 = riscv_get_register_on_hart(target, first_hart, GDB_REGNO_TDATA1);
        int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));

        int result = ERROR_OK;
        for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
            if (!riscv_hart_enabled(target, hartid))
                continue;
            if (hartid > first_hart)
                riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
            switch (type) {
                case 1:
                    result = maybe_add_trigger_t1(target, hartid, trigger, tdata1);
                    break;
                case 2:
                    result = maybe_add_trigger_t2(target, hartid, trigger, tdata1);
                    break;
                default:
                    LOG_DEBUG("trigger %d has unknown type %d", i, type);
                    continue;
            }

            if (result != ERROR_OK)
                continue;
        }

        if (result != ERROR_OK)
            continue;

        LOG_DEBUG("Using trigger %d (type %d) for bp %d", i, type,
                trigger->unique_id);
        r->trigger_unique_id[i] = trigger->unique_id;
        break;
    }

    for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT,
                tselect[hartid]);
    }

    if (i >= r->trigger_count[first_hart]) {
        LOG_ERROR("Couldn't find an available hardware trigger.");
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    return ERROR_OK;
}

中間落落長的判斷,不過因為只有Trigger type = 2的時候才能夠當作Breakpoint用,
所以這邊我們只關心核心的地方:

result = maybe_add_trigger_t2(target, hartid, trigger, tdata1);

來剖析maybe_add_trigger_t2()的實作部分,
參考以下內容(src/target/riscv/riscv.c):

static int maybe_add_trigger_t2(struct target *target, unsigned hartid,
        struct trigger *trigger, uint64_t tdata1)
{
    RISCV_INFO(r);

    /* tselect is already set */
    if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
        /* Trigger is already in use, presumably by user code. */
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    /* address/data match trigger */
    tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
    tdata1 = set_field(tdata1, MCONTROL_ACTION,
            MCONTROL_ACTION_DEBUG_MODE);
    tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
    tdata1 |= MCONTROL_M;
    if (r->misa & (1 << ('H' - 'A')))
        tdata1 |= MCONTROL_H;
    if (r->misa & (1 << ('S' - 'A')))
        tdata1 |= MCONTROL_S;
    if (r->misa & (1 << ('U' - 'A')))
        tdata1 |= MCONTROL_U;

    if (trigger->execute)
        tdata1 |= MCONTROL_EXECUTE;
    if (trigger->read)
        tdata1 |= MCONTROL_LOAD;
    if (trigger->write)
        tdata1 |= MCONTROL_STORE;

    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);

    uint64_t tdata1_rb = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TDATA1);
    LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);

    if (tdata1 != tdata1_rb) {
        LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
                PRIx64 " to tdata1 it contains 0x%" PRIx64,
                tdata1, tdata1_rb);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);

    return ERROR_OK;
}

一樣,我們來一一剖析實作,
首先是一個簡單的判斷:

    /* tselect is already set */
    if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
        /* Trigger is already in use, presumably by user code. */
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

這邊主要是判斷,這個Trigger有沒有被使用過,
最基本的就是判斷$tdata1中的execute、store、load有沒有被使用過!?

再來是做些基本的設定:

    ///譯註: 將dmode設定成1,作保護
    tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
    
    ///譯註: 設定action = 1
    tdata1 = set_field(tdata1, MCONTROL_ACTION,
            MCONTROL_ACTION_DEBUG_MODE);

    ///譯註: 設定match = 0
    tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
    
    ///譯註: 設定m、h、s、u
    tdata1 |= MCONTROL_M;
    if (r->misa & (1 << ('H' - 'A')))
        tdata1 |= MCONTROL_H;
    if (r->misa & (1 << ('S' - 'A')))
        tdata1 |= MCONTROL_S;
    if (r->misa & (1 << ('U' - 'A')))
        tdata1 |= MCONTROL_U;

    ///譯註: 設定execute、read、write
    if (trigger->execute)
        tdata1 |= MCONTROL_EXECUTE;
    if (trigger->read)
        tdata1 |= MCONTROL_LOAD;
    if (trigger->write)
        tdata1 |= MCONTROL_STORE;

一開始先將dmode設定成1,防止除了Debugger以外的人,修改到這個Trigger;
接著是設定action為1,讓踩到Trigger的時候,Hart直接停下來並進入Debug Mode中;
然後設定match為0,表示address必須完全相符才行;
除了M(Matchine) Mode一定會有之外,依照硬體ISA支援的部分,將對應的H(hypervisor) Mode、S(supervisor) Mode、U(user) Mode中,讓Trigger在這些Mode下都能夠作用;
最後是將execute、load、store依照所需,設定對應的部分!
  
  
  

2. Watchpoint

Watchpoint也是這是Debugger最重要的的功能之一,主要是負責當程式讀/寫指定的位置時候後,能夠停下來,等待進一步的處理!

不像Breakpoint一樣分成兩種,Watchpoint一定要硬體支援才行!
  
  

2.1 新增Watchpoint 進入點

先來看看進入點的部分,參考以下內容(src/target/riscv/riscv.c):

int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
{
    struct trigger trigger;
    trigger_from_watchpoint(&trigger, watchpoint);

    int result = add_trigger(target, &trigger);
    if (result != ERROR_OK)
        return result;
    watchpoint->set = true;

    return ERROR_OK;
}

可以看到OpenOCD在負責處理GDB重送過來的要求後,會把幾本的資料放入struct watchpoint中,基本定義如下!

參考以下內容(src/target/watchpoint.h):

struct watchpoint {
    target_addr_t address;
    uint32_t length;
    uint32_t mask;
    uint32_t value;
    enum watchpoint_rw rw;
    int set;
    struct watchpoint *next;
    int unique_id;
};

裡面幾本上包含以下資訊:

  • address: 要觀察目標的位置
  • length: 觀察目標的長度
  • rw: Watchpoint分成三種
    • Read Watchpoint: 當目標位置的資料被讀取時觸發
    • Write Watchpoint: 當目標位置的資料被寫入時觸發
    • Access Watchpoint: 當目標位置的資料被讀取或寫入時都觸發
  • value: 目標值,不過目前實作中還沒有支援!
  • mask: 主要是當作"遮罩"使用,不過目前實作中還沒有支援!
  • unique_id: OpenOCD會幫Breakpoint和Watchpoint編一個獨一無二的號,方便區分處理!

再來就是很簡單的呼叫trigger_from_watchpoint()add_trigger()來處理!
而這個處理的過程在「Day 13: 了解Trigger Module的神秘面紗(下)~~!」中也有稍微剖析過!

首先是trigger_from_watchpoint()的部分,
參考以下內容(src/target/watchpoint.h):

static void trigger_from_watchpoint(struct trigger *trigger,
        const struct watchpoint *watchpoint)
{
    trigger->address = watchpoint->address;
    trigger->length = watchpoint->length;
    trigger->mask = watchpoint->mask;
    trigger->value = watchpoint->value;
    trigger->read = (watchpoint->rw == WPT_READ || watchpoint->rw == WPT_ACCESS);    ///譯註: 這行很重要,用來標示這是個Read Watchpoint或是Access Watchpoint
    trigger->write = (watchpoint->rw == WPT_WRITE || watchpoint->rw == WPT_ACCESS);    ///譯註: 這行很重要,用來標示這是個Write Watchpoint或是Access Watchpoint
    trigger->execute = false;
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = watchpoint->unique_id;
}

類似trigger_from_breakpoint(),將對應的資料設定好!
主要需要注意的地方只有read、write、execute設定上有差異外,基本流程都一樣!

最後是呼叫add_trigger()來處理!
這部分已經在上面的"# 1.4 Add Trigger"中提到,就不需要再重複說明!
  
  
  

3. 移除Trigger

最後是移除Trigger的部分!
由於這部分比較簡單些,將Breakpoint和Watchpoint在這邊統一剖析!
  
  

3.1 移除Breakpoint

一樣先來剖一下上層的進入點,參考以下內容(src/target/riscv/riscv.c):

int riscv_remove_breakpoint(struct target *target,
        struct breakpoint *breakpoint)
{
    if (breakpoint->type == BKPT_SOFT) {
        if (target_write_memory(target, breakpoint->address, breakpoint->length, 1,
                    breakpoint->orig_instr) != ERROR_OK) {
            LOG_ERROR("Failed to restore instruction for %d-byte breakpoint at "
                    "0x%" TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
            return ERROR_FAIL;
        }

    } else if (breakpoint->type == BKPT_HARD) {
        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);
        int result = remove_trigger(target, &trigger);
        if (result != ERROR_OK)
            return result;

    } else {
        LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
    }

    breakpoint->set = false;

    return ERROR_OK;
}

上面章節有提到過Breakpoint分成兩種:

  • Software Breakpoint
  • Hardware Breakpoint

當然這邊處理的流程也稍微不同!
首先是Software Breakpoint的部分:

        if (target_write_memory(target, breakpoint->address, breakpoint->length, 1,
                    breakpoint->orig_instr) != ERROR_OK) {
            LOG_ERROR("Failed to restore instruction for %d-byte breakpoint at "
                    "0x%" TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
            return ERROR_FAIL;
        }

幾本上就是把原先breakpoint中紀錄的orig_instr給寫回去原本的位置!
搞定收工!

再來是Hardware Breakpoint的部分:

        struct trigger trigger;
        trigger_from_breakpoint(&trigger, breakpoint);
        int result = remove_trigger(target, &trigger);
        if (result != ERROR_OK)
            return result;

不得不說我覺得這個實作有點多餘XD
因為他先呼叫了trigger_from_breakpoint(),將breakpoint轉成trigger的資料,
比如以下的實作部分,參考以下內容(src/target/riscv/riscv.c):

static void trigger_from_breakpoint(struct trigger *trigger,
        const struct breakpoint *breakpoint)
{
    trigger->address = breakpoint->address;
    trigger->length = breakpoint->length;
    trigger->mask = ~0LL;
    trigger->read = false;
    trigger->write = false;
    trigger->execute = true;
    /* unique_id is unique across both breakpoints and watchpoints. */
    trigger->unique_id = breakpoint->unique_id;
}

不過基本上會用到的資料也只有那個unique_id而已....
  
  

3.2 移除Trigger

最後,呼叫remove_trigger()來移除這個Trigger!
基本實作如下,參考以下內容(src/target/riscv/riscv.c):

static int remove_trigger(struct target *target, struct trigger *trigger)
{
    RISCV_INFO(r);

    int first_hart = -1;
    for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        if (first_hart < 0) {
            first_hart = hartid;
            break;
        }
    }
    assert(first_hart >= 0);

    unsigned int i;
    ///譯註: 這裡最重要,要找到這個Breakpoint對應的Trigger編號!
    for (i = 0; i < r->trigger_count[first_hart]; i++) {
        if (r->trigger_unique_id[i] == trigger->unique_id)
            break;
    }
    if (i >= r->trigger_count[first_hart]) {
        LOG_ERROR("Couldn't find the hardware resources used by hardware "
                "trigger.");
        return ERROR_FAIL;
    }
    LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
    for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
        if (!riscv_hart_enabled(target, hartid))
            continue;
        riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);

        ///譯註: 清除Trigger
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
        riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
    }
    r->trigger_unique_id[i] = -1;

    return ERROR_OK;
}

基本流程就是先用unique_id找到對應的Trigger點,然後就....
直接清成"0"就行了XD!
  
  

3.3 移除Watchpoint

最後是移除Watchpoint的進入點,參考以下內容(src/target/riscv/riscv.c):

int riscv_remove_watchpoint(struct target *target,
        struct watchpoint *watchpoint)
{
    struct trigger trigger;
    trigger_from_watchpoint(&trigger, watchpoint);

    int result = remove_trigger(target, &trigger);
    if (result != ERROR_OK)
        return result;
    watchpoint->set = false;

    return ERROR_OK;
}

一樣先呼叫trigger_from_watchpoint()將Watchpoint轉成Trigger的資料結構,
然後..... 就呼叫remove_trigger()來移除!
收工!
  
  
  

99. 結語

本篇詳細的說明Software breakpoint、Hardware breakpoint和Watchpoint的部分,
當然,對比原本的Spec中,Trigger還包含多種match、length、mask等應用,
比方說希望能夠在某個變數大於0x10000時候才停下來,這邊的實作"完全做不到"XD!

等日後有更進一步的實作,應該會再另闢新文說明!
如果有時間的話

沒意外的話,明天應該就會是這個系列的最終章!
再來就要換全新的主題啦!!!
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd
  3. Day 13: 了解Trigger Module的神秘面紗(下)~~!
  4. Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access
  5. Day 19: 深入淺出 RISC-V 源碼剖析 (4) - Memory Access

上一篇
Day 19: 深入淺出 RISC-V 源碼剖析 (4) - Memory Access
下一篇
Day 21: 深入淺出 RISC-V 源碼剖析 (6) - Polling & Debug(Halt) Reason
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言