Skip to content

Commit

Permalink
Merge tag 'kgdb-5.8-rc3' of git://git.kernel.org/pub/scm/linux/kernel…
Browse files Browse the repository at this point in the history
…/git/danielt/linux

Pull kgdb fixes from Daniel Thompson:
 "The main change here is a fix for a number of unsafe interactions
  between kdb and the console system. The fixes are specific to kdb
  (pure kgdb debugging does not use the console system at all). On
  systems with an NMI then kdb, if it is enabled, must get messages to
  the user despite potentially running from some "difficult" calling
  contexts. These fixes avoid using the console system where we have
  been provided an alternative (safer) way to interact with the user
  and, if using the console system in unavoidable, use oops_in_progress
  for deadlock avoidance. These fixes also ensure kdb honours the
  console enable flag.

  Also included is a fix that wraps kgdb trap handling in an RCU read
  lock to avoids triggering diagnostic warnings. This is a wide lock
  scope but this is OK because kgdb is a stop-the-world debugger. When
  we stop the world we put all the CPUs into holding pens and this
  inhibits RCU update anyway"

* tag 'kgdb-5.8-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/danielt/linux:
  kgdb: Avoid suspicious RCU usage warning
  kdb: Switch to use safer dbg_io_ops over console APIs
  kdb: Make kdb_printf() console handling more robust
  kdb: Check status of console prior to invoking handlers
  kdb: Re-factor kdb_printf() message write code
  • Loading branch information
torvalds committed Jun 27, 2020
2 parents 21d2f68 + 440ab9e commit 6116dea
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 50 deletions.
2 changes: 1 addition & 1 deletion drivers/tty/serial/kgdb_nmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static int kgdb_nmi_console_setup(struct console *co, char *options)
* I/O utilities that messages sent to the console will automatically
* be displayed on the dbg_io.
*/
dbg_io_ops->is_console = true;
dbg_io_ops->cons = co;

return 0;
}
Expand Down
32 changes: 16 additions & 16 deletions drivers/tty/serial/kgdboc.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ static struct platform_device *kgdboc_pdev;

#if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE)
static struct kgdb_io kgdboc_earlycon_io_ops;
static struct console *earlycon;
static int (*earlycon_orig_exit)(struct console *con);
#endif /* IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */

Expand Down Expand Up @@ -145,7 +144,7 @@ static void kgdboc_unregister_kbd(void)
#if IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE)
static void cleanup_earlycon(void)
{
if (earlycon)
if (kgdboc_earlycon_io_ops.cons)
kgdb_unregister_io_module(&kgdboc_earlycon_io_ops);
}
#else /* !IS_BUILTIN(CONFIG_KGDB_SERIAL_CONSOLE) */
Expand Down Expand Up @@ -178,7 +177,7 @@ static int configure_kgdboc(void)
goto noconfig;
}

kgdboc_io_ops.is_console = 0;
kgdboc_io_ops.cons = NULL;
kgdb_tty_driver = NULL;

kgdboc_use_kms = 0;
Expand All @@ -198,7 +197,7 @@ static int configure_kgdboc(void)
int idx;
if (cons->device && cons->device(cons, &idx) == p &&
idx == tty_line) {
kgdboc_io_ops.is_console = 1;
kgdboc_io_ops.cons = cons;
break;
}
}
Expand Down Expand Up @@ -433,15 +432,17 @@ static int kgdboc_earlycon_get_char(void)
{
char c;

if (!earlycon->read(earlycon, &c, 1))
if (!kgdboc_earlycon_io_ops.cons->read(kgdboc_earlycon_io_ops.cons,
&c, 1))
return NO_POLL_CHAR;

return c;
}

static void kgdboc_earlycon_put_char(u8 chr)
{
earlycon->write(earlycon, &chr, 1);
kgdboc_earlycon_io_ops.cons->write(kgdboc_earlycon_io_ops.cons, &chr,
1);
}

