[PATCH] kexec/kdump and kdb

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

Below is a very preliminary patch that adds kexec/kdump invocation 
functionality in kdb. It is currently based on 2.6.20.11 and 
supported only for x86_64.

The main use case is for a user who would like to make use of the
interactive debugger but wants to conserve the option whether to 
create a crash dump interactively. It also makes it possible to 
dump even if the kernel hasn't crashed by manually entering kdb 
and entering 'kdump' (yes I know sysrq also allows you to trigger 
a crash dump).

There are a few issues with this patch (mainly being incomplete), 
I'd like to check the public interest with this feature before I
develop it further.


diff --git a/arch/x86_64/kernel/crash.c b/arch/x86_64/kernel/crash.c
index 95a7a2c..b68a0f5 100644
--- a/arch/x86_64/kernel/crash.c
+++ b/arch/x86_64/kernel/crash.c
@@ -31,6 +31,18 @@ static int crashing_cpu;
 #ifdef CONFIG_SMP
 static atomic_t waiting_for_crash_ipi;
 
+static void halt_current_cpu(struct pt_regs *regs)
+{
+	local_irq_disable();
+
+	crash_save_cpu(regs, raw_smp_processor_id());
+	disable_local_APIC();
+	atomic_dec(&waiting_for_crash_ipi);
+	/* Assume hlt works */
+	for(;;)
+		halt();
+}
+
 static int crash_nmi_callback(struct notifier_block *self,
 				unsigned long val, void *data)
 {
@@ -50,15 +62,8 @@ static int crash_nmi_callback(struct notifier_block *self,
 	 */
 	if (cpu == crashing_cpu)
 		return NOTIFY_STOP;
-	local_irq_disable();
-
-	crash_save_cpu(regs, cpu);
-	disable_local_APIC();
-	atomic_dec(&waiting_for_crash_ipi);
-	/* Assume hlt works */
-	for(;;)
-		halt();
 
+	halt_current_cpu(regs);
 	return 1;
 }
 
@@ -77,11 +82,26 @@ static struct notifier_block crash_nmi_nb = {
 	.notifier_call = crash_nmi_callback,
 };
 
-static void nmi_shootdown_cpus(void)
+static void wait_other_cpus(void)
 {
 	unsigned long msecs;
 
+	msecs = 1000; /* Wait at most a second for the other cpus to stop */
+	while ((atomic_read(&waiting_for_crash_ipi) > 0) && msecs) {
+		mdelay(1);
+		msecs--;
+	}
+}
+
+static void nmi_shootdown_cpus_init(void)
+{
 	atomic_set(&waiting_for_crash_ipi, num_online_cpus() - 1);
+}
+
+static void nmi_shootdown_cpus(void)
+{
+	nmi_shootdown_cpus_init();
+
 	if (register_die_notifier(&crash_nmi_nb))
 		return;         /* return what? */
 
@@ -93,19 +113,23 @@ static void nmi_shootdown_cpus(void)
 
 	smp_send_nmi_allbutself();
 
-	msecs = 1000; /* Wait at most a second for the other cpus to stop */
-	while ((atomic_read(&waiting_for_crash_ipi) > 0) && msecs) {
-		mdelay(1);
-		msecs--;
-	}
+	wait_other_cpus();
 	/* Leave the nmi callback set */
+
 	disable_local_APIC();
 }
+
 #else
+
 static void nmi_shootdown_cpus(void)
 {
 	/* There are no cpus to shootdown */
 }
+
+static void nmi_shootdown_cpus_init(void) {};
+static void wait_other_cpus() {}
+static void halt_current_cpu(struct pt_regs *regs) {};
+
 #endif
 
 void machine_crash_shutdown(struct pt_regs *regs)
@@ -133,3 +157,26 @@ void machine_crash_shutdown(struct pt_regs *regs)
 
 	crash_save_cpu(regs, smp_processor_id());
 }
+
+void machine_crash_shutdown_begin(void)
+{
+	local_irq_disable();
+	nmi_shootdown_cpus_init();
+}
+
+void machine_crash_shutdown_end(struct pt_regs *regs)
+{
+	wait_other_cpus();
+
+	local_irq_disable();
+	if(cpu_has_apic)
+		disable_local_APIC();
+
+	disable_IO_APIC();
+	crash_save_cpu(regs, smp_processor_id());
+}
+
+void machine_crash_shutdown_other_cpu(struct pt_regs *regs)
+{
+	halt_current_cpu(regs);
+}
diff --git a/include/linux/kdb.h b/include/linux/kdb.h
index e4340d3..1cf413e 100644
--- a/include/linux/kdb.h
+++ b/include/linux/kdb.h
@@ -161,4 +161,11 @@ int kdb_process_cpu(const struct task_struct *p)
 
 extern const char kdb_serial_str[];
 
