linux/arch/arm64/kernel/kgdb.c
Ada Couprie Diaz 403b48aad5 arm64: debug: call step handlers statically
Software stepping checks for the correct handler by iterating over a list
of dynamically registered handlers and calling all of them until one
handles the exception.

This is the only generic way to handle software stepping handlers in arm64
as the exception does not provide an immediate that could be checked,
contrary to software breakpoints.

However, the registration mechanism is not exported and has only
two current users : the KGDB stepping handler, and the uprobe single step
handler.
Given that one comes from user mode and the other from kernel mode, call
the appropriate one by checking the source EL of the exception.
Add a stand-in that returns DBG_HOOK_ERROR when the configuration
options are not enabled.

Remove `arch_init_uprobes()` as it is not useful anymore and is
specific to arm64.

Unify the naming of the handler to XXX_single_step_handler(), making it
clear they are related.

Signed-off-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Tested-by: Luis Claudio R. Goncalves <lgoncalv@redhat.com>
Reviewed-by: Will Deacon <will@kernel.org>
Acked-by: Mark Rutland <mark.rutland@arm.com>
Link: https://lore.kernel.org/r/20250707114109.35672-5-ada.coupriediaz@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
2025-07-08 13:27:41 +01:00

333 lines
8.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* AArch64 KGDB support
*
* Based on arch/arm/kernel/kgdb.c
*
* Copyright (C) 2013 Cavium Inc.
* Author: Vijaya Kumar K <vijaya.kumar@caviumnetworks.com>
*/
#include <linux/bug.h>
#include <linux/irq.h>
#include <linux/kdebug.h>
#include <linux/kgdb.h>
#include <linux/kprobes.h>
#include <linux/sched/task_stack.h>
#include <asm/debug-monitors.h>
#include <asm/insn.h>
#include <asm/text-patching.h>
#include <asm/traps.h>
struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = {
{ "x0", 8, offsetof(struct pt_regs, regs[0])},
{ "x1", 8, offsetof(struct pt_regs, regs[1])},
{ "x2", 8, offsetof(struct pt_regs, regs[2])},
{ "x3", 8, offsetof(struct pt_regs, regs[3])},
{ "x4", 8, offsetof(struct pt_regs, regs[4])},
{ "x5", 8, offsetof(struct pt_regs, regs[5])},
{ "x6", 8, offsetof(struct pt_regs, regs[6])},
{ "x7", 8, offsetof(struct pt_regs, regs[7])},
{ "x8", 8, offsetof(struct pt_regs, regs[8])},
{ "x9", 8, offsetof(struct pt_regs, regs[9])},
{ "x10", 8, offsetof(struct pt_regs, regs[10])},
{ "x11", 8, offsetof(struct pt_regs, regs[11])},
{ "x12", 8, offsetof(struct pt_regs, regs[12])},
{ "x13", 8, offsetof(struct pt_regs, regs[13])},
{ "x14", 8, offsetof(struct pt_regs, regs[14])},
{ "x15", 8, offsetof(struct pt_regs, regs[15])},
{ "x16", 8, offsetof(struct pt_regs, regs[16])},
{ "x17", 8, offsetof(struct pt_regs, regs[17])},
{ "x18", 8, offsetof(struct pt_regs, regs[18])},
{ "x19", 8, offsetof(struct pt_regs, regs[19])},
{ "x20", 8, offsetof(struct pt_regs, regs[20])},
{ "x21", 8, offsetof(struct pt_regs, regs[21])},
{ "x22", 8, offsetof(struct pt_regs, regs[22])},
{ "x23", 8, offsetof(struct pt_regs, regs[23])},
{ "x24", 8, offsetof(struct pt_regs, regs[24])},
{ "x25", 8, offsetof(struct pt_regs, regs[25])},
{ "x26", 8, offsetof(struct pt_regs, regs[26])},
{ "x27", 8, offsetof(struct pt_regs, regs[27])},
{ "x28", 8, offsetof(struct pt_regs, regs[28])},
{ "x29", 8, offsetof(struct pt_regs, regs[29])},
{ "x30", 8, offsetof(struct pt_regs, regs[30])},
{ "sp", 8, offsetof(struct pt_regs, sp)},
{ "pc", 8, offsetof(struct pt_regs, pc)},
/*
* struct pt_regs thinks PSTATE is 64-bits wide but gdb remote
* protocol disagrees. Therefore we must extract only the lower
* 32-bits. Look for the big comment in asm/kgdb.h for more
* detail.
*/
{ "pstate", 4, offsetof(struct pt_regs, pstate)
#ifdef CONFIG_CPU_BIG_ENDIAN
+ 4
#endif
},
{ "v0", 16, -1 },
{ "v1", 16, -1 },
{ "v2", 16, -1 },
{ "v3", 16, -1 },
{ "v4", 16, -1 },
{ "v5", 16, -1 },
{ "v6", 16, -1 },
{ "v7", 16, -1 },
{ "v8", 16, -1 },
{ "v9", 16, -1 },
{ "v10", 16, -1 },
{ "v11", 16, -1 },
{ "v12", 16, -1 },
{ "v13", 16, -1 },
{ "v14", 16, -1 },
{ "v15", 16, -1 },
{ "v16", 16, -1 },
{ "v17", 16, -1 },
{ "v18", 16, -1 },
{ "v19", 16, -1 },
{ "v20", 16, -1 },
{ "v21", 16, -1 },
{ "v22", 16, -1 },
{ "v23", 16, -1 },
{ "v24", 16, -1 },
{ "v25", 16, -1 },
{ "v26", 16, -1 },
{ "v27", 16, -1 },
{ "v28", 16, -1 },
{ "v29", 16, -1 },
{ "v30", 16, -1 },
{ "v31", 16, -1 },
{ "fpsr", 4, -1 },
{ "fpcr", 4, -1 },
};
char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)
{
if (regno >= DBG_MAX_REG_NUM || regno < 0)
return NULL;
if (dbg_reg_def[regno].offset != -1)
memcpy(mem, (void *)regs + dbg_reg_def[regno].offset,
dbg_reg_def[regno].size);
else
memset(mem, 0, dbg_reg_def[regno].size);
return dbg_reg_def[regno].name;
}
int dbg_set_reg(int regno, void *mem, struct pt_regs *regs)
{
if (regno >= DBG_MAX_REG_NUM || regno < 0)
return -EINVAL;
if (dbg_reg_def[regno].offset != -1)
memcpy((void *)regs + dbg_reg_def[regno].offset, mem,
dbg_reg_def[regno].size);
return 0;
}
void
sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task)
{
struct cpu_context *cpu_context = &task->thread.cpu_context;
/* Initialize to zero */
memset((char *)gdb_regs, 0, NUMREGBYTES);
gdb_regs[19] = cpu_context->x19;
gdb_regs[20] = cpu_context->x20;
gdb_regs[21] = cpu_context->x21;
gdb_regs[22] = cpu_context->x22;
gdb_regs[23] = cpu_context->x23;
gdb_regs[24] = cpu_context->x24;
gdb_regs[25] = cpu_context->x25;
gdb_regs[26] = cpu_context->x26;
gdb_regs[27] = cpu_context->x27;
gdb_regs[28] = cpu_context->x28;
gdb_regs[29] = cpu_context->fp;
gdb_regs[31] = cpu_context->sp;
gdb_regs[32] = cpu_context->pc;
}
void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc)
{
regs->pc = pc;
}
static int compiled_break;
static void kgdb_arch_update_addr(struct pt_regs *regs,
char *remcom_in_buffer)
{
unsigned long addr;
char *ptr;
ptr = &remcom_in_buffer[1];
if (kgdb_hex2long(&ptr, &addr))
kgdb_arch_set_pc(regs, addr);
else if (compiled_break == 1)
kgdb_arch_set_pc(regs, regs->pc + 4);
compiled_break = 0;
}
int kgdb_arch_handle_exception(int exception_vector, int signo,
int err_code, char *remcom_in_buffer,
char *remcom_out_buffer,
struct pt_regs *linux_regs)
{
int err;
switch (remcom_in_buffer[0]) {
case 'D':
case 'k':
/*
* Packet D (Detach), k (kill). No special handling
* is required here. Handle same as c packet.
*/
case 'c':
/*
* Packet c (Continue) to continue executing.
* Set pc to required address.
* Try to read optional parameter and set pc.
* If this was a compiled breakpoint, we need to move
* to the next instruction else we will just breakpoint
* over and over again.
*/
kgdb_arch_update_addr(linux_regs, remcom_in_buffer);
atomic_set(&kgdb_cpu_doing_single_step, -1);
kgdb_single_step = 0;
/*
* Received continue command, disable single step
*/
if (kernel_active_single_step())
kernel_disable_single_step();
err = 0;
break;
case 's':
/*
* Update step address value with address passed
* with step packet.
* On debug exception return PC is copied to ELR
* So just update PC.
* If no step address is passed, resume from the address
* pointed by PC. Do not update PC
*/
kgdb_arch_update_addr(linux_regs, remcom_in_buffer);
atomic_set(&kgdb_cpu_doing_single_step, raw_smp_processor_id());
kgdb_single_step = 1;
/*
* Enable single step handling
*/
if (!kernel_active_single_step())
kernel_enable_single_step(linux_regs);
else
kernel_rewind_single_step(linux_regs);
err = 0;
break;
default:
err = -1;
}
return err;
}
int kgdb_brk_handler(struct pt_regs *regs, unsigned long esr)
{
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
NOKPROBE_SYMBOL(kgdb_brk_handler)
int kgdb_compiled_brk_handler(struct pt_regs *regs, unsigned long esr)
{
compiled_break = 1;
kgdb_handle_exception(1, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
NOKPROBE_SYMBOL(kgdb_compiled_brk_handler);
int kgdb_single_step_handler(struct pt_regs *regs, unsigned long esr)
{
if (!kgdb_single_step)
return DBG_HOOK_ERROR;
kgdb_handle_exception(0, SIGTRAP, 0, regs);
return DBG_HOOK_HANDLED;
}
NOKPROBE_SYMBOL(kgdb_single_step_handler);
static int __kgdb_notify(struct die_args *args, unsigned long cmd)
{
struct pt_regs *regs = args->regs;
if (kgdb_handle_exception(1, args->signr, cmd, regs))
return NOTIFY_DONE;
return NOTIFY_STOP;
}
static int
kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr)
{
unsigned long flags;
int ret;
local_irq_save(flags);
ret = __kgdb_notify(ptr, cmd);
local_irq_restore(flags);
return ret;
}
static struct notifier_block kgdb_notifier = {
.notifier_call = kgdb_notify,
/*
* Want to be lowest priority
*/
.priority = -INT_MAX,
};
/*
* kgdb_arch_init - Perform any architecture specific initialization.
* This function will handle the initialization of any architecture
* specific callbacks.
*/
int kgdb_arch_init(void)
{
return register_die_notifier(&kgdb_notifier);
}
/*
* kgdb_arch_exit - Perform any architecture specific uninitalization.
* This function will handle the uninitalization of any architecture
* specific callbacks, for dynamic registration and unregistration.
*/
void kgdb_arch_exit(void)
{
unregister_die_notifier(&kgdb_notifier);
}
const struct kgdb_arch arch_kgdb_ops;
int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt)
{
int err;
BUILD_BUG_ON(AARCH64_INSN_SIZE != BREAK_INSTR_SIZE);
err = aarch64_insn_read((void *)bpt->bpt_addr, (u32 *)bpt->saved_instr);
if (err)
return err;
return aarch64_insn_write((void *)bpt->bpt_addr,
(u32)AARCH64_BREAK_KGDB_DYN_DBG);
}
int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt)
{
return aarch64_insn_write((void *)bpt->bpt_addr,
*(u32 *)bpt->saved_instr);
}