Robert Zieba has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/63657 )
Change subject: arch/x86: Add support for catching null deferences through debug regs ......................................................................
arch/x86: Add support for catching null deferences through debug regs
This commit adds support for catching null deferences 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. To support this feature three new configs have been added: 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 --- M src/arch/x86/Kconfig M src/arch/x86/Makefile.inc A src/arch/x86/breakpoint.c M src/arch/x86/exception.c A src/arch/x86/include/arch/breakpoint.h A src/arch/x86/include/arch/null_breakpoint.h A src/arch/x86/null_breakpoint.c 7 files changed, 422 insertions(+), 2 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/57/63657/1
diff --git a/src/arch/x86/Kconfig b/src/arch/x86/Kconfig index e9fce50..9491686 100644 --- a/src/arch/x86/Kconfig +++ b/src/arch/x86/Kconfig @@ -320,6 +320,28 @@ string default "src/arch/x86/memlayout.ld"
+config DEBUG_HW_BREAKPOINTS + bool + default y + help + Enables support for hardware data and instruction breakpoints through + the x86 debug registers + +config DEBUG_NULL_DEREF_BREAKPOINTS + bool + default y + depends on DEBUG_HW_BREAKPOINTS + help + Enables support for catching null deferences and instruction execution + +config DEBUG_NULL_DEREF_HALT + bool + default n + depends on DEBUG_NULL_DEREF_BREAKPOINTS + help + When enabled null deferences 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 458bcc6..b85b5e5 100644 --- a/src/arch/x86/Makefile.inc +++ b/src/arch/x86/Makefile.inc @@ -234,6 +234,8 @@ ramstage-y += c_exit.S ramstage-y += cpu.c ramstage-y += cpu_common.c +ramstage-$(CONFIG_DEBUG_HW_BREAKPOINTS) += breakpoint.c +ramstage-$(CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS) += null_breakpoint.c ramstage-y += ebda.c ramstage-y += exception.c ramstage-y += idt.S diff --git a/src/arch/x86/breakpoint.c b/src/arch/x86/breakpoint.c new file mode 100644 index 0000000..e50b1c0 --- /dev/null +++ b/src/arch/x86/breakpoint.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <arch/registers.h> +#include <arch/breakpoint.h> +#include <console/console.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)) + +/* Type takes up the lower two bits */ +#define DEBUG_CTRL_GET_TYPE(index, value) (((value) & DEBUG_CTRL_ENABLE_MASK(index)) \ + >> (DEBUG_CTRL_ENABLE_SHIFT(index) + 2)) + +/* 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 types values */ +#define DEBUG_CTRL_INSTR 0x0 +#define DEBUG_CTRL_WRITE 0x1 +#define DEBUG_CTRL_IO_RW 0x2 +#define DEBUG_CTRL_RW 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) + +static bool allocated_breakpoints[DEBUG_REGISTER_COUNT] = { 0, 0, 0, 0 }; +static breakpoint_handler handlers[DEBUG_REGISTER_COUNT] = { NULL, NULL, NULL, NULL}; + +static inline uintptr_t debug_read_addr_reg(int index) { + uintptr_t ret = ~0x0; + switch (index) { + case 0: + asm("mov %%dr0, %0" : "=r"(ret)); + break; + + case 1: + asm("mov %%dr1, %0" : "=r"(ret)); + break; + + case 2: + asm("mov %%dr2, %0" : "=r"(ret)); + break; + + case 3: + asm("mov %%dr3, %0" : "=r"(ret)); + break; + + default: + break; + } + + return ret; +} + +static inline void 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: + break; + } +} + +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(int *out_handle) { + for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) { + if (allocated_breakpoints[i]) + continue; + + allocated_breakpoints[i] = 1; + handlers[i] = NULL; + *out_handle = i; + return BREAKPOINT_RES_OK; + } + + return BREAKPOINT_RES_NONE_AVAILABLE; +} + +static enum breakpoint_result validate_handle(int handle) { + if (handle < 0 || handle >= DEBUG_REGISTER_COUNT || + !allocated_breakpoints[handle]) { + return BREAKPOINT_RES_INVALID_HANDLE; + } + + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_create_instruction(int *out_handle, void *virt_addr) { + enum breakpoint_result res = allocate_breakpoint(out_handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + + debug_write_addr_reg(*out_handle, (uintptr_t)virt_addr); + uintptr_t control = debug_read_control(); + control &= ~DEBUG_CTRL_LT_MASK(*out_handle); + control |= DEBUG_CTRL_LT(*out_handle, DEBUG_CTRL_LEN_1, DEBUG_CTRL_INSTR); + debug_write_control(control); + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_create_data(int *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; + +#if ENV_X86_64 + /* Only supported on 64-bit CPUs */ + case 8: + len_value = DEBUG_CTRL_LEN_8; + break; +#endif + + default: + return BREAKPOINT_RES_INVALID_LENGTH; + + } + + enum breakpoint_result res = allocate_breakpoint(out_handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + + uintptr_t type = write_only ? DEBUG_CTRL_WRITE : DEBUG_CTRL_RW; + debug_write_addr_reg(*out_handle, (uintptr_t)virt_addr); + uintptr_t control = debug_read_control(); + control &= ~DEBUG_CTRL_LT_MASK(*out_handle); + control |= DEBUG_CTRL_LT(*out_handle, len_value, type); + debug_write_control(control); + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_remove(int handle) { + enum breakpoint_result res = validate_handle(handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + breakpoint_enable(handle, 0); + + allocated_breakpoints[handle] = 0; + handlers[handle] = NULL; + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_enable(int handle, bool enabled) { + enum breakpoint_result res = validate_handle(handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + + uintptr_t control = debug_read_control(); + control &= ~DEBUG_CTRL_ENABLE_MASK(handle); + if (enabled) { + control |= DEBUG_CTRL_ENABLE(handle, DEBUG_CTRL_ENABLE_GLOBAL); + } + debug_write_control(control); + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_get_type(int handle, enum breakpoint_type* type) { + enum breakpoint_result res = validate_handle(handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + + *type = DEBUG_CTRL_GET_TYPE(handle, debug_read_control()); + return BREAKPOINT_RES_OK; +} + +enum breakpoint_result breakpoint_set_handler(int handle, breakpoint_handler handler) { + enum breakpoint_result res = validate_handle(handle); + if (res != BREAKPOINT_RES_OK) { + return res; + } + + handlers[handle] = handler; + return BREAKPOINT_RES_OK; +} + +int breakpoint_dispatch_handler(struct eregs* info) { + /* See which breakpoint was hit */ + uintptr_t status = debug_read_status(); + int bp = -1; + for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) { + if (DEBUG_STATUS_GET_BP_HIT(i, status)) { + bp = i; + break; + } + } + + if (bp == -1) { + return 0; + } + + /* Clear hit breakpoints */ + for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) { + status &= ~DEBUG_STATUS_BP_HIT_MASK(i); + } + + debug_write_status(status); + + /* Call the breakpoint handler */ + int ret = 0; + if (handlers[bp]) { + ret = handlers[bp](bp, info); + } + + /* Default to instruction breakpoint so that if breakpoint_get_type fails, + for whatever reason, any instruction breakpoints won't get stuck. */ + enum breakpoint_type type = BREAKPOINT_TYPE_INSTRUCTION; + breakpoint_get_type(bp, &type); + + if (type == BREAKPOINT_TYPE_INSTRUCTION && ret == 0) { + /* Set the resume flag so the same breakpoint won't be hit again immediately. + Only need to set this if we won't be halting. */ +#if ENV_X86_64 + info->rflags |= FLAGS_RESUME; +#else + info->eflags |= FLAGS_RESUME; +#endif + } + + return ret; +} \ No newline at end of file diff --git a/src/arch/x86/exception.c b/src/arch/x86/exception.c index 624226e3..1bffef4 100644 --- a/src/arch/x86/exception.c +++ b/src/arch/x86/exception.c @@ -1,6 +1,8 @@ /* 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 <commonlib/helpers.h> #include <console/console.h> @@ -371,8 +373,6 @@ } #endif /* CONFIG_GDB_STUB */
-#include <arch/registers.h> - void x86_exception(struct eregs *info);
void x86_exception(struct eregs *info) @@ -488,6 +488,14 @@ int logical_processor = 0; u32 apic_id = CONFIG(SMP) ? lapicid() : 0;
+#if ENV_RAMSTAGE && CONFIG_DEBUG_HW_BREAKPOINTS + if (info->vector == 1) { + if (breakpoint_dispatch_handler(info) == 0) { + return; + } + } +#endif + #if ENV_RAMSTAGE logical_processor = cpu_index(); #endif @@ -657,4 +665,8 @@ }
load_idt(idt, sizeof(idt)); + +#if ENV_RAMSTAGE && CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS + null_breakpoint_init(); +#endif } diff --git a/src/arch/x86/include/arch/breakpoint.h b/src/arch/x86/include/arch/breakpoint.h new file mode 100644 index 0000000..8a47f98 --- /dev/null +++ b/src/arch/x86/include/arch/breakpoint.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _BREAKPOINT_H_ +#define _BREAKPOINT_H_ + +#ifdef CONFIG_DEBUG_HW_BREAKPOINTS + +#include <arch/registers.h> +#include <types.h> + +typedef int(*breakpoint_handler)(int, 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, + BREAKPOINT_TYPE_DATA, + BREAKPOINT_TYPE_IO, +}; + +/* Creates an instruction breakpoint at the given address. */ +enum breakpoint_result breakpoint_create_instruction(int *out_handle, void *virt_addr); +/* Creates a data breakpoint at the given address for len bytes. */ +enum breakpoint_result breakpoint_create_data(int *out_handle, void *virt_addr, size_t len, bool write_only); +/* Removes a given breakpoint. */ +enum breakpoint_result breakpoint_remove(int handle); +/* Enables or disables a given breakpoint. */ +enum breakpoint_result breakpoint_enable(int handle, bool enabled); +/* Returns the type of a breakpoint. */ +enum breakpoint_result breakpoint_get_type(int 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(int handle, breakpoint_handler handler); +/* Called by x86_exception to dispatch breakpoint exceptions to the correct handler. */ +int breakpoint_dispatch_handler(struct eregs *info); +#endif /* CONFIG_DEBUG_HW_BREAKPOINTS */ +#endif /* _BREAKPOINT_H_ */ \ No newline at end of file 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 0000000..512a342 --- /dev/null +++ b/src/arch/x86/include/arch/null_breakpoint.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _NULL_BREAKPOINT_H_ +#define _NULL_BREAKPOINT_H_ + +#ifdef CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS + +/* Places data and instructions breakpoints at address zero. */ +void null_breakpoint_init(void); +#endif /* CONFIG_DEBUG_NULL_DEREF_BREAKPOINTS */ +#endif /* _NULL_BREAKPOINT_H_ */ \ No newline at end of file diff --git a/src/arch/x86/null_breakpoint.c b/src/arch/x86/null_breakpoint.c new file mode 100644 index 0000000..19b6d6a --- /dev/null +++ b/src/arch/x86/null_breakpoint.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <arch/breakpoint.h> +#include <arch/null_breakpoint.h> +#include <console/console.h> +#include <stdint.h> + +static int null_deref_bp = 0; +static int null_exec_bp = 0; + +static int handle_breakpoint(int handle, struct eregs* regs) +{ + if (handle == null_exec_bp) { + printk(BIOS_ERR, "Null instruction execution\n"); + } + else { +#if ENV_X86_64 + printk(BIOS_ERR, "Null dereference at rip: 0x%lx \n", regs->rip); +#else + printk(BIOS_ERR, "Null dereference at eip: 0x%x \n", regs->eip); +#endif + } + + return CONFIG_DEBUG_NULL_DEREF_HALT; +} + +void null_breakpoint_init(void) +{ + enum breakpoint_result res = breakpoint_create_data(&null_deref_bp, NULL, sizeof(uintptr_t), 0); + if (res != BREAKPOINT_RES_OK) { + printk(BIOS_ERR, "Failed to create NULL dereference breakpoint\n"); + } + + res = breakpoint_create_instruction(&null_exec_bp, NULL); + if (res != BREAKPOINT_RES_OK) { + printk(BIOS_ERR, "Failed to create NULL execution breakpoint\n"); + } + + breakpoint_set_handler(null_deref_bp, &handle_breakpoint); + breakpoint_set_handler(null_exec_bp, &handle_breakpoint); + + breakpoint_enable(null_deref_bp, 1); + breakpoint_enable(null_exec_bp, 1); +} \ No newline at end of file