static void kgdboc_earlycon_pre_exp_handler(void)
Expand All @@ -461,7 +462,7 @@ static void kgdboc_earlycon_pre_exp_handler(void)
* boot if we detect this case.
*/
for_each_console(con)
if (con == earlycon)
if (con == kgdboc_earlycon_io_ops.cons)
return;

already_warned = true;
Expand All @@ -484,25 +485,25 @@ static int kgdboc_earlycon_deferred_exit(struct console *con)

static void kgdboc_earlycon_deinit(void)
{
if (!earlycon)
if (!kgdboc_earlycon_io_ops.cons)
return;

if (earlycon->exit == kgdboc_earlycon_deferred_exit)
if (kgdboc_earlycon_io_ops.cons->exit == kgdboc_earlycon_deferred_exit)
/*
* kgdboc_earlycon is exiting but original boot console exit
* was never called (AKA kgdboc_earlycon_deferred_exit()
* didn't ever run). Undo our trap.
*/
earlycon->exit = earlycon_orig_exit;
else if (earlycon->exit)
kgdboc_earlycon_io_ops.cons->exit = earlycon_orig_exit;
else if (kgdboc_earlycon_io_ops.cons->exit)
/*
* We skipped calling the exit() routine so we could try to
* keep using the boot console even after it went away. We're
* finally done so call the function now.
*/
earlycon->exit(earlycon);
kgdboc_earlycon_io_ops.cons->exit(kgdboc_earlycon_io_ops.cons);

earlycon = NULL;
kgdboc_earlycon_io_ops.cons = NULL;
}

static struct kgdb_io kgdboc_earlycon_io_ops = {
Expand All @@ -511,7 +512,6 @@ static struct kgdb_io kgdboc_earlycon_io_ops = {
.write_char = kgdboc_earlycon_put_char,
.pre_exception = kgdboc_earlycon_pre_exp_handler,
.deinit = kgdboc_earlycon_deinit,
.is_console = true,
};

#define MAX_CONSOLE_NAME_LEN (sizeof((struct console *) 0)->name)
Expand Down Expand Up @@ -557,10 +557,10 @@ static int __init kgdboc_earlycon_init(char *opt)
goto unlock;
}

earlycon = con;
kgdboc_earlycon_io_ops.cons = con;
pr_info("Going to register kgdb with earlycon '%s'\n", con->name);
if (kgdb_register_io_module(&kgdboc_earlycon_io_ops) != 0) {
earlycon = NULL;
kgdboc_earlycon_io_ops.cons = NULL;
pr_info("Failed to register kgdb with earlycon\n");
} else {
/* Trap exit so we can keep earlycon longer if needed. */
Expand Down
3 changes: 2 additions & 1 deletion drivers/usb/early/ehci-dbgp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,8 @@ static int __init kgdbdbgp_parse_config(char *str)
kgdbdbgp_wait_time = simple_strtoul(ptr, &ptr, 10);
}
kgdb_register_io_module(&kgdbdbgp_io_ops);
kgdbdbgp_io_ops.is_console = early_dbgp_console.index != -1;
if (early_dbgp_console.index != -1)
kgdbdbgp_io_ops.cons = &early_dbgp_console;

