关于dispatch_main的__noreturn__属性与pthread_exit行为冲突引发栈变量访问问题的技术咨询
我们最近遇到了一些奇怪的崩溃,排查过程中发现了一个让我们非常困惑的点——dispatch_main的对外契约和它的内部实现似乎存在根本性的矛盾,目前还不确定这是不是导致崩溃的直接原因,想跟大家请教一下。
核心冲突点
官方文档和函数声明里都明确说明:dispatch_main(内部会调用xpc_main)This function never returns,而且函数也标注了__attribute__((__noreturn__))属性。文档里还补充解释,这个函数会“挂起主线程,等待任务提交到主队列”。
但我们通过调试和代码分析发现,dispatch_main内部实际上调用了pthread_exit。而pthread_exit的官方文档明确规定:
线程终止后,访问该线程的局部(自动)变量的结果是未定义的;同时,不能把这些局部变量的引用作为
pthread_exit的value_ptr参数值。
这两个描述完全对立:我们理解的“永不返回”是主线程只是进入休眠状态,它的栈内存会一直保持存活;但pthread_exit的行为相当于直接杀死线程,对应的栈内存会被释放——这种矛盾很容易引发非常隐蔽的内存访问问题。
简化的复现代码
下面是我们实际代码的极度简化版,核心逻辑和我们的真实代码完全一致:
struct asd { int a; }; struct asd* ptr; void fff(void* ctx) { while(true) { printf("%d\n", ptr->a); ptr->a = (ptr->a + 1); usleep(100000); } } int main(int argc, const char * argv[]) { struct asd zxc; zxc.a = 1; ptr = &zxc; dispatch_async_f(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), NULL, fff); dispatch_main(); return 0; }
我们的真实代码是C++实现,在栈上创建了一个对象,然后把它的某个成员的地址暴露为全局指针——原本的假设是,因为dispatch_main永不返回,主线程的栈永远不会被释放,所以这个全局指针的访问是安全的。
我们的验证和额外困惑
- 我们用调试器确认了,
dispatch_main确实会调用pthread_exit终止主线程(而不是单纯休眠) - 更奇怪的是,clang/Xcode提供的所有 sanitizer 工具都没有检测到这个明显的栈内存访问问题
- 我们也查看了libdispatch的相关实现,没有发现对主线程的
pthread_t有任何特殊处理来保留栈内存
我们的问题
目前我们已经通过把栈上的对象改成堆分配的方式临时解决了问题,但还是想搞清楚几个关键问题:
- 是不是我对
__attribute__((__noreturn__))的理解完全错误?是不是只要调用了标注这个属性的函数,后续访问原线程的栈变量本身就是未定义行为? dispatch_main对外宣称“永不返回”但内部调用pthread_exit的行为,这是系统框架的设计问题,还是我们对文档的理解有偏差?
内容来源于stack exchange