+#ifdef CONFIG_KEXEC
+
+void kdb_kexec_prepare(struct pt_regs *regs);
+void kexec_from_kdb(struct pt_regs *regs, int kdump);
+
+#endif
+
 #endif	/* !_KDB_H */
diff --git a/include/linux/kdbprivate.h b/include/linux/kdbprivate.h
index 50dee9f..ca2c3db 100644
--- a/include/linux/kdbprivate.h
+++ b/include/linux/kdbprivate.h
@@ -162,6 +162,7 @@ volatile extern int kdb_state[ /*NR_CPUS*/ ];
 #define KDB_STATE_IP_ADJUSTED	0x00008000	/* Restart IP has been adjusted */
 #define KDB_STATE_GO1		0x00010000	/* go only releases one cpu */
 #define KDB_STATE_KEYBOARD	0x00020000	/* kdb entered via keyboard on this cpu */
+#define KDB_STATE_KEXEC   	0x00040000	/* kexec issued */
 #define KDB_STATE_ARCH		0xff000000	/* Reserved for arch specific use */
 
 #define KDB_STATE_CPU(flag,cpu)		(kdb_state[cpu] & KDB_STATE_##flag)
diff --git a/include/linux/reboot.h b/include/linux/reboot.h
index 1dd1c70..f4d682c 100644
--- a/include/linux/reboot.h
+++ b/include/linux/reboot.h
@@ -53,7 +53,10 @@ extern void machine_power_off(void);
 
 extern void machine_shutdown(void);
 struct pt_regs;
