本文主要分析 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("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);
}
ハードウェアブレークポイントの実装は主に 2 つのレジスタ、CPUV8_DBG_BCR_BASE
とCPUV8_DBG_BVR_BASE
に依存しています。
CPUV8_DBG_BCR_BASE
はブレークポイント制御レジスタで、アドレスの倒数第二のビットが格納されているのがわかります。
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;
であり、これはブレークポイントアドレスの最後の 2 ビットを除いたものです。
ソフトウェアブレークポイント#
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です
* この場合、opcodeはブレークポイントの長さに依存します:
* - 長さ == 4 => A32 opcode
* - 長さ == 2 => T32 opcode
* - 長さ == 3 => T32 opcode (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 : 状態 ARM_STATE_AARCH64
Info : opcode:0xD4400160
Info : aarch64_write_cpu_memoryを開始、addr:4000134C
Info : 書き込み:0xd440160
Info : aarch64_write_cpu_memory_fastを開始
Info : [rk3568.lcore0] 0x4000134cの長さ0x00000004のソフトウェアブレークポイントを追加しました (BPID: 0)
Info : aarch64_write_cpu_memoryを開始、addr:4000134C
Info : 書き込み:0xa9be7bfd
Info : 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
続行中。
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 コードセクションの書き込み権限の問題を思い出し、書き込み権限を追加したところ、問題が解決しました!