Skip to content

Commit

Permalink
arm64: kgdb: fix single stepping
Browse files Browse the repository at this point in the history
Jason,

Could you please review my patch below?
See also arm64 maintainer's comment:
http://lists.infradead.org/pipermail/linux-arm-kernel/2015-January/313712.html

Thanks,
-Takahiro AKASHI

I tried to verify kgdb in vanilla kernel on fast model, but it seems that
the single stepping with kgdb doesn't work correctly since its first
appearance at v3.15.

On v3.15, 'stepi' command after breaking the kernel at some breakpoint
steps forward to the next instruction, but the succeeding 'stepi' never
goes beyond that.
On v3.16, 'stepi' moves forward and stops at the next instruction just
after enable_dbg in el1_dbg, and never goes beyond that. This variance of
behavior seems to come in with the following patch in v3.16:

    commit 2a28307 ("arm64: debug: avoid accessing mdscr_el1 on fault
    paths where possible")

This patch
(1) moves kgdb_disable_single_step() from 'c' command handling to single
    step handler.
    This makes sure that single stepping gets effective at every 's' command.
    Please note that, under the current implementation, single step bit in
    spsr, which is cleared by the first single stepping, will not be set
    again for the consecutive 's' commands because single step bit in mdscr
    is still kept on (that is, kernel_active_single_step() in
    kgdb_arch_handle_exception() is true).
(2) re-implements kgdb_roundup_cpus() because the current implementation
    enabled interrupts naively. See below.
(3) removes 'enable_dbg' in el1_dbg.
    Single step bit in mdscr is turned on in do_handle_exception()->
    kgdb_handle_expection() before returning to debugged context, and if
    debug exception is enabled in el1_dbg, we will see unexpected single-
    stepping in el1_dbg.
    Since v3.18, the following patch does the same:
      commit 1059c6b ("arm64: debug: don't re-enable debug exceptions
      on return from el1_dbg)
(4) masks interrupts while single-stepping one instruction.
    If an interrupt is caught during processing a single-stepping, debug
    exception is unintentionally enabled by el1_irq's 'enable_dbg' before
    returning to debugged context.
    Thus, like in (2), we will see unexpected single-stepping in el1_irq.

Basically (1) and (2) are for v3.15, (3) and (4) for v3.1[67].

* issue fixed by (2):
Without (2), we would see another problem if a breakpoint is set at
interrupt-sensible places, like gic_handle_irq():

    KGDB: re-enter error: breakpoint removed ffffffc000081258
    ------------[ cut here ]------------
    WARNING: CPU: 0 PID: 650 at kernel/debug/debug_core.c:435
					kgdb_handle_exception+0x1dc/0x1f4()
    Modules linked in:
    CPU: 0 PID: 650 Comm: sh Not tainted 3.17.0-rc2+ torvalds#177
    Call trace:
    [<ffffffc000087fac>] dump_backtrace+0x0/0x130
    [<ffffffc0000880ec>] show_stack+0x10/0x1c
    [<ffffffc0004d683c>] dump_stack+0x74/0xb8
    [<ffffffc0000ab824>] warn_slowpath_common+0x8c/0xb4
    [<ffffffc0000ab90c>] warn_slowpath_null+0x14/0x20
    [<ffffffc000121bfc>] kgdb_handle_exception+0x1d8/0x1f4
    [<ffffffc000092ffc>] kgdb_brk_fn+0x18/0x28
    [<ffffffc0000821c8>] brk_handler+0x9c/0xe8
    [<ffffffc0000811e8>] do_debug_exception+0x3c/0xac
    Exception stack(0xffffffc07e027650 to 0xffffffc07e027770)
    ...
    [<ffffffc000083cac>] el1_dbg+0x14/0x68
    [<ffffffc00012178c>] kgdb_cpu_enter+0x464/0x5c0
    [<ffffffc000121bb4>] kgdb_handle_exception+0x190/0x1f4
    [<ffffffc000092ffc>] kgdb_brk_fn+0x18/0x28
    [<ffffffc0000821c8>] brk_handler+0x9c/0xe8
    [<ffffffc0000811e8>] do_debug_exception+0x3c/0xac
    Exception stack(0xffffffc07e027ac0 to 0xffffffc07e027be0)
    ...
    [<ffffffc000083cac>] el1_dbg+0x14/0x68
    [<ffffffc00032e4b4>] __handle_sysrq+0x11c/0x190
    [<ffffffc00032e93c>] write_sysrq_trigger+0x4c/0x60
    [<ffffffc0001e7d58>] proc_reg_write+0x54/0x84
    [<ffffffc000192fa4>] vfs_write+0x98/0x1c8
    [<ffffffc0001939b0>] SyS_write+0x40/0xa0

Once some interrupt occurs, a breakpoint at gic_handle_irq() triggers kgdb.
Kgdb then calls kgdb_roundup_cpus() to sync with other cpus.
Current kgdb_roundup_cpus() unmasks interrupts temporarily to
use smp_call_function().
This eventually allows another interrupt to occur and likely results in
hitting a breakpoint at gic_handle_irq() again since debug exception is
always enabled in el1_irq.

We can avoid this issue by specifying "nokgdbroundup" in kernel parameter,
but this will also leave other cpus be in unknown state in terms of kgdb,
and may result in interfering with kgdb activity.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
Cc: linux-stable <stable@vger.kernel.org>
Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
  • Loading branch information
AKASHI Takahiro authored and jwessel committed Sep 15, 2016
1 parent 7a6653f commit a1cba8e
Showing 1 changed file with 46 additions and 14 deletions.
60 changes: 46 additions & 14 deletions arch/arm64/kernel/kgdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <linux/cpumask.h>
#include <linux/irq.h>
#include <linux/irq_work.h>
#include <linux/kdebug.h>
#include <linux/kgdb.h>
#include <linux/kprobes.h>
#include <linux/percpu.h>
#include <asm/ptrace.h>
#include <asm/traps.h>

struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = {
Expand Down Expand Up @@ -106,6 +110,9 @@ struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = {
{ "fpcr", 4, -1 },
};

static DEFINE_PER_CPU(unsigned int, kgdb_pstate);
static DEFINE_PER_CPU(struct irq_work, kgdb_irq_work);

char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)
{
if (regno >= DBG_MAX_REG_NUM || regno < 0)
Expand Down Expand Up @@ -189,18 +196,14 @@ int kgdb_arch_handle_exception(int exception_vector, int signo,
* 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':
/* mask interrupts while single stepping */
__this_cpu_write(kgdb_pstate, linux_regs->pstate);
linux_regs->pstate |= PSR_I_BIT;

/*
* Update step address value with address passed
* with step packet.
Expand All @@ -211,8 +214,6 @@ int kgdb_arch_handle_exception(int exception_vector, int signo,
*/
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
*/
Expand Down Expand Up @@ -244,6 +245,18 @@ NOKPROBE_SYMBOL(kgdb_compiled_brk_fn);

static int kgdb_step_brk_fn(struct pt_regs *regs, unsigned int esr)
{
unsigned int pstate;

kernel_disable_single_step();
atomic_set(&kgdb_cpu_doing_single_step, -1);

/* restore interrupt mask status */
pstate = __this_cpu_read(kgdb_pstate);
if (pstate & PSR_I_BIT)
regs->pstate |= PSR_I_BIT;
else
regs->pstate &= ~PSR_I_BIT;

kgdb_handle_exception(1, SIGTRAP, 0, regs);
return 0;
}
Expand All @@ -265,16 +278,27 @@ static struct step_hook kgdb_step_hook = {
.fn = kgdb_step_brk_fn
};

static void kgdb_call_nmi_hook(void *ignored)
static void kgdb_roundup_hook(struct irq_work *work)
{
kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs());
}

void kgdb_roundup_cpus(unsigned long flags)
{
local_irq_enable();
smp_call_function(kgdb_call_nmi_hook, NULL, 0);
local_irq_disable();
int cpu;
struct cpumask mask;
struct irq_work *work;

mask = *cpu_online_mask;
cpumask_clear_cpu(smp_processor_id(), &mask);
cpu = cpumask_first(&mask);
if (cpu >= nr_cpu_ids)
return;

for_each_cpu(cpu, &mask) {
work = per_cpu_ptr(&kgdb_irq_work, cpu);
irq_work_queue_on(work, cpu);
}
}

static int __kgdb_notify(struct die_args *args, unsigned long cmd)
Expand Down Expand Up @@ -315,13 +339,21 @@ static struct notifier_block kgdb_notifier = {
int kgdb_arch_init(void)
{
int ret = register_die_notifier(&kgdb_notifier);
int cpu;
struct irq_work *work;

if (ret != 0)
return ret;

register_break_hook(&kgdb_brkpt_hook);
register_break_hook(&kgdb_compiled_brkpt_hook);
register_step_hook(&kgdb_step_hook);

for_each_possible_cpu(cpu) {
work = per_cpu_ptr(&kgdb_irq_work, cpu);
init_irq_work(work, kgdb_roundup_hook);
}

return 0;
}

Expand Down

0 comments on commit a1cba8e

Please sign in to comment.