return 0;
}
Expand Down
5 changes: 2 additions & 3 deletions include/linux/kgdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,7 @@ struct kgdb_arch {
* the I/O driver.
* @post_exception: Pointer to a function that will do any cleanup work
* for the I/O driver.
* @is_console: 1 if the end device is a console 0 if the I/O device is
* not a console
* @cons: valid if the I/O device is a console; else NULL.
*/
struct kgdb_io {
const char *name;
Expand All @@ -288,7 +287,7 @@ struct kgdb_io {
void (*deinit) (void);
void (*pre_exception) (void);
void (*post_exception) (void);
int is_console;
struct console *cons;
};

extern const struct kgdb_arch arch_kgdb_ops;
Expand Down
4 changes: 4 additions & 0 deletions kernel/debug/debug_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
arch_kgdb_ops.disable_hw_break(regs);

acquirelock:
rcu_read_lock();
/*
* Interrupts will be restored by the 'trap return' code, except when
* single stepping.
Expand Down Expand Up @@ -646,6 +647,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
atomic_dec(&slaves_in_kgdb);
dbg_touch_watchdogs();
local_irq_restore(flags);
rcu_read_unlock();
return 0;
}
cpu_relax();
Expand All @@ -664,6 +666,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
raw_spin_unlock(&dbg_master_lock);
dbg_touch_watchdogs();
local_irq_restore(flags);
rcu_read_unlock();

goto acquirelock;
}
Expand Down Expand Up @@ -787,6 +790,7 @@ static int kgdb_cpu_enter(struct kgdb_state *ks, struct pt_regs *regs,
raw_spin_unlock(&dbg_master_lock);
dbg_touch_watchdogs();
local_irq_restore(flags);
rcu_read_unlock();

return kgdb_info[cpu].ret_state;
}
Expand Down
72 changes: 43 additions & 29 deletions kernel/debug/kdb/kdb_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,44 @@ static int kdb_search_string(char *searched, char *searchfor)
return 0;
}

static void kdb_msg_write(const char *msg, int msg_len)
{
struct console *c;

if (msg_len == 0)
return;

if (dbg_io_ops) {
const char *cp = msg;
int len = msg_len;

while (len--) {
dbg_io_ops->write_char(*cp);
cp++;
}
}

for_each_console(c) {
if (!(c->flags & CON_ENABLED))
continue;
if (c == dbg_io_ops->cons)
continue;
/*
* Set oops_in_progress to encourage the console drivers to
* disregard their internal spin locks: in the current calling
* context the risk of deadlock is a bigger problem than risks
* due to re-entering the console driver. We operate directly on
* oops_in_progress rather than using bust_spinlocks() because
* the calls bust_spinlocks() makes on exit are not appropriate
* for this calling context.
*/
++oops_in_progress;
c->write(c, msg, msg_len);
--oops_in_progress;
touch_nmi_watchdog();
}
}

int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
{
int diag;
Expand All @@ -553,7 +591,6 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
int this_cpu, old_cpu;
char *cp, *cp2, *cphold = NULL, replaced_byte = ' ';
char *moreprompt = "more> ";
struct console *c;
unsigned long uninitialized_var(flags);

/* Serialize kdb_printf if multiple cpus try to write at once.
Expand Down Expand Up @@ -687,22 +724,11 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
*/
retlen = strlen(kdb_buffer);
cp = (char *) printk_skip_headers(kdb_buffer);
if (!dbg_kdb_mode && kgdb_connected) {
if (!dbg_kdb_mode && kgdb_connected)
gdbstub_msg_write(cp, retlen - (cp - kdb_buffer));
} else {
if (dbg_io_ops && !dbg_io_ops->is_console) {
len = retlen - (cp - kdb_buffer);
cp2 = cp;
while (len--) {
dbg_io_ops->write_char(*cp2);
cp2++;
}
}
for_each_console(c) {
c->write(c, cp, retlen - (cp - kdb_buffer));
touch_nmi_watchdog();
}
}
else
kdb_msg_write(cp, retlen - (cp - kdb_buffer));

if (logging) {
saved_loglevel = console_loglevel;
console_loglevel = CONSOLE_LOGLEVEL_SILENT;
Expand Down Expand Up @@ -751,19 +777,7 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
moreprompt = "more> ";

kdb_input_flush();

if (dbg_io_ops && !dbg_io_ops->is_console) {
len = strlen(moreprompt);
cp = moreprompt;
while (len--) {
dbg_io_ops->write_char(*cp);
cp++;
}
}
for_each_console(c) {
c->write(c, moreprompt, strlen(moreprompt));
touch_nmi_watchdog();
}
kdb_msg_write(moreprompt, strlen(moreprompt));

if (logging)
printk("%s", moreprompt);
Expand Down

0 comments on commit 6116dea

Please sign in to comment.