summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Zieba <robertzieba@google.com>2022-04-14 10:36:15 -0600
committerNick Vaccaro <nvaccaro@google.com>2022-05-12 15:47:18 +0000
commit3f01cd14533f12f04a87a9cf1111dd948094bac4 (patch)
treee1ef98e60c0f91fd59fc14f3983525d21c62760a
parent4be0f4bf9943f5f6c84d1c13dee70b7442f99bd0 (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/Kconfig32
-rw-r--r--src/arch/x86/Makefile.inc12
-rw-r--r--src/arch/x86/breakpoint.c300
-rw-r--r--src/arch/x86/exception.c13
-rw-r--r--src/arch/x86/include/arch/breakpoint.h58
-rw-r--r--src/arch/x86/include/arch/null_breakpoint.h16
-rw-r--r--src/arch/x86/null_breakpoint.c57
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();
+}