最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Statically linked pthread and terminate called after throwing an instance of 'std::system_error' - Stack O

programmeradmin3浏览0评论

I have seen terminate called after throwing an instance of 'std::system_error' - and while I believe it talks about the similar underlying problem, the proposed solution there does not work in my case.

I want to develop a cross-platform code that uses a threaded periodic timer function; here is the code:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)

project(myProject LANGUAGES C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)

SET(CMAKE_INCLUDE_CURRENT_DIR ON)

add_executable(myProject
  ${CMAKE_SOURCE_DIR}/main.cpp
)

target_compile_options(myProject PRIVATE -Wall -Wextra)

#target_link_libraries(myProject PUBLIC -static) # this causes "terminate called after throwing an instance of 'std::system_error'; what():  Unknown error 1992352464"

target_link_libraries(myProject PUBLIC
  stdc++fs
)

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(myProject PUBLIC Threads::Threads)

main.cpp

#include <cinttypes>
#include <cstdlib>
#include <iostream>
#include <ostream>
#include <fstream>

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>

#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;

#define CHOSEN_CLOCK std::chrono::high_resolution_clock

// class Timer below derived from:
// .h
// /
// 


class Timer {
    std::atomic<bool> active{true};
  public:
    std::chrono::time_point<CHOSEN_CLOCK> timer_entry_ts; // SO:31497531
    template<typename Function>
    void setInterval(Function function, int interval);
    void stop();
};

template<typename Function>
void Timer::setInterval(Function function, int interval) {
  active = true;
  std::thread t([=]() {
    while(active.load()) {
      timer_entry_ts = CHOSEN_CLOCK::now();
      function();
      if(!active.load()) return;
      std::this_thread::sleep_until(timer_entry_ts + std::chrono::milliseconds(interval));
    }
  });
  t.detach(); // it seems this causes "terminate called after throwing an instance of 'std::system_error' ; what():  Unknown error 1992278736"; if we do a -static linkage in build on Linux
}

void Timer::stop() {
  active = false;
}


Timer mtimer;
std::chrono::time_point<CHOSEN_CLOCK> last_entry_ts{};

void timer_function(void)
{
  long long int delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(mtimer.timer_entry_ts-last_entry_ts).count();
  printf("%7.3f ms\n", delta_micros/1000.0f);
  last_entry_ts = mtimer.timer_entry_ts;
}

int main(const int argc, char** argv)
{
  (void)argc; // warning: unused parameter
  (void)argv; // warning: unused parameter
  static_cast<void>(pthread_create);
  static_cast<void>(pthread_cancel);

  #ifdef _GLIBCXX_HAS_GTHREADS
  printf("_GLIBCXX_HAS_GTHREADS is defined.\n");
  #else
  printf("_GLIBCXX_HAS_GTHREADS is NOT defined!\n");
  #endif

  last_entry_ts = CHOSEN_CLOCK::now();
  mtimer.setInterval(timer_function, 25); // repeat timer_function at 25 ms period
  std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // wait for 2 seconds
  mtimer.stop();
  printf("Thread stopped\n");

  std::exit(EXIT_SUCCESS);
}

I build this code using:

mkdir build && cd build
cmake ../ -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles"
make

... and I build it both in

  • MINGW64 (under Windows 10), where I have g++.exe (Rev3, Built by MSYS2 project) 14.2.0
  • Raspbian Stretch on Raspberry Pi 3B+, where I have g++ (Raspbian 6.3.0-18+rpi1+deb9u1) 6.3.0 20170516

If I build the code as posted, then I can see the threaded timer_function run on both platforms.

However, if the line target_link_libraries(myProject PUBLIC -static) is uncommented (enabled) in CMakeLists.txt and the project rebuilt - then it again works in MINGW64/Windows, howevers it fails on Raspbian with:

$ ./myProject
_GLIBCXX_HAS_GTHREADS is defined.
  0.202 ms
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error 1995920080
Aborted

Now, it is quite unfortunate that I get "Unknown error 1995920080" so I cannot look in further into it; however, the "0.202 ms" printout tells me timer_function has ran once. Best I could do was to confirm via objdump -S ./myProject | less that t.detach(); seems to call _ZNSt6thread6detachEv - and using gdb --args ./myProject I can set a breakpoint on that function, however gdb does not let me step through it, not even in TUI mode (it says "[ No Source Available ]"); all I can say, is that once I press step in gdb after hitting that breakpoint, I get the above error:

