banner
ekko

ekko's blog

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

OpenOCD GDB breakpoint principle

This article mainly analyzes the process of adding hardware breakpoints and software breakpoints in openocd.

Hardware Breakpoints#

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);

	}

The implementation of hardware breakpoints mainly relies on two registers, CPUV8_DBG_BCR_BASE and CPUV8_DBG_BVR_BASE.
CPUV8_DBG_BCR_BASE is the breakpoint control register, and you can see that the second-to-last bit of the address is stored in it.

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 is the breakpoint data register, and you can see that its value depends on the BT of the control register. In openocd, the corresponding BT is 0, and the data register stores the virtual address.
Pasted image 20240513174703

Pasted image 20240513174841
Pasted image 20240513174854
From the perspective of the code, the value stored in the data register is: breakpoint->address & 0xFFFFFFFFFFFFFFFCULL;, which is the breakpoint address except for the last two bits.

Software Breakpoints#

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
The principle of software breakpoints is to use HLT to replace the original instruction at the breakpoint location. When the program reaches this address, a halt exception will be triggered. At this time, jlink takes over the program and restores the breakpoint instruction to the original instruction, which is basically the same as the gdbstub principle.
The difference is that the triggering exception instruction used is not HLT, and the reading and writing of memory is forwarded through the debug register.
The problem with the software breakpoints encountered earlier was caused by an error in restoring the original instruction.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.