arch/x86: Add support for catching null dereferences through debug regs
This commit adds support for catching null dereferences and execution through x86's debug registers. This is particularly useful when running 32-bit coreboot as paging is not enabled to catch these through page faults. This commit adds three new configs to support this feature: DEBUG_HW_BREAKPOINTS, DEBUG_NULL_DEREF_BREAKPOINTS and DEBUG_NULL_DEREF_HALT. BUG=b:223902046 TEST=Ran on nipperkin device, verifying that HW breakpoints work as expected. Change-Id: I113590689046a13c2a552741bbfe7668a834354a Signed-off-by: Robert Zieba <robertzieba@google.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/63657 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Raul Rangel <rrangel@chromium.org>
This commit is contained in:
		
				
					committed by
					
						
						Nick Vaccaro
					
				
			
			
				
	
			
			
			
						parent
						
							4be0f4bf99
						
					
				
				
					commit
					3f01cd1453
				
			@@ -320,6 +320,38 @@ config MEMLAYOUT_LD_FILE
 | 
			
		||||
	string
 | 
			
		||||
	default "src/arch/x86/memlayout.ld"
 | 
			
		||||
 | 
			
		||||
config DEBUG_HW_BREAKPOINTS
 | 
			
		||||
	bool
 | 
			
		||||
	default y
 | 
			
		||||
	help
 | 
			
		||||
	  Enable support for hardware data and instruction breakpoints through
 | 
			
		||||
	  the x86 debug registers
 | 
			
		||||
 | 
			
		||||
config DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES
 | 
			
		||||
	bool
 | 
			
		||||
	default y
 | 
			
		||||
	depends on DEBUG_HW_BREAKPOINTS && IDT_IN_EVERY_STAGE
 | 
			
		||||
 | 
			
		||||
config DEBUG_NULL_DEREF_BREAKPOINTS
 | 
			
		||||
	bool
 | 
			
		||||
	default y
 | 
			
		||||
	depends on DEBUG_HW_BREAKPOINTS
 | 
			
		||||
	help
 | 
			
		||||
	  Enable support for catching null dereferences and instruction execution
 | 
			
		||||
 | 
			
		||||
config DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES
 | 
			
		||||
	bool
 | 
			
		||||
	default y
 | 
			
		||||
	depends on DEBUG_NULL_DEREF_BREAKPOINTS && DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES
 | 
			
		||||
 | 
			
		||||
config DEBUG_NULL_DEREF_HALT
 | 
			
		||||
	bool
 | 
			
		||||
	default n
 | 
			
		||||
	depends on DEBUG_NULL_DEREF_BREAKPOINTS
 | 
			
		||||
	help
 | 
			
		||||
	  When enabled null dereferences and instruction fetches will halt execution.
 | 
			
		||||
	  Otherwise an error will be printed.
 | 
			
		||||
 | 
			
		||||
# Some EC need an "EC firmware pointer" (a data structure hinting the address
 | 
			
		||||
# of its firmware blobs) being put at a fixed position. Its space
 | 
			
		||||
# (__section__(".ecfw_ptr")) should be reserved if it lies in the range of a
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ endef
 | 
			
		||||
ifeq ($(CONFIG_ARCH_BOOTBLOCK_X86_32)$(CONFIG_ARCH_BOOTBLOCK_X86_64),y)
 | 
			
		||||
 | 
			
		||||
bootblock-y += boot.c
 | 
			
		||||
bootblock-$(CONFIG_DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) += breakpoint.c
 | 
			
		||||
bootblock-y += post.c
 | 
			
		||||
bootblock-y += cpu_common.c
 | 
			
		||||
bootblock-$(CONFIG_IDT_IN_EVERY_STAGE) += exception.c
 | 
			
		||||
@@ -87,6 +88,7 @@ bootblock-y += memset.c
 | 
			
		||||
bootblock-y += memmove.c
 | 
			
		||||