(gdb) b _ZNSt6thread6detachEv
Breakpoint 1 at 0x612b0
(gdb) r
...
_GLIBCXX_HAS_GTHREADS is defined.
[New Thread 0x76ffc2d0 (LWP 32461)]
  1.260 ms

Thread 1 "myProject" hit Breakpoint 1, 0x000612b0 in std::thread::detach() ()
(gdb) s
Single stepping until exit from function _ZNSt6thread6detachEv,
which has no line number information.
24859.580 ms
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error 1996473040

Thread 1 "myProject" received signal SIGABRT, Aborted.
raise (sig=<optimized out>) at ../sysdeps/unix/sysv/linux/raise.c:51
51      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  raise (sig=<optimized out>) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x000b6eb8 in abort ()
#2  0x00000020 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

(Note: I saw here /include/bits/std_thread.h#L324 that detach() might end up as inline void thread::detach() { std::__throw_system_error(EINVAL); } if _GLIBCXX_HAS_GTHREADS is not defined, but apparently it is, both in my MINGW64/Windows and Raspbian environments)

Any ideas how can I get the -static build of this example to work also on Raspbian Stretch?

I have seen terminate called after throwing an instance of 'std::system_error' - and while I believe it talks about the similar underlying problem, the proposed solution there does not work in my case.

I want to develop a cross-platform code that uses a threaded periodic timer function; here is the code:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)

project(myProject LANGUAGES C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)

SET(CMAKE_INCLUDE_CURRENT_DIR ON)

add_executable(myProject
  ${CMAKE_SOURCE_DIR}/main.cpp
)

target_compile_options(myProject PRIVATE -Wall -Wextra)

#target_link_libraries(myProject PUBLIC -static) # this causes "terminate called after throwing an instance of 'std::system_error'; what():  Unknown error 1992352464"

target_link_libraries(myProject PUBLIC
  stdc++fs
)

set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(myProject PUBLIC Threads::Threads)

main.cpp

#include <cinttypes>
#include <cstdlib>
#include <iostream>
#include <ostream>
#include <fstream>

#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>

#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;

#define CHOSEN_CLOCK std::chrono::high_resolution_clock

// class Timer below derived from:
// https://raw.githubusercontent/99x/timercpp/refs/heads/master/timercpp.h
// https://www.fluentcpp/2018/12/28/timer-cpp/
// https://stackoverflow/q/21057676


class Timer {
    std::atomic<bool> active{true};
  public:
    std::chrono::time_point<CHOSEN_CLOCK> timer_entry_ts; // SO:31497531
    template<typename Function>
    void setInterval(Function function, int interval);
    void stop();
};

template<typename Function>
void Timer::setInterval(Function function, int interval) {
  active = true;
  std::thread t([=]() {
    while(active.load()) {
      timer_entry_ts = CHOSEN_CLOCK::now();
      function();
      if(!active.load()) return;
      std::this_thread::sleep_until(timer_entry_ts + std::chrono::milliseconds(interval));
    }
  });
  t.detach(); // it seems this causes "terminate called after throwing an instance of 'std::system_error' ; what():  Unknown error 1992278736"; if we do a -static linkage in build on Linux
}

void Timer::stop() {
  active = false;
}


Timer mtimer;
std::chrono::time_point<CHOSEN_CLOCK> last_entry_ts{};

void timer_function(void)
{
  long long int delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(mtimer.timer_entry_ts-last_entry_ts).count();
  printf("%7.3f ms\n", delta_micros/1000.0f);
  last_entry_ts = mtimer.timer_entry_ts;
}

int main(const int argc, char** argv)
{
  (void)argc; // warning: unused parameter
  (void)argv; // warning: unused parameter
  static_cast<void>(pthread_create);
  static_cast<void>(pthread_cancel);

  #ifdef _GLIBCXX_HAS_GTHREADS
  printf("_GLIBCXX_HAS_GTHREADS is defined.\n");
  #else
  printf("_GLIBCXX_HAS_GTHREADS is NOT defined!\n");
  #endif

  last_entry_ts = CHOSEN_CLOCK::now();
  mtimer.setInterval(timer_function, 25); // repeat timer_function at 25 ms period
  std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // wait for 2 seconds
  mtimer.stop();
  printf("Thread stopped\n");

  std::exit(EXIT_SUCCESS);
}

I build this code using:

mkdir build && cd build
cmake ../ -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles"
make

... and I build it both in

  • MINGW64 (under Windows 10), where I have g++.exe (Rev3, Built by MSYS2 project) 14.2.0
  • Raspbian Stretch on Raspberry Pi 3B+, where I have g++ (Raspbian 6.3.0-18+rpi1+deb9u1) 6.3.0 20170516

If I build the code as posted, then I can see the threaded timer_function run on both platforms.

However, if the line target_link_libraries(myProject PUBLIC -static) is uncommented (enabled) in CMakeLists.txt and the project rebuilt - then it again works in MINGW64/Windows, howevers it fails on Raspbian with:

$ ./myProject
_GLIBCXX_HAS_GTHREADS is defined.
  0.202 ms
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error 1995920080
Aborted

Now, it is quite unfortunate that I get "Unknown error 1995920080" so I cannot look in further into it; however, the "0.202 ms" printout tells me timer_function has ran once. Best I could do was to confirm via objdump -S ./myProject | less that t.detach(); seems to call _ZNSt6thread6detachEv - and using gdb --args ./myProject I can set a breakpoint on that function, however gdb does not let me step through it, not even in TUI mode (it says "[ No Source Available ]"); all I can say, is that once I press step in gdb after hitting that breakpoint, I get the above error:

(gdb) b _ZNSt6thread6detachEv
Breakpoint 1 at 0x612b0
(gdb) r
...
_GLIBCXX_HAS_GTHREADS is defined.
[New Thread 0x76ffc2d0 (LWP 32461)]
  1.260 ms

Thread 1 "myProject" hit Breakpoint 1, 0x000612b0 in std::thread::detach() ()
(gdb) s
Single stepping until exit from function _ZNSt6thread6detachEv,
which has no line number information.
24859.580 ms
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error 1996473040

Thread 1 "myProject" received signal SIGABRT, Aborted.
raise (sig=<optimized out>) at ../sysdeps/unix/sysv/linux/raise.c:51
51      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  raise (sig=<optimized out>) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x000b6eb8 in abort ()
#2  0x00000020 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

(Note: I saw here https://github/gcc-mirror/gcc/blob/73987e69/libstdc%2B%2B-v3/include/bits/std_thread.h#L324 that detach() might end up as inline void thread::detach() { std::__throw_system_error(EINVAL); } if _GLIBCXX_HAS_GTHREADS is not defined, but apparently it is, both in my MINGW64/Windows and Raspbian environments)

Any ideas how can I get the -static build of this example to work also on Raspbian Stretch?

Share Improve this question asked Mar 19 at 13:41 sdbbssdbbs 5,5947 gold badges57 silver badges121 bronze badges 5
  • 2 unrelated: you dont need (void)argc; // warning: unused parameter , when you just write int main(), and then on the next lines you use a static_cast<void> which is basically doing the same, just nicer. Inconsistency is the enemy of claritry. I would even prefer all (void), but better of course use static_cast<void> consistently – 463035818_is_not_an_ai Commented Mar 19 at 13:45
  • 1 Alternatively, [[maybe_unused]] on the unused parameters. (But I agree with ^ and would just omit them altogether via int main().) – Eljay Commented Mar 19 at 13:49
  • 2 why use -static on linux ? it is needed on windows because windows uses msvcrt, and pthread on windows is just a thin wrapper around win32 API, but linux should ship gcc libraries and pthreads that depend on the system libc, you shouldn't static link pthreads on linux, worst case is you are using a newer gcc than the one on system then you just -static-libstdc++ -static-libgcc – Ahmed AEK Commented Mar 19 at 14:01
  • Thanks all, advice much appreciated; @AhmedAEK - -static on linux because 1) I just want to have as few conditionals as possible in the CMakeLists.txt and 2) should I have to use the executable on a different (32-bit) Raspbian, I'd have wanted to avoid possible incompatibilities as much as possible. Btw, would -static-libstdc++ -static-libgcc also link in/handle pthreads? – sdbbs Commented Mar 19 at 14:42
  • 1 you'll have to rebuild it on any other OS anyway because of libc (and syscalls), and the extra conditional is not the end of the world. and no, you'll still need to link pthread dynamically. – Ahmed AEK Commented Mar 19 at 15:43
