本文主要分析 openocd 添加硬件斷點與軟件斷點的流程
硬件斷點#
if (breakpoint->type == BKPT_HARD) {
int64_t bpt_value;
while (brp_list[brp_i].used && (brp_i < aarch64->brp_num))
brp_i++;
if (brp_i >= aarch64->brp_num) {
LOG_ERROR("錯誤:無法找到空閒的斷點寄存器對");
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
breakpoint_hw_set(breakpoint, brp_i);
if (breakpoint->length == 2)
byte_addr_select = (3 << (breakpoint->address & 0x02));
control = ((matchmode & 0x7) << 20)
| (1 << 13)
| (byte_addr_select << 5)
| (3 << 1) | 1;
brp_list[brp_i].used = 1;
brp_list[brp_i].value = breakpoint->address & 0xFFFFFFFFFFFFFFFCULL;
brp_list[brp_i].control = control;
bpt_value = brp_list[brp_i].value;
retval = aarch64_dap_write_memap_register_u32(target, armv8->debug_base
+ CPUV8_DBG_BVR_BASE + 16 * brp_list[brp_i].brpn,
(uint32_t)(bpt_value & 0xFFFFFFFF));
if (retval != ERROR_OK)
return retval;
retval = aarch64_dap_write_memap_register_u32(target, armv8->debug_base
+ CPUV8_DBG_BVR_BASE + 4 + 16 * brp_list[brp_i].brpn,
(uint32_t)(bpt_value >> 32));
if (retval != ERROR_OK)
return retval;
retval = aarch64_dap_write_memap_register_u32(target, armv8->debug_base
+ CPUV8_DBG_BCR_BASE + 16 * brp_list[brp_i].brpn,
brp_list[brp_i].control);
if (retval != ERROR_OK)
return retval;
LOG_DEBUG("brp %i control 0x%0" PRIx32 " value 0x%" TARGET_PRIxADDR, brp_i,
brp_list[brp_i].control,
brp_list[brp_i].value);
}
硬件斷點的實現主要依靠兩個寄存器,CPUV8_DBG_BCR_BASE
和CPUV8_DBG_BVR_BASE
。
CPUV8_DBG_BCR_BASE
是斷點控制寄存器,可以看到存入了地址的倒數第二個 bit
if (breakpoint->length == 2)
byte_addr_select = (3 << (breakpoint->address & 0x02));
control = ((matchmode & 0x7) << 20)
| (1 << 13)
| (byte_addr_select << 5)
| (3 << 1) | 1;
CPUV8_DBG_BVR_BASE
是斷點數據寄存器,可以看到它的值依賴於控制寄存器的 BT,openocd 中對應的 BT 為 0,對應的數據寄存器中存儲的就是虛擬地址。
從代碼角度存入數據寄存器的值為:breakpoint->address & 0xFFFFFFFFFFFFFFFCULL;
就是斷點地址除了最後兩個 bit
軟件斷點#
if (breakpoint->type == BKPT_SOFT) {
uint32_t opcode;
uint8_t code[4];
if (armv8_dpm_get_core_state(&armv8->dpm) == ARM_STATE_AARCH64) {
LOG_INFO("狀態 ARM_STATE_AARCH64");
opcode = ARMV8_HLT(11);
if (breakpoint->length != 4)
LOG_ERROR("錯誤:在AArch64模式下斷點長度應為4");
} else {
/**
* core_state是ARM_STATE_ARM
* 在這種情況下,操作碼取決於斷點長度:
* - 如果長度 == 4 => A32操作碼
* - 如果長度 == 2 => T32操作碼
* - 如果長度 == 3 => T32操作碼(參考gdb文檔:ARM-Breakpoint-Kinds)
* 在這種情況下,長度應該從3更改為4字節
**/
opcode = (breakpoint->length == 4) ? ARMV8_HLT_A1(11) :
(uint32_t) (ARMV8_HLT_T1(11) | ARMV8_HLT_T1(11) << 16);
if (breakpoint->length == 3)
breakpoint->length = 4;
}
LOG_INFO("opcode:0x%X",opcode);
buf_set_u32(code, 0, 32, opcode);
retval = target_read_memory(target,
breakpoint->address & 0xFFFFFFFFFFFFFFFEULL,
breakpoint->length, 1,
breakpoint->orig_instr);
if (retval != ERROR_OK)
return retval;
armv8_cache_d_inner_flush_virt(armv8,
breakpoint->address & 0xFFFFFFFFFFFFFFFEULL,
breakpoint->length);
retval = target_write_memory(target,
breakpoint->address & 0xFFFFFFFFFFFFFFFEULL,
breakpoint->length, 1, code);
if (retval != ERROR_OK)
return retval;
armv8_cache_d_inner_flush_virt(armv8,
breakpoint->address & 0xFFFFFFFFFFFFFFFEULL,
breakpoint->length);
armv8_cache_i_inner_inval_virt(armv8,
breakpoint->address & 0xFFFFFFFFFFFFFFFEULL,
breakpoint->length);
breakpoint->is_set = true;
}
軟件斷點的原理就是在需要設置斷點的指令處使用 HLT 替換原始指令,程序運行到該地址時就會觸發 halt 異常,此時由 jlink 接管程序,並將斷點指令還原回原始指令,與 gdbstub 原理基本相同。
不同的是使用的觸發異常指令為 HLT,以及讀寫內存的方式是通過調試寄存器轉發。
而之前遇到的軟件斷點問題就是在還原原始指令時出錯導致。
Info : breakpoint_add
Info : breakpoint_add_internal type:1, add:0x4000134C
Info : aarch64_set_breakpoint type:1,addr:0x4000134C
Info : state ARM_STATE_AARCH64
Info : opcode:0xD4400160
Info : begin aarch64_write_cpu_memory,addr:4000134C
Info : write:0xd440160
Info : begin aarch64_write_cpu_memory_fast
Info : [rk3568.lcore0] added software breakpoint at 0x4000134c of length 0x00000004, (BPID: 0)
Info : begin aarch64_write_cpu_memory,addr:4000134C
Info : write:0xa9be7bfd
Info : begin aarch64_write_cpu_memory_fast
Error: abort occurred - dscr = 0x03047d6f
(gdb) b main
Breakpoint 1 at 0x4000134c: file /home/ekko/zephyrproject/zephyr/samples/hello_world/src/main.c, line 67.
(gdb) c
Continuing.
Breakpoint 1, main () at /home/ekko/zephyrproject/zephyr/samples/hello_world/src/main.c:67
67 {
(gdb)
0xd440160
就是 HLT 觸發指令,可以看到這個寫成功了,繼續運行到 main 停止,此時需要將原始的0xa9be7bfd
還原回去,但是出現了錯誤,此時dscr
寄存器的值為0x03047d6f
,bit6 和 bit7 都為 1,有錯誤產生,從出現這個錯誤後,再寫內存也都會失敗,並且此時斷點也無法正常跳出
發現了剛進入 gdb 時寫內存正常,到 main 函數後失敗,經過測試是在 mmu 初始化後寫內存就失敗了,想到了之前 gdbstub 代碼段寫權限問題,也增加了寫權限後問題解決!