bootblock-$(CONFIG_COLLECT_TIMESTAMPS_TSC) += timestamp.c
 | 
			
		||||
bootblock-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
bootblock-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) += null_breakpoint.c
 | 
			
		||||
bootblock-$(CONFIG_BOOTBLOCK_NORMAL) += bootblock_normal.c
 | 
			
		||||
bootblock-y += gdt_init.S
 | 
			
		||||
bootblock-y += id.S
 | 
			
		||||
@@ -122,6 +124,7 @@ ifeq ($(CONFIG_ARCH_VERSTAGE_X86_32)$(CONFIG_ARCH_VERSTAGE_X86_64),y)
 | 
			
		||||
 | 
			
		||||
verstage-$(CONFIG_VBOOT_SEPARATE_VERSTAGE) += assembly_entry.S
 | 
			
		||||
verstage-y += boot.c
 | 
			
		||||
verstage-$(CONFIG_DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) += breakpoint.c
 | 
			
		||||
verstage-y += post.c
 | 
			
		||||
verstage-$(CONFIG_VBOOT_SEPARATE_VERSTAGE) += gdt_init.S
 | 
			
		||||
verstage-$(CONFIG_IDT_IN_EVERY_STAGE) += exception.c
 | 
			
		||||
@@ -133,6 +136,7 @@ verstage-y += memset.c
 | 
			
		||||
verstage-y += memcpy.c
 | 
			
		||||
verstage-y += memmove.c
 | 
			
		||||
verstage-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
verstage-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) += null_breakpoint.c
 | 
			
		||||
# If verstage is a separate stage it means there's no need
 | 
			
		||||
# for a chipset-specific car_stage_entry() so use the generic one
 | 
			
		||||
# which just calls verstage().
 | 
			
		||||
@@ -158,6 +162,7 @@ ifeq ($(CONFIG_ARCH_ROMSTAGE_X86_32)$(CONFIG_ARCH_ROMSTAGE_X86_64),y)
 | 
			
		||||
 | 
			
		||||
romstage-y += assembly_entry.S
 | 
			
		||||
romstage-y += boot.c
 | 
			
		||||
romstage-$(CONFIG_DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) += breakpoint.c
 | 
			
		||||
romstage-y += post.c
 | 
			
		||||
romstage-y += gdt_init.S
 | 
			
		||||
romstage-y += cpu_common.c
 | 
			
		||||
@@ -167,6 +172,7 @@ romstage-y += memcpy.c
 | 
			
		||||
romstage-y += memmove.c
 | 
			
		||||
romstage-y += memset.c
 | 
			
		||||
romstage-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
romstage-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) += null_breakpoint.c
 | 
			
		||||
romstage-y += postcar_loader.c
 | 
			
		||||
romstage-$(CONFIG_COLLECT_TIMESTAMPS_TSC) += timestamp.c
 | 
			
		||||
romstage-$(CONFIG_HAVE_CF9_RESET) += cf9_reset.c
 | 
			
		||||
@@ -199,6 +205,7 @@ endif
 | 
			
		||||
postcar-generic-ccopts += -D__POSTCAR__
 | 
			
		||||
 | 
			
		||||
postcar-y += boot.c
 | 
			
		||||
postcar-$(CONFIG_DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) += breakpoint.c
 | 
			
		||||
postcar-y += post.c
 | 
			
		||||
postcar-y += gdt_init.S
 | 
			
		||||
postcar-y += cpu_common.c
 | 
			
		||||
@@ -209,6 +216,7 @@ postcar-y += memcpy.c
 | 
			
		||||
postcar-y += memmove.c
 | 
			
		||||
postcar-y += memset.c
 | 
			
		||||
postcar-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
postcar-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) += null_breakpoint.c
 | 
			
		||||
postcar-y += postcar.c
 | 
			
		||||
postcar-$(CONFIG_COLLECT_TIMESTAMPS_TSC) += timestamp.c
 | 
			
		||||
postcar-$(CONFIG_HAVE_CF9_RESET) += cf9_reset.c
 | 
			
		||||