Add a comment  | 

2 Answers 2

Reset to default 1

Why this seems to happen when -static is used is really the wrong question.

The problem is that you reach the end of main and exit your program without ensuring that the background thread you created has actually been stopped.

You can't just exit from main while other threads are running; signaling the thread to stop is not enough, so you need to either change your stop code to wait for the thread to stop, or to provide another mechanism (e.g. by returning the std::thread object and calling join.

Ok, this maybe does not answer the question as posed - but at least, it will provide a bit more understanding into where does the error occur; and it does indeed seem to occur in thread::detach.

So, since on Raspbian Stretch I have g++ 6.3.0, I looked up thread::detach for that version (unfortunately have to git clone --depth 1 --branch releases/gcc-6.3.0 https://github/gcc-mirror/gcc.git [ref], can't search code directly in GitHub in a given tag) and found it in https://github/gcc-mirror/gcc/blob/releases/gcc-6.3.0/libstdc%2B%2B-v3/src/c%2B%2B11/thread :

  void
  thread::detach()
  {
    int __e = EINVAL;

    if (_M_id != id())
      __e = __gthread_detach(_M_id._M_thread);

    if (__e)
      __throw_system_error(__e);

    _M_id = id();
  }

So, indeed, thread::detach itself can throw a system error, if the return from __gthread_detach is anything but zero. And __gthread_detach merely calls pthread_detach, whose docs say:

RETURN VALUE
       On success, pthread_detach() returns 0; on error, it returns an
       error number.

ERRORS
       EINVAL thread is not a joinable thread.
       ESRCH  No thread with the ID thread could be found.

... although, above I get neither 3 (ESRCH, No such process) nor 22 (EINVAL, Invalid argument), as numeric error value.

I tried a couple of more things in order to get better debug overview, which ultimately failed to give me more debug information:

  • Did sudo apt install libstdc++6-6-dbg to get libstc++ debug symbols on Raspbian - had to manually look into http://legacy.raspbian./raspbian/pool/main/g/gcc-6/ to see what package would correspond
  • Used target_compile_definitions(myProject PUBLIC -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_BACKTRACE) in CMakeLists.txt, so as to use the libstdc++ debug mode but that did not quite work either (especially no backtraces because I could not find libstdc++exp or libstdc++_libbacktrace to link against on this platform)
  • Tried to link with target_link_libraries(myProject PUBLIC /usr/lib/arm-linux-gnueabihf/debug/libstdc++.a -static-libgcc -static) - but since I did not have the source files, did not get much info from this in gdb

So, had to go to inspect direct assembly in gdb, and even if I do not know assembly, it at least revealed something. Couple of caveats:

  • If gdb complains of "[ No Source Available ]", then in gdb TUI (Ctrl-x Ctrl-a) mode, enter layout asm to make the upper window display assembly ... use stepi or nexti (which can be abbreviated to si or ni) to step through your machine code
  • On my Raspbian, the window display gets messed up after every stepi command - to get around this, can use the refresh approach - and we can even directly redefine si as:
define si
  stepi
  refresh
end

So, here is how the gdb session looks like:

$  gdb --args ./myProject
GNU gdb (Raspbian 7.12-6) 7.12.0.20161007-git
...
(gdb) define si
Type commands for definition of "si".
End with a line saying just "end".
>stepi
>refresh
>end

(gdb) b __gthread_detach
Breakpoint 1 at 0x4771c

(gdb) r
Starting program: ~/code/test/build/myProject
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/arm-linux-gnueabihf/libthread_db.so.1".
_GLIBCXX_HAS_GTHREADS is defined.
[New Thread 0x76ffc2d0 (LWP 1367)]
  1.676 ms

Thread 1 "myProject" hit Breakpoint 1, 0x0004771c in __gthread_detach(unsigned long) ()

(gdb) bt
#0  0x0004771c in __gthread_detach(unsigned long) ()
#1  0x76ffc2d0 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

(gdb) C-x C-a
(gdb) layout asm

At this point, the gdb screen (after some scrolling) looks like this:

   │0x47710 <_ZL16__gthread_detachm+24>                     mov    r3, r0                                    │
   │0x47714 <_ZL16__gthread_detachm+28>                     mov    r0, r3                                    │
   │0x47718 <_ZL16__gthread_detachm+32>                     sub    sp, r11, #4                               │
B+>│0x4771c <_ZL16__gthread_detachm+36>                     pop    {r11, pc}                                 │
   │0x47720 <_ZN9__gnu_cxxL18__exchange_and_addEPVii>       push   {r11}           ; (str r11, [sp, #-4]!)   │
   │0x47724 <_ZN9__gnu_cxxL18__exchange_and_addEPVii+4>     add    r11, sp, #0                               │   
   └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
multi-thre Thread 0x18c000 (LW In: __gthread_detach                                         L??   PC: 0x4771c 
(gdb)

So, it seems gdb managed to break at the very exit of __gthread_detach ... So, the next thing is si:

   │0x47e24 <_ZNSt6thread6detachEv+76>      bl     0x476f8 <_ZL16__gthread_detachm>                          │
  >│0x47e28 <_ZNSt6thread6detachEv+80>      str    r0, [r11, #-8]                                            │
   │0x47e2c <_ZNSt6thread6detachEv+84>      ldr    r3, [r11, #-8]                                            │
   │0x47e30 <_ZNSt6thread6detachEv+88>      cmp    r3, #0                                                    │
   │0x47e34 <_ZNSt6thread6detachEv+92>      beq    0x47e40 <_ZNSt6thread6detachEv+104>                       │   
   │0x47e38 <_ZNSt6thread6detachEv+96>      ldr    r0, [r11, #-8]                                            │
   │0x47e3c <_ZNSt6thread6detachEv+100>     bl     0x8959c <_ZSt20__throw_system_errori>                     │
   │0x47e40 <_ZNSt6thread6detachEv+104>     sub    r3, r11, #12                                              │
   │0x47e44 <_ZNSt6thread6detachEv+108>     mov    r0, r3                                                    │
   │0x47e48 <_ZNSt6thread6detachEv+112>     bl     0x10aa0 <std::thread::id::id()>                           │
   │0x47e4c <_ZNSt6thread6detachEv+116>     ldr    r3, [r11, #-24] ; 0xffffffe8                              │   
   └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
multi-thre Thread 0x18c000 (LW In: std::thread::detach                                      L??   PC: 0x47e34
(gdb) si
0x00047e28 in std::thread::detach() ()
(gdb) si
0x00047e2c in std::thread::detach() ()
(gdb) si
0x00047e30 in std::thread::detach() ()
(gdb) si
0x00047e34 in std::thread::detach() ()
(gdb) info registers r0
r0             0x76ffc2d0       1996473040
(gdb) info registers r3
r3             0x76ffc2d0       1996473040
(gdb) si
0x00047e38 in std::thread::detach() ()
(gdb) si
0x00047e3c in std::thread::detach() ()
(gdb) si
0x0008959c in std::__throw_system_error(int) ()

SO, in the above snippet, we start right at the next instruction after the bl 0x476f8 <_ZL16__gthread_detachm>. The next few instructions follow in sequence, and if we assume that also here return values that fit into a register are simply stored into the first register (r0), then the return from __gthread_detach is in r0, which has the non-zero value 1996473040; that value seems to get copied to register r3 for purposes of comparing to zero, and since that comparison fails, we never branch via beq 0x47e40 <_ZNSt6thread6detachEv+104>, and one instruction later, the system error is thrown via bl 0x8959c <_ZSt20__throw_system_errori>. And continuing at this point will generate:

(gdb) c
Continuing.
terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error 1996473040

Note that the numeric value for the "Unknown error" is exactly the value of the r0 register inspected earlier.

So the problem is, when linking with -static in this case, that __gthread_detach - that is, pthread_detach - when called from thread::detach returns a non-zero value, indicating an error; and therefore, thread::detach itself throws a system error via __throw_system_error. Now, why would -static have influence on this, is unclear to me ...

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论