Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix coredump stack uncomplete when usercode throw exceptions #2256

Merged
merged 1 commit into from
May 23, 2023

Conversation

smbzhang
Copy link
Contributor

@smbzhang smbzhang commented May 19, 2023

What problem does this PR solve?

高版本编译器编译的brpc,用户异常抛到brpc框架内部导致core栈不完整

Issue Number:
程序内部core但是core栈不完整 #1997
运行一段时间,就会core在brpc内部 #1437
crash #985

Problem Summary:
高版本GCC编译器(如GCC12)对于析构函数默认是noexcept的(也有人说是因为C++标准的问题 比如C++11之后才默认noexcept,但是GCC12下我使用C++11和C++17都是默认noexcept的), 低版本的GCC82默认就是noexcept(false)的。

所以当你使用了高版本GCC编译程序的时候,业务代码抛出异常,在进行栈回溯之前会先沿着调用栈寻找处理异常的代码块(专业名词是Landing pad),如果找到Landing pad,那么就会进行栈回溯到Landing pad进行异常处理。找不到Landing pad就会原地调用std::terminate终止程序,这个时候你的core栈就是完整的,不会回溯陷入brpc框架。

从我的测试看,Landing pad 一种是真正的 try catch 块,另一种就是整个调用链上有noexcept属性的函数结束的代码段地址(汇编代码就是一块Unwind_Resume代码块),brpc整个调用链上没有try catch 非 bthread::ExitException 异常的Landing pad。 所以我hook了__gxx_personality_v0函数,找出了具有noexcept属性的函数
image

修改这个析构函数为noexcept(false)之后,__gxx_personality_v0就在brpc框架内找不到Landing pad代码块了,于是就会在抛异常的地方终止,堆栈也就完整了

What is changed and the side effects?

Changed:

Side effects:

  • Performance effects(性能影响):

  • Breaking backward compatibility(向后兼容性):


Check List:

  • Please make sure your changes are compilable(请确保你的更改可以通过编译).
  • When providing us with a new feature, it is best to add related tests(如果你向我们增加一个新的功能, 请添加相关测试).
  • Please follow Contributor Covenant Code of Conduct.(请遵循贡献者准则).

@smbzhang smbzhang force-pushed the master branch 2 times, most recently from bdbd539 to 1d7bfd4 Compare May 22, 2023 08:31
@wwbmmm
Copy link
Contributor

wwbmmm commented May 22, 2023

LGTM

@serverglen serverglen merged commit cb7a6ff into apache:master May 23, 2023
@gydong
Copy link
Contributor

gydong commented May 23, 2023

这个 fix 只对 gcc 生效吗?其他编译器是否有试过

@smbzhang
Copy link
Contributor Author

smbzhang commented May 25, 2023

这个 fix 只对 gcc 生效吗?其他编译器是否有试过

image

clang也是生效的(clang14),这是没有加 noexcept(false)的clang堆栈 (ps: llvm yyds!!!堆栈直接就在baidu::rpc::InputMessenger::InputMessageClosure::~InputMessageClosure 不像gcc那么隐晦,哈哈)
image

这是修复后的:
image

@chenBright
Copy link
Contributor

低版本的GCC82默认就是noexcept(false)的。

那就是说低版本的编译器,堆栈是完整的,只有高版本编译器有这个问题吗?

@smbzhang
Copy link
Contributor Author

smbzhang commented May 25, 2023

低版本的GCC82默认就是noexcept(false)的。

那就是说低版本的编译器,堆栈是完整的,只有高版本编译器有这个问题吗?

应该是取决于编译器是否认为这个析构函数是noexcept的,如果编译器认为它是noexcept的,那么堆栈就会回溯到这个析构函数结束的地方,堆栈就不完整。 因为我是百度的同学,我们厂内只提供了 GCC82 GCC10 GCC12 CLANG10 CLANG14

@chenBright
Copy link
Contributor

低版本的GCC82默认就是noexcept(false)的。

那就是说低版本的编译器,堆栈是完整的,只有高版本编译器有这个问题吗?

应该是取决于编译器是否认为这个析构函数是noexcept的,如果编译器认为它是noexcept的,那么堆栈就会回溯到这个析构函数结束的地方,堆栈就不完整。 因为我是百度的同学,我们厂内只提供了 GCC82 GCC10 GCC12 CLANG10 CLANG14