@@ -243,6 +251,7 @@ ramstage-y += c_start.S
 | 
			
		||||
ramstage-y += c_exit.S
 | 
			
		||||
ramstage-y += cpu.c
 | 
			
		||||
ramstage-y += cpu_common.c
 | 
			
		||||
ramstage-$(CONFIG_DEBUG_HW_BREAKPOINTS) += breakpoint.c
 | 
			
		||||
ramstage-y += ebda.c
 | 
			
		||||
ramstage-y += exception.c
 | 
			
		||||
ramstage-y += idt.S
 | 
			
		||||
@@ -252,6 +261,7 @@ ramstage-y += memmove.c
 | 
			
		||||
ramstage-y += memset.c
 | 
			
		||||
ramstage-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
ramstage-$(CONFIG_GENERATE_MP_TABLE) += mpspec.c
 | 
			
		||||
ramstage-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS) += null_breakpoint.c
 | 
			
		||||
ramstage-$(CONFIG_GENERATE_PIRQ_TABLE) += pirq_routing.c
 | 
			
		||||
ramstage-y += rdrand.c
 | 
			
		||||
ramstage-$(CONFIG_GENERATE_SMBIOS_TABLES) += smbios.c
 | 
			
		||||
@@ -306,11 +316,13 @@ $(objgenerated)/ramstage.o: $$(ramstage-objs) $(COMPILER_RT_ramstage) $$(ramstag
 | 
			
		||||
 | 
			
		||||
endif # CONFIG_ARCH_RAMSTAGE_X86_32 / CONFIG_ARCH_RAMSTAGE_X86_64
 | 
			
		||||
 | 
			
		||||
smm-$(CONFIG_DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) += breakpoint.c
 | 
			
		||||
smm-$(CONFIG_IDT_IN_EVERY_STAGE) += exception.c
 | 
			
		||||
smm-$(CONFIG_IDT_IN_EVERY_STAGE) += idt.S
 | 
			
		||||
smm-y += memcpy.c
 | 
			
		||||
smm-y += memmove.c
 | 
			
		||||
smm-y += memset.c
 | 
			
		||||
smm-$(CONFIG_X86_TOP4G_BOOTMEDIA_MAP) += mmap_boot.c
 | 
			
		||||
smm-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) += null_breakpoint.c
 | 
			
		||||
 | 
			
		||||
smm-srcs += $(wildcard src/mainboard/$(MAINBOARDDIR)/smihandler.c)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										300
									
								
								src/arch/x86/breakpoint.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								src/arch/x86/breakpoint.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
			
		||||
/* SPDX-License-Identifier: GPL-2.0-only */
 | 
			
		||||
#include <arch/registers.h>
 | 
			
		||||
#include <arch/breakpoint.h>
 | 
			
		||||
#include <console/console.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <types.h>
 | 
			
		||||
 | 
			
		||||
#define DEBUG_REGISTER_COUNT 4
 | 
			
		||||
 | 
			
		||||
/* Each enable field is 2 bits and starts at bit 0 */
 | 
			
		||||
#define DEBUG_CTRL_ENABLE_SHIFT(index) (2 * (index))
 | 
			
		||||
#define DEBUG_CTRL_ENABLE_MASK(index) (0x3 << DEBUG_CTRL_ENABLE_SHIFT(index))
 | 
			
		||||
#define DEBUG_CTRL_ENABLE(index, enable) ((enable) << DEBUG_CTRL_ENABLE_SHIFT(index))
 | 
			
		||||
 | 
			
		||||
/* Each breakpoint has a length and type, each is two bits and start at bit 16 */
 | 
			
		||||
#define DEBUG_CTRL_LT_SHIFT(index) (4 * (index) + 16)
 | 
			
		||||
#define DEBUG_CTRL_LT_MASK(index) (0xf << DEBUG_CTRL_LT_SHIFT(index))
 | 
			
		||||