-extern void machine_crash_shutdown(struct pt_regs *);
+extern void machine_crash_shutdown(struct pt_regs *regs);
+extern void machine_crash_shutdown_begin(void);
+extern void machine_crash_shutdown_other_cpu(struct pt_regs *regs);
+extern void machine_crash_shutdown_end(struct pt_regs *regs);
 
 /* 
  * Architecture independent implemenations of sys_reboot commands.
diff --git a/kdb/kdbmain.c b/kdb/kdbmain.c
index 9dfa3b3..2ecacfc 100644
--- a/kdb/kdbmain.c
+++ b/kdb/kdbmain.c
@@ -1124,6 +1124,116 @@ kdb_reboot(int argc, const char **argv)
 	return 0;
 }
 
+#ifdef CONFIG_KEXEC
+
+int kdb_kexec_state = -1;
+
+static int kdb_cpu(int argc, const char **argv);
+
+void kdb_kexec_prepare(struct pt_regs *regs)
+{
+	int i;
+	struct pt_regs r;
+	if (regs == NULL)
+		regs = &r;
+
+	machine_crash_shutdown_begin();
+	
+	for (i = 1; i < NR_CPUS; ++i) {
+		if (!cpu_online(i))
+			continue;
+
+		KDB_STATE_SET_CPU(KEXEC, i);
+	}
+
+	machine_crash_shutdown_end(regs);
+}
+
+void kdb_kexec_check(struct pt_regs *regs)
+{
+	if (kdb_kexec_state != -1) {
+		kexec_from_kdb(regs, kdb_kexec_state);
+
+		/* If the call above returned then something 
+		   didn't work */
+		kdb_kexec_state = -1;
+	}
+}
+
+static int 
+kdb_switch_cpu_for_kexec(int new_state)
+{
+	const char *cpu_argv[] = {NULL, "0", NULL};
+	int ret;
+
+	kdb_kexec_state = new_state;
+	ret = kdb_cpu(1, cpu_argv);
+	if (ret != KDB_CMD_CPU) {
+		kdb_kexec_state = -1;
+	}
+
+	return ret;
+}
+
+
+/*
+ * kdb_kexec
+ *
+ *     This function implements the 'kexec' command.
+ *
+ * Inputs:
+ *     argc    argument count
+ *     argv    argument vector
+ *     envp    environment vector
+ *     regs    registers at time kdb was entered.
+ * Outputs:
+ *     None.
+ * Returns:
+ *     zero for success, a kdb diagnostic if error
+ * Locking:
+ *     none.
+ * Remarks:
+ *     Shouldn't return from this function.
+ */
+
+static int
+kdb_kexec(int argc, const char **argv)
+{
+	return kdb_switch_cpu_for_kexec(0);
+}
+
+/*
+ * kdb_kdump
+ *
+ *     This function implements the 'kdump' command.
+ *
+ * Inputs:
+ *     argc    argument count
+ *     argv    argument vector
+ *     envp    environment vector
+ *     regs    registers at time kdb was entered.
+ * Outputs:
+ *     None.
+ * Returns:
+ *     zero for success, a kdb diagnostic if error
+ * Locking:
+ *     none.
+ * Remarks:
+ *     Shouldn't return from this function.
+ */
+
+static int
+kdb_kdump(int argc, const char **argv)
+{
+	return kdb_switch_cpu_for_kexec(1);
+}
+
+#else
+
+static inline void kdb_kexec_check(struct pt_regs *regs) {};
+
+#endif
+
 static int
 kdb_quiet(int reason)
 {
@@ -1166,6 +1276,8 @@ kdb_local(kdb_reason_t reason, int error, struct pt_regs *regs, kdb_dbtrap_t db_
 	int diag;
 	struct task_struct *kdb_current = kdb_curr_task(smp_processor_id());
 
+	kdb_kexec_check(regs);
+
 	/* If kdb has been entered for an event which has been/will be
 	 * recovered then silently return.  We have to get this far into kdb in
 	 * order to synchronize all the cpus, typically only one cpu (monarch)
@@ -1553,7 +1665,18 @@ kdb_main_loop(kdb_reason_t reason, kdb_reason_t reason2, int error,
 			if (!KDB_STATE(KDB)) {
 				KDB_STATE_SET(KDB);
 			}
+
+#ifdef CONFIG_KEXEC
+			if (KDB_STATE(KEXEC)) {
+				struct pt_regs r;
+				if (regs == NULL)
+					regs = &r;
+				machine_crash_shutdown_other_cpu(regs);
+				return 0;
+			}
+#endif
 		}
+
 		KDB_STATE_CLEAR(SUPPRESS);
 		KDB_DEBUG_STATE("kdb_main_loop 2", reason);
 		if (KDB_STATE(LEAVING))
@@ -3821,6 +3944,10 @@ kdb_inittab(void)
 	kdb_register_repeat("ps", kdb_ps, "", 		"Display active task list", 0, KDB_REPEAT_NONE);
 	kdb_register_repeat("pid", kdb_pid, "<pidnum>",	"Switch to another task", 0, KDB_REPEAT_NONE);
 	kdb_register_repeat("reboot", kdb_reboot, "",  "Reboot the machine immediately", 0, KDB_REPEAT_NONE);
+#if defined(CONFIG_KEXEC)
+	kdb_register_repeat("kexec", kdb_kexec, "",    "Calls kexec in crash mode", 0, KDB_REPEAT_NONE);
+	kdb_register_repeat("kdump", kdb_kdump, "",    "Calls kdump mode", 0, KDB_REPEAT_NONE);
+#endif
 #if defined(CONFIG_MODULES)
 	kdb_register_repeat("lsmod", kdb_lsmod, "",	"List loaded kernel modules", 0, KDB_REPEAT_NONE);
 #endif
diff --git a/kernel/kexec.c b/kernel/kexec.c
index 2a59c8a..5858db1 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -22,6 +22,8 @@
 #include <linux/hardirq.h>
 #include <linux/elf.h>
 #include <linux/elfcore.h>
+#include <linux/module.h>
+#include <linux/device.h>
 
 #include <asm/page.h>
 #include <asm/uaccess.h>
@@ -29,6 +31,10 @@
 #include <asm/system.h>
 #include <asm/semaphore.h>
 
+#ifdef CONFIG_KDB
+#include <linux/kdb.h>
+#endif
+
 /* Per cpu memory for storing cpu states in case of system crash. */
 note_buf_t* crash_notes;
 
@@ -1045,6 +1051,12 @@ asmlinkage long compat_sys_kexec_load(unsigned long entry,
 
 void crash_kexec(struct pt_regs *regs)
 {
+#if !CONFIG_KDB
+	/*
+	 * If we enabled KDB, we don't want to automatically 
+	 * perform a kdump since KDB will be responsible for 
+	 * executing kdb through a special 'kdump' command.
+	 */
 	int locked;
 
 
@@ -1067,8 +1079,38 @@ void crash_kexec(struct pt_regs *regs)
 		locked = xchg(&kexec_lock, 0);
 		BUG_ON(!locked);
 	}
+#endif
 }
 
+#ifdef CONFIG_KDB
+
+void kexec_from_kdb(struct pt_regs *regs, int kdump)
+{
+	struct kimage *image;
+	int locked;
+
+	locked = xchg(&kexec_lock, 1);
+	if (!locked) {
+		if (kdump) 
+			image = kexec_crash_image;
+		else 
+			image = kexec_image;
+
+		if (image) {
+			struct pt_regs fixed_regs;
+
+			crash_setup_regs(&fixed_regs, regs);
+			kdb_kexec_prepare(&fixed_regs);
+			machine_kexec(image);
+		}
+
+		locked = xchg(&kexec_lock, 0);
+		BUG_ON(!locked);
+	}
+}
+
+#endif
+
 static u32 *append_elf_note(u32 *buf, char *name, unsigned type, void *data,
 			    size_t data_len)
 {


-- 
Dan Aloni
XIV LTD, http://www.xivstorage.com
da-x (at) monatomic.org, dan (at) xiv.co.il
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux