为啥堆栈展开后程序无法到达正确的返回指令?

Posted

技术标签:

【中文标题】为啥堆栈展开后程序无法到达正确的返回指令?【英文标题】:Why program cannot reach proper return instruction after stack unwinding?为什么堆栈展开后程序无法到达正确的返回指令? 【发布时间】:2020-08-16 22:58:35 【问题描述】:

编译器:g++ 9.2.0 操作系统:Windows 10 g++ 调用:

g++ -E main.cpp -v -o main.i
g++ -c main.cpp -v -o main.o 
g++ main.o -v -o main.exe
main.exe

main.cpp:

#include <chrono>
#include <iostream>
#include <string>
#include <exception>
#include <iostream>
//#include <thread>
#include "mingw.thread.h"
struct Object
    struct Exception : public std::exception
        std::string error_;
        Exception(std::string str)
            this->error_ = str;
        
        ~Exception() 
        
        std::string get()
            return error_;
        
    ;
    void DoSomeWork() 
        try 
        std::thread AnotherTh(&Object::GenerateException ,this);
        AnotherTh.detach ();
        while(true);
    
        catch (...) 
            throw ;
        
    
    void GenerateException()
        std::this_thread::sleep_for (std::chrono::seconds(5));
        throw Object::Exception ("Some error");
    
;
int main()
    try
        Object instance;
        std::thread th(&Object::DoSomeWork,std::ref(instance));
        th.join ();
    
    catch (Object::Exception &ex ) 
        std::cout << ex.get ();
    
    catch (std::exception &ex )
        std::cout << ex.what ();
    
    catch (...)
    
    std::cout << "never reach this";
    return 0;

输出:

terminate called after throwing an instance of 'Object::Exception'
  what():  std::exception

我正在使用 新线程 (th) 启动 主线程 并等待它,在 th 内部启动 另一个线程 将引发异常的位置。因此,当它出现时,开始堆栈展开(从 Object::GenerateException 到 Object::DoSomeWork,因为没有更多的调用是 Object::GenerateException 的堆栈)并且管理传递给 Object::DoSomeWork 的 try-catch,有相同的调用链到 main 的 try-catch,因为 Object::DoSomeWork “知道”它是从 main 调用的。

我不明白为什么它不能处理异常并将其传递给 main 的 try-catch。

【问题讨论】:

稍微相关一点:如果您有catch(...) throw; ,那么拥有try catch 有什么意义呢?这只是自然发生的事情(异常在堆栈中向上传递)。 这并没有解决问题,但std::exception 有一个成员函数const char *what(),它返回异常的描述。派生类通常会覆盖该函数,因此捕获std::exception&amp; 的代码可以获得有意义的消息。 【参考方案1】:

为什么c++栈展开后程序无法到达正确的返回指令?

因为您的代码创建了多个线程,而您没有在实际抛出异常的线程中捕获异常。即使调用std::thread 的成员函数join(),异常也不会跨线程传播。

Try blocks 被定义为堆栈的动态构造。 try 块从其内容中捕获由调用动态到达的代码引发的异常。

当你创建一个新线程时,你创建了一个全新的堆栈,它根本不是 try 块的动态上下文的一部分,即使对 pthread_create 或构造可连接 std::thread() 的调用是在try里面。

要捕获源自线程 X 的异常,您必须在线程 X 中使用 try-catch 子句(例如,围绕线程函数中的所有内容,类似于您在 main 中已经执行的操作)。

有关相关问题,请参阅How can I propagate exceptions between threads?。

一个例子:

#include <chrono>
#include <iostream>
#include <string>
#include <exception>
#include <iostream>
#include <thread>


struct Object 

    void DoSomeWork() 
    
        std::cout << "DoSomeWork Thread ID: " << std::this_thread::get_id() << std::endl;
        try 
            std::thread thread(&Object::GenerateException, this);
            thread.detach();
            while(true);
        
        catch (...) 
            std::cout << "Caught exception: " << std::this_thread::get_id() << std::endl;
            throw ;
        
    
    void GenerateException(void)
    
        std::cout << "GenerateException Thread ID: " << std::this_thread::get_id() << std::endl;
        try 
            std::this_thread::sleep_for (std::chrono::seconds(5));
            throw std::runtime_error("Some error");
         catch (...) 
            std::cout << "Caught exception: " << std::this_thread::get_id() << std::endl;
            throw;
        
    
;
int main()

    std::cout << "Main Thread ID: " << std::this_thread::get_id() << std::endl;
    try 
        Object instance;
        std::thread th(&Object::DoSomeWork,std::ref(instance));
        th.join();
    
    catch (const std::exception &ex) 
        std::cout << ex.what() << std::endl;
        std::cout << "Exception caught at: " << std::this_thread::get_id() << std::endl;
    
    std::cout << "never reach this" << std::endl;
    return 0;

输出:

Main Thread ID: 140596684195648
DoSomeWork Thread ID: 140596665124608
GenerateException Thread ID: 140596656670464
Caught exception: 140596656670464
terminate called after throwing an instance of 'std::runtime_error'
  what():  Some error
Aborted (core dumped)

【讨论】:

什么是 Object::GenerateException 在异常抛出时刻调用堆栈? 没找到你! 在这里发布了 qt + mingw 7.3.0 调试器的 Object::GenerateException imgur.com/a/FeoJkxu 调用堆栈的屏幕截图,我很惊讶在 lvl 1 没有 Object::DoSomeWork。 因为它向您显示了未捕获异常的堆栈。但是会有两个更多的堆栈。我可以在我的 gdb 中看到 默认情况下,gdb/debugger 会显示唯一发现问题的线程/堆栈。我可以通过thread all apply bt 看到 gdb 中的所有线程【参考方案2】:

来自this std::thread reference:

...如果通过抛出异常终止,则调用std::terminate

如果线程中抛出未捕获的异常,则程序将被强制终止。

【讨论】:

以上是关于为啥堆栈展开后程序无法到达正确的返回指令?的主要内容,如果未能解决你的问题,请参考以下文章

glibc backtrace() 如何确定哪些堆栈内存是返回地址?

为啥 GCC 会在堆栈上推送一个额外的返回地址?

当堆栈仍然有元素时,为啥会跳过“如果堆栈不为空”条件?

为啥到达 MEX 文件的最后一行后返回 Matlab 需要这么长时间?

IA-32汇编语言笔记——堆栈的作用

MSIL实用指南-返回结果