banner
ekko

ekko's blog

时间不在于你拥有多少,而在于你怎样使用
github
xbox
email

openocd gdb 断点原理

本文主要分析 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 Can not find free Breakpoint Register Pair");
			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_BASECPUV8_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;

Pasted image 20240513174604
CPUV8_DBG_BVR_BASE是断点数据寄存器,可以看到它的值依赖与控制寄存器的 BT,openocd 中对应的 BT 为 0,对应的数据寄存器中存储的就是虚拟地址。
Pasted image 20240513174703

Pasted image 20240513174841
Pasted image 20240513174854
从代码角度存入数据寄存器的值为: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("state ARM_STATE_AARCH64");
			opcode = ARMV8_HLT(11);

			if (breakpoint->length != 4)
				LOG_ERROR("bug: breakpoint length should be 4 in AArch64 mode");
		} else {
			/**
			 * core_state is ARM_STATE_ARM
			 * in that case the opcode depends on breakpoint length:
			 *  - if length == 4 => A32 opcode
			 *  - if length == 2 => T32 opcode
			 *  - if length == 3 => T32 opcode (refer to gdb doc : ARM-Breakpoint-Kinds)
			 *    in that case the length should be changed from 3 to 4 bytes
			 **/
			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;
	}

Pasted image 20240513111418
Pasted image 20240513111432
软件断点的原理就是在需要设置断点的指令处使用 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,有错误产生,从出现这个错误后,再写内存也都会失败,并且此时断点也无法正常跳出
Pasted image 20240513145931
发现了刚进入 gdb 时写内存正常,到 main 函数后失败,经过测试是在 mmu 初始化后写内存就失败了,想到了之前 gdbstub 代码段写权限问题,也增加了写权限后问题解决!

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。