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:
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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue