diff options
author | Robert Zieba <robertzieba@google.com> | 2022-04-14 10:36:15 -0600 |
---|---|---|
committer | Nick Vaccaro <nvaccaro@google.com> | 2022-05-12 15:47:18 +0000 |
commit | 3f01cd14533f12f04a87a9cf1111dd948094bac4 (patch) | |
tree | e1ef98e60c0f91fd59fc14f3983525d21c62760a | |
parent | 4be0f4bf9943f5f6c84d1c13dee70b7442f99bd0 (diff) |
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>
-rw-r--r-- | src/arch/x86/Kconfig | 32 | ||||
-rw-r--r-- | src/arch/x86/Makefile.inc | 12 | ||||
-rw-r--r-- | src/arch/x86/breakpoint.c | 300 | ||||
-rw-r--r-- | src/arch/x86/exception.c | 13 | ||||
-rw-r--r-- | src/arch/x86/include/arch/breakpoint.h | 58 | ||||
-rw-r--r-- | src/arch/x86/include/arch/null_breakpoint.h | 16 | ||||
-rw-r--r-- | src/arch/x86/null_breakpoint.c | 57 |
7 files changed, 486 insertions, 2 deletions
diff --git a/src/arch/x86/Kconfig b/src/arch/x86/Kconfig index e9fce5026f..993b1e6608 100644 --- a/src/arch/x86/Kconfig +++ b/src/arch/x86/Kconfig @@ -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 diff --git a/src/arch/x86/Makefile.inc b/src/arch/x86/Makefile.inc index 80cd9cf826..e5d52ef390 100644 --- a/src/arch/x86/Makefile.inc +++ b/src/arch/x86/Makefile.inc @@ -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) diff --git a/src/arch/x86/breakpoint.c b/src/arch/x86/breakpoint.c new file mode 100644 index 0000000000..4b21e32823 --- /dev/null +++ b/src/arch/x86/breakpoint.c @@ -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; +} diff --git a/src/arch/x86/exception.c b/src/arch/x86/exception.c index 624226e36e..5f6c0fc641 100644 --- a/src/arch/x86/exception.c +++ b/src/arch/x86/exception.c @@ -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(); } diff --git a/src/arch/x86/include/arch/breakpoint.h b/src/arch/x86/include/arch/breakpoint.h new file mode 100644 index 0000000000..32e9f48d09 --- /dev/null +++ b/src/arch/x86/include/arch/breakpoint.h @@ -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_ */ diff --git a/src/arch/x86/include/arch/null_breakpoint.h b/src/arch/x86/include/arch/null_breakpoint.h new file mode 100644 index 0000000000..bc86dc03e4 --- /dev/null +++ b/src/arch/x86/include/arch/null_breakpoint.h @@ -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_ */ diff --git a/src/arch/x86/null_breakpoint.c b/src/arch/x86/null_breakpoint.c new file mode 100644 index 0000000000..8b21a77cdc --- /dev/null +++ b/src/arch/x86/null_breakpoint.c @@ -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(); +} |