哦哦 显式指定noexcept(false),就可以抹平编译器之间的差异

@gydong
Copy link
Contributor

gydong commented May 25, 2023

这个 fix 只对 gcc 生效吗?其他编译器是否有试过

image

clang也是生效的(clang14),这是没有加 noexcept(false)的clang堆栈 (ps: llvm yyds!!!堆栈直接就在baidu::rpc::InputMessenger::InputMessageClosure::~InputMessageClosure 不像gcc那么隐晦,哈哈) image

这是修复后的: image

哈哈,这个实践太棒了,赞!
之前关于 GCC 的堆栈,有一点不明白为什么会在 InputMessageClosure::~InputMessageClosure 的析构调用链上直接去调用了 _Unwind_Resume,就好像是 GCC 知道哪个是最终的叶子节点似的。

因为按照我的理解,~InputMessageClosure 作为 landing pad,它本身这一层 frame 要么就全部完成退栈,要么就一点也不退栈,直接 std::terminate()(关于这块,标准中如何规定的不太清楚)。而,GCC 那个堆栈看起来就像是执行了一部分退栈,过程中,突然转去 _Unwind_Resume 了,令我百思不得其解。

@gydong
Copy link
Contributor

gydong commented May 25, 2023

@smbzhang 欢迎加入开发者群,一起探讨 brpc 相关的问题。

@smbzhang
Copy link
Contributor Author

smbzhang commented May 25, 2023

@smbzhang 欢迎加入开发者群,一起探讨 brpc 相关的问题。

我已经在群里了同学, 『里贝里晃倒梅西』

@chenBright
Copy link
Contributor

@smbzhang 服务端支持progressive reader的话,好像会走这个分支。

} else {
QueueMessage(msg.release(), &num_bthread_created,
m->_keytable_pool);
bthread_flush();
num_bthread_created = 0;
}

这时候这个fix会生效吗?

@smbzhang
Copy link
Contributor Author

smbzhang commented May 28, 2023

@smbzhang 服务端支持progressive reader的话,好像会走这个分支。

} else {
QueueMessage(msg.release(), &num_bthread_created,
m->_keytable_pool);
bthread_flush();
num_bthread_created = 0;
}

这时候这个fix会生效吗?

我没用过服务端progressive功能,只用过客户端读取http chunked消息的功能。所以这块不太熟悉没法给你构造一个demo。 不过我看brpc逻辑是把消息放到另外的bthread中处理去了,传递的msg也是原生指针。没有通过析构last_msg来处理http消息(调起callmethod),所以这种情况应该不属于我这个case,整个新的bthread调用栈上没有通过析构函数调起Echo函数,也就不会出现栈缺失了

@chenBright
Copy link
Contributor

@smbzhang 服务端支持progressive reader的话,好像会走这个分支。

} else {
QueueMessage(msg.release(), &num_bthread_created,
m->_keytable_pool);
bthread_flush();
num_bthread_created = 0;
}

这时候这个fix会生效吗?

我没用过服务端progressive功能,只用过客户端读取http chunked消息的功能。所以这块不太熟悉没法给你构造一个demo。 不过我看brpc逻辑是把消息放到另外的bthread中处理去了,传递的msg也是原生指针。没有通过析构last_msg来处理http消息(调起callmethod),所以这种情况应该不属于我这个case,整个新的bthread调用栈上没有通过析构函数调起Echo函数,也就不会出现栈缺失了

DestroyingPtr<InputMessageBase> msg(pr.message());
QueueMessage(last_msg.release(), &num_bthread_created,
m->_keytable_pool);

一次OnNewMessages解出多个协议包,只有最后一个包才通过InputMessageClosure的析构函数调起Echo函数,其他包都是通过 QueueMessage起协程调起Echo函数。

我验证了一下,如你所说,整个新的bthread调用栈上没有通过析构函数调起Echo函数,也就不会出现栈缺失了

学习了,感谢 @smbzhang

@zhoukangsheng
Copy link

@smbzhang 欢迎加入开发者群,一起探讨 brpc 相关的问题。

求一个开发者群号

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants