zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.78k stars 6.58k forks source link

RISCV32: There is a problem with c++ exception handling under RISCV32(throw???) #55850

Closed duanlangtaosha closed 1 year ago

duanlangtaosha commented 1 year ago

Describe the problem

I tested a simple C++ exception capture example, it is normal under the arm platform, but the program will crash under the RISCV platform.

The zephyr version is as follows:

commit bb6fa5707f77d72307008405f19dd83247405392 (HEAD -> test, origin/main, origin/HEAD)
Author: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
Date:   Tue Mar 14 09:17:13 2023 +0100

    nrf52_bsim: Fix unfreed memory

    There was a chunk of unfreed memory left over at exit().
    Clear it as it should be.
    The issue is only a cause for a warning in valgrind,
    as Linux frees all process booked memory on exit,
    but nonetheless it is better fixing it.

    Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>

The SDK version is as follows:

zephyr-sdk-0.15.2

sample code

test_exception.cpp

#include "stdio.h"

int test_exception ()
{
  int y = 0;
  try {
      if( y == 0 )
      {
        throw -1;
      }
  }
  catch (...) {
    printf("caught an exception! \n");
  }

  return 0;
}

extern "C" {
int test_exception_demo ()
{
   test_exception();
   return 0;
}

}

main.c

void helloLoop(const char *my_name,
           struct k_sem *my_sem, struct k_sem *other_sem)
{
    const char *tname;
    uint8_t cpu;
    struct k_thread *current_thread;

    while (1) {
        /* take my semaphore */
        k_sem_take(my_sem, K_FOREVER);

        current_thread = k_current_get();
        tname = k_thread_name_get(current_thread);
#if CONFIG_SMP
        cpu = arch_curr_cpu()->id;
#else
        cpu = 0;
#endif
        /* say "hello" */
        if (tname == NULL) {
            printk("%s: Hello World from cpu %d on %s!\n",
                my_name, cpu, CONFIG_BOARD);
        } else {
            printk("%s: Hello World from cpu %d on %s!\n",
                tname, cpu, CONFIG_BOARD);
        }
        test_exception_demo();

        /* wait a while, then let other thread have a turn */
        k_busy_wait(100000);
        k_msleep(SLEEPTIME);
        k_sem_give(other_sem);
    }
}

compilation process


west build -b qemu_riscv32  samples/synchronization/ -t guiconfig

Modify the default configuration, only modify GLIBCXX_LIBCPP and CPP_EXCEPTIONS, NEWLIB_LIBC, the detailed configuration is as follows

#
# C++ Language Support
#
CONFIG_CPP=y
# CONFIG_STD_CPP98 is not set
CONFIG_STD_CPP11=y
# CONFIG_STD_CPP14 is not set
# CONFIG_STD_CPP17 is not set
# CONFIG_STD_CPP2A is not set
# CONFIG_STD_CPP20 is not set
# CONFIG_STD_CPP2B is not set
# CONFIG_MINIMAL_LIBCPP is not set
CONFIG_GLIBCXX_LIBCPP=y
# CONFIG_EXTERNAL_LIBCPP is not set
# CONFIG_CPP_MAIN is not set
CONFIG_CPP_EXCEPTIONS=y
# CONFIG_CPP_RTTI is not set
CONFIG_CPP_STATIC_INIT_GNU=y

#
# C Library
#
CONFIG_SUPPORT_MINIMAL_LIBC=y
# CONFIG_MINIMAL_LIBC is not set
CONFIG_NEWLIB_LIBC=y
# CONFIG_EXTERNAL_LIBC is not set
CONFIG_HAS_NEWLIB_LIBC_NANO=y
# CONFIG_NEWLIB_LIBC_NANO is not set
CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=8192
# CONFIG_NEWLIB_LIBC_FLOAT_PRINTF is not set
# CONFIG_NEWLIB_LIBC_FLOAT_SCANF is not set
# CONFIG_NEWLIB_LIBC_HEAP_LISTENER is not set
CONFIG_STDOUT_CONSOLE=y
# end of C Library
west build -b qemu_riscv32  samples/synchronization/ -t run

The result of the program execution is as follows, the program has currently crashed

*** Booting Zephyr OS build zephyr-v3.3.0-1108-gbb6fa5707f77 ***
thread_a: Hello World from cpu 0 on qemu_riscv32!

When I debugged, I found that the program crashed in the _Unwind_Find_FDE function

#0  0x80002598 in get_cie_encoding ()
#1  0x80002692 in classify_object_over_fdes ()
#2  0x80002a16 in search_object ()
#3  0x80002ed6 in _Unwind_Find_FDE ()
#4  0x80000f08 in uw_frame_state_for ()
#5  0x800018c6 in uw_init_context_1 ()
#6  0x80001d92 in _Unwind_RaiseException ()
#7  0x800064b2 in __cxa_throw ()
#8  0x80003484 in test_exception () at /home/yang/zephyrproject/zephyr/samples/synchronization/src/test_exception.cpp:14
#9  0x800034b4 in test_exception_demo () at /home/yang/zephyrproject/zephyr/samples/synchronization/src/test_exception.cpp:27
#10 0x8000337c in helloLoop (my_name=my_name@entry=0x80008728 <__func__.0> "threadA", my_sem=my_sem@entry=0x8000d120 <threadA_sem>, other_sem=other_sem@entry=0x8000d130 <threadB_sem>) at /home/yang/zephyrproject/zephyr/samples/synchronization/src/main.c:62
#11 0x800033c0 in threadA (dummy1=dummy1@entry=0x0, dummy2=dummy2@entry=0x0, dummy3=dummy3@entry=0x0) at /home/yang/zephyrproject/zephyr/samples/synchronization/src/main.c:104
#12 0x8000358e in z_thread_entry (entry=0x800033aa <threadA>, p1=0x0, p2=0x0, p3=0x0) at /home/yang/zephyrproject/zephyr/lib/os/thread_entry.c:36
#13 0x00000000 in ?? ()

But when I change the platform and follow the same configuration above, the execution result is normal again

west build -b qemu_cortex_a9  samples/synchronization/ -t guiconfig
west build -b qemu_cortex_a9  samples/synchronization/ -t run

The normal execution results are as follows:

*** Booting Zephyr OS build zephyr-v3.3.0-1108-gbb6fa5707f77 ***
thread_a: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_b: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_a: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_b: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_a: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_b: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
thread_a: Hello World from cpu 0 on qemu_cortex_a9!
caught an exception! 
qemu-system-xilinx-aarch64: terminating on signal 2
github-actions[bot] commented 1 year ago

This issue has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this issue will automatically be closed in 14 days. Note, that you can always re-open a closed issue at any time.

github-actions[bot] commented 1 year ago

This issue has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this issue will automatically be closed in 14 days. Note, that you can always re-open a closed issue at any time.

cfriedt commented 1 year ago

Is there a minimal repro recipe? This works fine.

twister -i -p qemu_riscv32 -T tests/lib/cpp/libcxx
wsipak commented 1 year ago

I've managed to run the code you provided on qemu_riscv32. It appears that using C++ exceptions requires a larger stack.

I'm using Zephyr version 56a8123eed2eb27.

I have reproduced the source code of the application based on the information provided in the issue.

I've also modified samples/synchronization/prj.conf accordingly:

CONFIG_STDOUT_CONSOLE=y
# enable to use thread names
CONFIG_THREAD_NAME=y
CONFIG_SCHED_CPU_MASK=y

CONFIG_CPP=y
CONFIG_STD_CPP11=y
CONFIG_GLIBCXX_LIBCPP=y
CONFIG_CPP_EXCEPTIONS=y

CONFIG_NEWLIB_LIBC=y

CONFIG_STDOUT_CONSOLE=y

We already know this doesn't work. However, after I modified the stack size this way:

diff --git a/samples/synchronization/src/main.c b/samples/synchronization/src/main.c
index e9735d9781..b5b09b12c8 100644
--- a/samples/synchronization/src/main.c
+++ b/samples/synchronization/src/main.c
@@ -19,7 +19,7 @@
 #define PIN_THREADS (IS_ENABLED(CONFIG_SMP) && IS_ENABLED(CONFIG_SCHED_CPU_MASK))

 /* size of stack area used by each thread */
-#define STACKSIZE 1024
+#define STACKSIZE 2800

 /* scheduling priority used by each thread */
 #define PRIORITY 7

The application works, and the output I'm getting is:

*** Booting Zephyr OS build zephyr-v3.4.0-1749-g568cb5f96e55 ***
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_a: Hello World from cpu 0 on qemu_riscv32!
caught an exception!
thread_b: Hello World from cpu 0 on qemu_riscv32!
caught an exception!

I don't know why exactly the default stack size is not sufficient on RISC-V while it is sufficient on ARM. Nonetheless, it looks like C++ exceptions are not necesssarily mishandled but rather need a larger stack.

cfriedt commented 1 year ago

I don't know why exactly the default stack size is not sufficient on RISC-V while it is sufficient on ARM.

Typically we want to make the default stack sizes as small as possible, rather than large enough to ensure that any possible application builds properly.

Nonetheless, it looks like C++ exceptions are not necesssarily mishandled but rather need a larger stack.

It sounds like you have solved your problem 👍