#define DEBUG_CTRL_LT(index, len, type) ((((len) << 2 | (type))) << DEBUG_CTRL_LT_SHIFT(index))
 | 
			
		||||
 | 
			
		||||
/* Each field is one bit, starting at bit 0 */
 | 
			
		||||
#define DEBUG_STATUS_BP_HIT_MASK(index) (1 << (index))
 | 
			
		||||
#define DEBUG_STATUS_GET_BP_HIT(index, value) \
 | 
			
		||||
	(((value) & DEBUG_STATUS_BP_HIT_MASK(index)) >> (index))
 | 
			
		||||
 | 
			
		||||
/* Breakpoint lengths values */
 | 
			
		||||
#define DEBUG_CTRL_LEN_1 0x0
 | 
			
		||||
#define DEBUG_CTRL_LEN_2 0x1
 | 
			
		||||
#define DEBUG_CTRL_LEN_8 0x2
 | 
			
		||||
#define DEBUG_CTRL_LEN_4 0x3
 | 
			
		||||
 | 
			
		||||
/* Breakpoint enable values */
 | 
			
		||||
#define DEBUG_CTRL_ENABLE_LOCAL 0x1
 | 
			
		||||
#define DEBUG_CTRL_ENABLE_GLOBAL 0x2
 | 
			
		||||
 | 
			
		||||
/* eflags/rflags bit to continue execution after hitting an instruction breakpoint */
 | 
			
		||||
#define FLAGS_RESUME (1 << 16)
 | 
			
		||||
 | 
			
		||||
struct breakpoint {
 | 
			
		||||
	bool allocated;
 | 
			
		||||
	enum breakpoint_type type;
 | 
			
		||||
	breakpoint_handler handler;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct breakpoint breakpoints[DEBUG_REGISTER_COUNT];
 | 
			
		||||
 | 
			
		||||
static inline bool debug_write_addr_reg(int index, uintptr_t value)
 | 
			
		||||
{
 | 
			
		||||
	switch (index) {
 | 
			
		||||
	case 0:
 | 
			
		||||
		asm("mov %0, %%dr0" ::"r"(value));
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 1:
 | 
			
		||||
		asm("mov %0, %%dr1" ::"r"(value));
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 2:
 | 
			
		||||
		asm("mov %0, %%dr2" ::"r"(value));
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 3:
 | 
			
		||||
		asm("mov %0, %%dr3" ::"r"(value));
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uintptr_t debug_read_status(void)
 | 
			
		||||
{
 | 
			
		||||
	uintptr_t ret = 0;
 | 
			
		||||
 | 
			
		||||
	asm("mov %%dr6, %0" : "=r"(ret));
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void debug_write_status(uintptr_t value)
 | 
			
		||||
{
 | 
			
		||||
	asm("mov %0, %%dr6" ::"r"(value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uintptr_t debug_read_control(void)
 | 
			
		||||
{
 | 
			
		||||
	uintptr_t ret = 0;
 | 
			
		||||
 | 
			
		||||
	asm("mov %%dr7, %0" : "=r"(ret));
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void debug_write_control(uintptr_t value)
 | 
			
		||||
{
 | 
			
		||||
	asm("mov %0, %%dr7" ::"r"(value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static enum breakpoint_result allocate_breakpoint(struct breakpoint_handle *out_handle,
 | 
			
		||||
						  enum breakpoint_type type)
 | 
			
		||||
{
 | 
			
		||||
	for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
 | 
			
		||||
		if (breakpoints[i].allocated)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		breakpoints[i].allocated = true;
 | 
			
		||||
		breakpoints[i].handler = NULL;
 | 
			
		||||
		breakpoints[i].type = type;
 | 
			
		||||
		out_handle->bp = i;
 | 
			
		||||
		return BREAKPOINT_RES_OK;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return BREAKPOINT_RES_NONE_AVAILABLE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static enum breakpoint_result validate_handle(struct breakpoint_handle handle)
 | 
			
		||||
{
 | 
			
		||||
	int bp = handle.bp;
 | 
			
		||||
 | 
			
		||||
	if (bp < 0 || bp >= DEBUG_REGISTER_COUNT || !breakpoints[bp].allocated)
 | 
			
		||||
		return BREAKPOINT_RES_INVALID_HANDLE;
 | 
			
		||||
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_create_instruction(struct breakpoint_handle *out_handle,
 | 
			
		||||
						     void *virt_addr)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res =
 | 
			
		||||
		allocate_breakpoint(out_handle, BREAKPOINT_TYPE_INSTRUCTION);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	int bp = out_handle->bp;
 | 
			
		||||
	if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
 | 
			
		||||
		return BREAKPOINT_RES_INVALID_HANDLE;
 | 
			
		||||
 | 
			
		||||
	uintptr_t control = debug_read_control();
 | 
			
		||||
	control &= ~DEBUG_CTRL_LT_MASK(bp);
 | 
			
		||||
	control |= DEBUG_CTRL_LT(bp, DEBUG_CTRL_LEN_1, BREAKPOINT_TYPE_INSTRUCTION);
 | 
			
		||||
	debug_write_control(control);
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_create_data(struct breakpoint_handle *out_handle,
 | 
			
		||||
					      void *virt_addr, size_t len, bool write_only)
 | 
			
		||||
{
 | 
			
		||||
	uintptr_t len_value = 0;
 | 
			
		||||
 | 
			
		||||
	switch (len) {
 | 
			
		||||
	case 1:
 | 
			
		||||
		len_value = DEBUG_CTRL_LEN_1;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 2:
 | 
			
		||||
		len_value = DEBUG_CTRL_LEN_2;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 4:
 | 
			
		||||
		len_value = DEBUG_CTRL_LEN_4;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case 8:
 | 
			
		||||
		/* Only supported on 64-bit CPUs */
 | 
			
		||||
		if (!ENV_X86_64)
 | 
			
		||||
			return BREAKPOINT_RES_INVALID_LENGTH;
 | 
			
		||||
		len_value = DEBUG_CTRL_LEN_8;
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return BREAKPOINT_RES_INVALID_LENGTH;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	enum breakpoint_type type =
 | 
			
		||||
		write_only ? BREAKPOINT_TYPE_DATA_WRITE : BREAKPOINT_TYPE_DATA_RW;
 | 
			
		||||
	enum breakpoint_result res = allocate_breakpoint(out_handle, type);
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	int bp = out_handle->bp;
 | 
			
		||||
	if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
 | 
			
		||||
		return BREAKPOINT_RES_INVALID_HANDLE;
 | 
			
		||||
 | 
			
		||||
	uintptr_t control = debug_read_control();
 | 
			
		||||
	control &= ~DEBUG_CTRL_LT_MASK(bp);
 | 
			
		||||
	control |= DEBUG_CTRL_LT(bp, len_value, type);
 | 
			
		||||
	debug_write_control(control);
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_remove(struct breakpoint_handle handle)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = validate_handle(handle);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
	breakpoint_enable(handle, false);
 | 
			
		||||
 | 
			
		||||
	int bp = handle.bp;
 | 
			
		||||
	breakpoints[bp].allocated = false;
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_enable(struct breakpoint_handle handle, bool enabled)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = validate_handle(handle);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	uintptr_t control = debug_read_control();
 | 
			
		||||
	int bp = handle.bp;
 | 
			
		||||
	control &= ~DEBUG_CTRL_ENABLE_MASK(bp);
 | 
			
		||||
	if (enabled)
 | 
			
		||||
		control |= DEBUG_CTRL_ENABLE(bp, DEBUG_CTRL_ENABLE_GLOBAL);
 | 
			
		||||
	debug_write_control(control);
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_get_type(struct breakpoint_handle handle,
 | 
			
		||||
					   enum breakpoint_type *type)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = validate_handle(handle);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	*type = breakpoints[handle.bp].type;
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result breakpoint_set_handler(struct breakpoint_handle handle,
 | 
			
		||||
					      breakpoint_handler handler)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = validate_handle(handle);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	breakpoints[handle.bp].handler = handler;
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static enum breakpoint_result is_breakpoint_hit(struct breakpoint_handle handle, bool *out_hit)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = validate_handle(handle);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK)
 | 
			
		||||
		return res;
 | 
			
		||||
 | 
			
		||||
	uintptr_t status = debug_read_status();
 | 
			
		||||
	*out_hit = DEBUG_STATUS_GET_BP_HIT(handle.bp, status);
 | 
			
		||||
 | 
			
		||||
	return BREAKPOINT_RES_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int breakpoint_dispatch_handler(struct eregs *info)
 | 
			
		||||
{
 | 
			
		||||
	bool instr_bp_hit = 0;
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
 | 
			
		||||
		struct breakpoint_handle handle = { i };
 | 
			
		||||
		bool hit = false;
 | 
			
		||||
		enum breakpoint_type type;
 | 
			
		||||
 | 
			
		||||
		if (is_breakpoint_hit(handle, &hit) != BREAKPOINT_RES_OK || !hit)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (breakpoint_get_type(handle, &type) != BREAKPOINT_RES_OK)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		instr_bp_hit |= type == BREAKPOINT_TYPE_INSTRUCTION;
 | 
			
		||||
 | 
			
		||||
		/* Call the breakpoint handler. */
 | 
			
		||||
		if (breakpoints[handle.bp].handler) {
 | 
			
		||||
			int ret = breakpoints[handle.bp].handler(handle, info);
 | 
			
		||||
			/* A non-zero return value indicates a fatal error. */
 | 
			
		||||
			if (ret)
 | 
			
		||||
				return ret;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Clear hit breakpoints. */
 | 
			
		||||
	uintptr_t status = debug_read_status();
 | 
			
		||||
	for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
 | 
			
		||||
		status &= ~DEBUG_STATUS_BP_HIT_MASK(i);
 | 
			
		||||
	}
 | 
			
		||||
	debug_write_status(status);
 | 
			
		||||
 | 
			
		||||
	if (instr_bp_hit) {
 | 
			
		||||
		/* Set the resume flag so the same breakpoint won't be hit immediately. */
 | 
			
		||||
#if ENV_X86_64
 | 
			
		||||
		info->rflags |= FLAGS_RESUME;
 | 
			
		||||
#else
 | 
			
		||||
		info->eflags |= FLAGS_RESUME;
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
/* SPDX-License-Identifier: GPL-2.0-only */
 | 
			
		||||
 | 
			
		||||
#include <arch/cpu.h>
 | 
			
		||||
#include <arch/breakpoint.h>
 | 
			
		||||
#include <arch/null_breakpoint.h>
 | 
			
		||||
#include <arch/exception.h>
 | 
			
		||||
#include <arch/registers.h>
 | 
			
		||||
#include <commonlib/helpers.h>
 | 
			
		||||
#include <console/console.h>
 | 
			
		||||
#include <console/streams.h>
 | 
			
		||||
@@ -371,7 +373,7 @@ static void put_packet(char *buffer)
 | 
			
		||||
}
 | 
			
		||||
#endif /* CONFIG_GDB_STUB */
 | 
			
		||||
 | 
			
		||||
#include <arch/registers.h>
 | 
			
		||||
#define DEBUG_VECTOR 1
 | 
			
		||||
 | 
			
		||||
void x86_exception(struct eregs *info);
 | 
			
		||||
 | 
			
		||||
@@ -488,6 +490,11 @@ void x86_exception(struct eregs *info)
 | 
			
		||||
	int logical_processor = 0;
 | 
			
		||||
	u32 apic_id = CONFIG(SMP) ? lapicid() : 0;
 | 
			
		||||
 | 
			
		||||
	if (info->vector == DEBUG_VECTOR) {
 | 
			
		||||
		if (breakpoint_dispatch_handler(info) == 0)
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#if ENV_RAMSTAGE
 | 
			
		||||
	logical_processor = cpu_index();
 | 
			
		||||
#endif
 | 
			
		||||
@@ -657,4 +664,6 @@ asmlinkage void exception_init(void)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	load_idt(idt, sizeof(idt));
 | 
			
		||||
 | 
			
		||||
	null_breakpoint_init();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								src/arch/x86/include/arch/breakpoint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/arch/x86/include/arch/breakpoint.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
/* SPDX-License-Identifier: GPL-2.0-only */
 | 
			
		||||
#ifndef _BREAKPOINT_H_
 | 
			
		||||
#define _BREAKPOINT_H_
 | 
			
		||||
 | 
			
		||||
#include <arch/registers.h>
 | 
			
		||||
#include <types.h>
 | 
			
		||||
 | 
			
		||||
#if CONFIG(DEBUG_HW_BREAKPOINTS) && \
 | 
			
		||||
	(CONFIG(DEBUG_HW_BREAKPOINTS_IN_ALL_STAGES) || ENV_RAMSTAGE)
 | 
			
		||||
struct breakpoint_handle {
 | 
			
		||||
	int bp;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef int (*breakpoint_handler)(struct breakpoint_handle, struct eregs *info);
 | 
			
		||||
 | 
			
		||||
enum breakpoint_result {
 | 
			
		||||
	BREAKPOINT_RES_OK = 0,
 | 
			
		||||
	BREAKPOINT_RES_NONE_AVAILABLE = -1,
 | 
			
		||||
	BREAKPOINT_RES_INVALID_HANDLE = -2,
 | 
			
		||||
	BREAKPOINT_RES_INVALID_LENGTH = -3
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum breakpoint_type {
 | 
			
		||||
	BREAKPOINT_TYPE_INSTRUCTION = 0x0,
 | 
			
		||||
	BREAKPOINT_TYPE_DATA_WRITE = 0x1,
 | 
			
		||||
	BREAKPOINT_TYPE_IO = 0x2,
 | 
			
		||||
	BREAKPOINT_TYPE_DATA_RW = 0x3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Creates an instruction breakpoint at the given address. */
 | 
			
		||||
enum breakpoint_result breakpoint_create_instruction(struct breakpoint_handle *out_handle,
 | 
			
		||||
						     void *virt_addr);
 | 
			
		||||
/* Creates a data breakpoint at the given address for len bytes. */
 | 
			
		||||
enum breakpoint_result breakpoint_create_data(struct breakpoint_handle *out_handle,
 | 
			
		||||
					      void *virt_addr, size_t len, bool write_only);
 | 
			
		||||
/* Removes a given breakpoint. */
 | 
			
		||||
enum breakpoint_result breakpoint_remove(struct breakpoint_handle handle);
 | 
			
		||||
/* Enables or disables a given breakpoint. */
 | 
			
		||||
enum breakpoint_result breakpoint_enable(struct breakpoint_handle handle, bool enabled);
 | 
			
		||||
/* Returns the type of a breakpoint. */
 | 
			
		||||
enum breakpoint_result breakpoint_get_type(struct breakpoint_handle handle,
 | 
			
		||||
					   enum breakpoint_type *type);
 | 
			
		||||
/*
 | 
			
		||||
 * Sets a handler function to be called when the breakpoint is hit. The handler should return 0
 | 
			
		||||
 * to continue or any other value to halt execution as a fatal error.
 | 
			
		||||
 */
 | 
			
		||||
enum breakpoint_result breakpoint_set_handler(struct breakpoint_handle handle,
 | 
			
		||||
					      breakpoint_handler handler);
 | 
			
		||||
/* Called by x86_exception to dispatch breakpoint exceptions to the correct handler. */
 | 
			
		||||
int breakpoint_dispatch_handler(struct eregs *info);
 | 
			
		||||
#else
 | 
			
		||||
static inline int breakpoint_dispatch_handler(struct eregs *info)
 | 
			
		||||
{
 | 
			
		||||
	/* Not implemented */
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#endif /* _BREAKPOINT_H_ */
 | 
			
		||||
							
								
								
									
										16
									
								
								src/arch/x86/include/arch/null_breakpoint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/arch/x86/include/arch/null_breakpoint.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
/* SPDX-License-Identifier: GPL-2.0-only */
 | 
			
		||||
#ifndef _NULL_BREAKPOINT_H_
 | 
			
		||||
#define _NULL_BREAKPOINT_H_
 | 
			
		||||
 | 
			
		||||
#if CONFIG(DEBUG_NULL_DEREF_BREAKPOINTS) && \
 | 
			
		||||
    (CONFIG(DEBUG_NULL_DEREF_BREAKPOINTS_IN_ALL_STAGES) || ENV_RAMSTAGE)
 | 
			
		||||
 | 
			
		||||
/* Places data and instructions breakpoints at address zero. */
 | 
			
		||||
void null_breakpoint_init(void);
 | 
			
		||||
#else
 | 
			
		||||
static inline void null_breakpoint_init(void)
 | 
			
		||||
{
 | 
			
		||||
    /* Not implemented */
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#endif /* _NULL_BREAKPOINT_H_ */
 | 
			
		||||
							
								
								
									
										57
									
								
								src/arch/x86/null_breakpoint.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/arch/x86/null_breakpoint.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
/* SPDX-License-Identifier: GPL-2.0-only */
 | 
			
		||||
#include <arch/breakpoint.h>
 | 
			
		||||
#include <arch/null_breakpoint.h>
 | 
			
		||||
#include <console/console.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
static struct breakpoint_handle null_deref_bp;
 | 
			
		||||
static struct breakpoint_handle null_fetch_bp;
 | 
			
		||||
 | 
			
		||||
static int handle_fetch_breakpoint(struct breakpoint_handle handle, struct eregs *regs)
 | 
			
		||||
{
 | 
			
		||||
	printk(BIOS_ERR, "Instruction fetch from address zero\n");
 | 
			
		||||
	return CONFIG(DEBUG_NULL_DEREF_HALT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int handle_deref_breakpoint(struct breakpoint_handle handle, struct eregs *regs)
 | 
			
		||||
{
 | 
			
		||||
#if ENV_X86_64
 | 
			
		||||
	printk(BIOS_ERR, "Null dereference at rip: 0x%llx \n", regs->rip);
 | 
			
		||||
#else
 | 
			
		||||
	printk(BIOS_ERR, "Null dereference at eip: 0x%x \n", regs->eip);
 | 
			
		||||
#endif
 | 
			
		||||
	return CONFIG(DEBUG_NULL_DEREF_HALT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void create_deref_breakpoint(void)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res =
 | 
			
		||||
		breakpoint_create_data(&null_deref_bp, NULL, sizeof(uintptr_t), false);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK) {
 | 
			
		||||
		printk(BIOS_ERR, "Failed to create NULL dereference breakpoint\n");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	breakpoint_set_handler(null_deref_bp, &handle_deref_breakpoint);
 | 
			
		||||
	breakpoint_enable(null_deref_bp, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void create_instruction_breakpoint(void)
 | 
			
		||||
{
 | 
			
		||||
	enum breakpoint_result res = breakpoint_create_instruction(&null_fetch_bp, NULL);
 | 
			
		||||
 | 
			
		||||
	if (res != BREAKPOINT_RES_OK) {
 | 
			
		||||
		printk(BIOS_ERR, "Failed to create address zero instruction fetch breakpoint\n");
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	breakpoint_set_handler(null_fetch_bp, &handle_fetch_breakpoint);
 | 
			
		||||
	breakpoint_enable(null_fetch_bp, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void null_breakpoint_init(void)
 | 
			
		||||
{
 | 
			
		||||
	create_deref_breakpoint();
 | 
			
		||||
	create_instruction_breakpoint();
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user