mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

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>
333 lines
8.4 KiB
C
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);
|
|
}
|