Re: [PATCH v1] LoongArch: Handle special PC in unwind_next_frame()

From: Tiezhu Yang

Date: Wed Nov 26 2025 - 04:09:28 EST


Cc: Bibo Mao <maobibo@xxxxxxxxxxx>

On 2025/11/26 下午2:28, Tiezhu Yang wrote:
On 2025/11/26 下午2:08, Tiezhu Yang wrote:
On 2025/11/25 下午6:52, Jinyang He wrote:
On 2025-11-25 14:33, Tiezhu Yang wrote:

When running virtual machine before testing the kernel live patching with
"modprobe livepatch-sample", there is a timeout over 15 seconds, the dmesg
command shows "unreliable stack" for user tasks in debug mode.

The "unreliable stack" is because it can not unwind from kvm_handle_exit()
to its previous frame kvm_exc_entry() due to the PC is not a valid kernel
address, the root cause is that the code of kvm_exc_entry() was copied to
the DMW area in kvm_loongarch_env_init(), so it should check the PC range
and then finish unwinding for this special case.

Hi, Tiezhu,

Since kvm_loongarch_env_init copies kvm_exc_entry which similar to how
trap_init copies the exception handler. We should apply the same
offset-based adjustment to compute the shadow PC address:
  shadow_pc = kvm_exc_entry + (cur_pc − copied_base_address)
This allows the ORC unwinder to correctly locate the corresponding unwind info.

Thank you. I did some tests, it can unwind from kvm_handle_exit()
to its previous frame kvm_exc_entry() only when CONFIG_KVM=y,
but it can not unwind from kvm_handle_exit() to its previous frame kvm_exc_entry() when CONFIG_KVM=m because we can not get the two
kvm address (unsigned long)kvm_exc_entry and
(unsigned long)kvm_loongarch_ops->exc_entry
in arch/loongarch/kernel/unwind_orc.c.

Use function pointer can get get the two kvm address
(unsigned long)kvm_exc_entry and
(unsigned long)kvm_loongarch_ops->exc_entry
in arch/loongarch/kernel/unwind_orc.c.

I will do more test and send v2 later.

Here are the draft changes:

----->8-----
diff --git a/arch/loongarch/kernel/unwind_guess.c b/arch/loongarch/kernel/unwind_guess.c
index 08d7951b2f60..64c50c483cbb 100644
--- a/arch/loongarch/kernel/unwind_guess.c
+++ b/arch/loongarch/kernel/unwind_guess.c
@@ -5,6 +5,11 @@
#include <asm/unwind.h>
#include <linux/export.h>

+#if IS_ENABLED(CONFIG_KVM)
+void (*get_kvm_entry_info)(unsigned long *old, unsigned long *new, unsigned long *size);
+EXPORT_SYMBOL_GPL(get_kvm_entry_info);
+#endif
+
unsigned long unwind_get_return_address(struct unwind_state *state)
{
return __unwind_get_return_address(state);
diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
index 1d60a593479a..ef8bb6cd7af8 100644
--- a/arch/loongarch/kernel/unwind_orc.c
+++ b/arch/loongarch/kernel/unwind_orc.c
@@ -356,6 +356,11 @@ static bool is_entry_func(unsigned long addr)
return addr >= (unsigned long)&kernel_entry && addr < (unsigned long)&kernel_entry_end;
}

+#if IS_ENABLED(CONFIG_KVM)
+void (*get_kvm_entry_info)(unsigned long *old, unsigned long *new, unsigned long *size);
+EXPORT_SYMBOL_GPL(get_kvm_entry_info);
+#endif
+
static inline unsigned long bt_address(unsigned long ra)
{
extern unsigned long eentry;
@@ -386,6 +391,16 @@ static inline unsigned long bt_address(unsigned long ra)
return func + offset;
}

+#if IS_ENABLED(CONFIG_KVM)
+ unsigned long old, new, size;
+
+ if (get_kvm_entry_info) {
+ get_kvm_entry_info(&old, &new, &size);
+ if (ra >= new && ra < new + size)
+ return old + (ra - new);
+ }
+#endif
+
return ra;
}

diff --git a/arch/loongarch/kernel/unwind_prologue.c b/arch/loongarch/kernel/unwind_prologue.c
index 729e775bd40d..42c210b0a378 100644
--- a/arch/loongarch/kernel/unwind_prologue.c
+++ b/arch/loongarch/kernel/unwind_prologue.c
@@ -28,6 +28,11 @@ extern unsigned long eentry;
extern unsigned long pcpu_handlers[NR_CPUS];
#endif

+#if IS_ENABLED(CONFIG_KVM)
+void (*get_kvm_entry_info)(unsigned long *old, unsigned long *new, unsigned long *size);
+EXPORT_SYMBOL_GPL(get_kvm_entry_info);
+#endif
+
static inline bool scan_handlers(unsigned long entry_offset)
{
int idx, offset;
diff --git a/arch/loongarch/kvm/main.c b/arch/loongarch/kvm/main.c
index 80ea63d465b8..3606554e43e0 100644
--- a/arch/loongarch/kvm/main.c
+++ b/arch/loongarch/kvm/main.c
@@ -338,6 +338,13 @@ void kvm_arch_disable_virtualization_cpu(void)
kvm_flush_tlb_all();
}

+static void kvm_entry_info(unsigned long *old, unsigned long *new, unsigned long *size)
+{
+ *old = (unsigned long)kvm_exc_entry;
+ *new = (unsigned long)kvm_loongarch_ops->exc_entry;
+ *size = kvm_exception_size;
+}
+
static int kvm_loongarch_env_init(void)
{
int cpu, order, ret;
@@ -430,6 +437,7 @@ static void kvm_loongarch_env_exit(void)
kvm_unregister_perf_callbacks();
}

+extern void (*get_kvm_entry_info)(unsigned long *old, unsigned long *new, unsigned long *size);
static int kvm_loongarch_init(void)
{
int r;
@@ -442,6 +450,8 @@ static int kvm_loongarch_init(void)
if (r)
return r;

+ get_kvm_entry_info = kvm_entry_info;
+
return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
}

diff --git a/arch/loongarch/kvm/switch.S b/arch/loongarch/kvm/switch.S
index f1768b7a6194..9eaad5dbed91 100644
--- a/arch/loongarch/kvm/switch.S
+++ b/arch/loongarch/kvm/switch.S
@@ -104,7 +104,7 @@
.text
.cfi_sections .debug_frame
SYM_CODE_START(kvm_exc_entry)
- UNWIND_HINT_UNDEFINED
+ UNWIND_HINT_END_OF_STACK
csrwr a2, KVM_TEMP_KS
csrrd a2, KVM_VCPU_KS
addi.d a2, a2, KVM_VCPU_ARCH
----->8-----

I tested with config UNWINDER_ORC, it can unwind from kvm_handle_exit()
to its previous frame kvm_exc_entry() which is the end of stack, there
is no "unreliable stack" in debug mode and also no timeout for kernel
livepatching.

I also tested with config UNWINDER_PROLOGUE and config UNWINDER_GUESS,
no build errors and the virtual machine can boot normally.

I would like to receive comments for the draft changes first and then
send the formal v2, the function and variable name may be not proper,
so any comments are welcome.

Since the merge window is coming soon and I am busy with the other
higher priority stuff, so maybe I will send v2 after the merge window.

Thanks,
Tiezhu