已关闭
[Bug-Report|缺陷反馈]: error_manager 析构的时候发生崩溃 #588
chenqi317创建于  6月4日关闭于  6月24日
chenqi317
6月4日 创建

Thanks for sending an issue! Please fill in the following template to help quickly solve your problem.

Describe the current behavior / 问题描述 (Mandatory / 必填)

程序退出时发生崩溃, 崩溃栈如下:
#0 0x0000ffffb6b0a238 in ?? () from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/libexe_graph.so
#1 0x0000ffffb6b0a324 in ?? () from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/libexe_graph.so
#2 0x0000ffffb6b10f80 in std::basic_string<char, std::char_traits, std::allocator >::_Rep::_M_dispose(std::allocator const&) ()
from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/libexe_graph.so
#3 0x0000ffffb6b104ac in std::basic_string<char, std::char_traits, std::allocator >::~basic_string() () from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/libexe_graph.so
#4 0x0000aaaaef5bcef4 in std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > >::~pair (
this=0xffffa80099a0, __in_chrg=) at /usr/include/c++/13/bits/stl_pair.h:187
#5 0x0000aaaaef5ce140 in std::__new_allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > >::destroy<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > (
__p=0xffffa80099a0, this=0xffffa8012768) at /usr/include/c++/13/bits/new_allocator.h:198
#6 std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > > >::destroy<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > (
__p=0xffffa80099a0, __a=...) at /usr/include/c++/13/bits/alloc_traits.h:558
#7 std::_Rb_tree<std::basic_string<char, std::char_traits, std::allocator >, std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > >, std::_Select1st<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > >, std::less<std::basic_string<char, std::char_traits, std::allocator > >, std::allocator<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > >::_M_destroy_node (this=0xffffa8012768, __p=0xffffa8009980) at /usr/include/c++/13/bits/stl_tree.h:625
#8 0x0000aaaaef5c8744 in std::_Rb_tree<std::basic_string<char, std::char_traits, std::allocator >, std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > >, std::_Select1st<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > >, std::less<std::basic_string<char, std::char_traits, std::allocator > >, std::allocator<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > >::_M_drop_node (this=0xffffa8012768, __p=0xffffa8009980)
at /usr/include/c++/13/bits/stl_tree.h:633
#9 0x0000aaaaef5c445c in std::_Rb_tree<std::basic_string<char, std::char_traits, std::allocator >, std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > >, std::_Select1st<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > >, std::less<std::basic_string<char, std::char_traits, std::allocator > >, std::allocator<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > >::_M_erase (this=0xffffa8012768, __x=0xffffa8009980) at /usr/include/c++/13/bits/stl_tree.h:1938
#10 0x0000aaaaef5c440c in std::_Rb_tree<std::basic_string<char, std::char_traits, std::allocator >, std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > >, std::_Select1st<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > >, std::less<std::basic_string<char, std::char_traits, std::allocator > >, std::allocator<std::pair<std::basic_string<char, std::char_traits, std::allocator > const, std::basic_string<char, std::char_traits, std::allocator > > > >::_M_erase (this=0xffffa8012768, __x=0xffffa8010ad0) at /usr/include/c++/13/bits/stl_tree.h:1936
#11 0x0000ffffb5452324 in std::_Rb_tree<unsigned long, std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > >, std::_Select1st<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > >, std::less, std::allocator<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > > >::_M_erase(std::_Rb_tree_node<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > >) ()
from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/liberror_manager.so
--Type for more, q to quit, c to continue without paging--
#12 0x0000ffffb54522ec in std::_Rb_tree<unsigned long, std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > >, std::_Select1st<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > >, std::less, std::allocator<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > > >::_M_erase(std::_Rb_tree_node<std::pair<unsigned long const, std::vector<error_message::ErrorItem, std::allocator<error_message::ErrorItem> > > >
) ()
from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/liberror_manager.so
#13 0x0000ffffb5453020 in ErrorManager::~ErrorManager() () from /home/developer/Ascend/cann-9.0.0-beta.2/lib64/liberror_manager.so
#14 0x0000ffffb54df228 in __run_exit_handlers (status=0, listp=0xffffb5650670 <__exit_funcs>, run_list_atexit=run_list_atexit@entry=true, run_dtors=run_dtors@entry=true) at ./stdlib/exit.c:108
#15 0x0000ffffb54df30c in __GI_exit (status=) at ./stdlib/exit.c:138
#16 0x0000ffffb54c84c8 in __libc_start_call_main (main=main@entry=0xaaaaee4cc9d8 <main(int, char**)>, argc=argc@entry=1, argv=argv@entry=0xffffd7d962a8) at ../sysdeps/nptl/libc_start_call_main.h:74
#17 0x0000ffffb54c8598 in __libc_start_main_impl (main=0xaaaaee4cc9d8 <main(int, char**)>, argc=1, argv=0xffffd7d962a8, init=, fini=, rtld_fini=,
stack_end=) at ../csu/libc-start.c:360
#18 0x0000aaaaee4cc8f0 in _start ()

Environment / 环境信息 (Mandatory / 必填)

ascend910B4

Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)

ErrorManager 崩溃问题分析与整改方案

问题现象

崩溃堆栈

#0  0x0000ffffb6b0a238 in ?? () from libexe_graph.so
#1  0x0000ffffb6b0a324 in ?? () from libexe_graph.so
#2  0x0000ffffb6b10f80 in std::string::_Rep::_M_dispose() from libexe_graph.so
#3  0x0000ffffb6b104ac in std::string::~string() from libexe_graph.so
#4  std::pair<std::string, std::string>::~pair()
#5-#10  std::map 内部节点析构
#11-#12 std::map<uint64_t, std::vector<ErrorItem>>::_M_erase()
#13 ErrorManager::~ErrorManager()
#14 __run_exit_handlers() (exit() 系统调用)
#15-#18 程序退出流程

崩溃特征

  • 崩溃时机: 程序退出时(exit()ErrorManager::~ErrorManager()
  • 崩溃位置: libexe_graph.so 的内存操作函数
  • 崩溃对象: std::string::~string() 析构时访问已释放内存
  • 根本原因: Static Destruction Order Fiasco(静态析构顺序灾难)

问题根本原因分析

1. 跨库内存管理混乱

问题代码位置: error_manager.cc:488

ErrorManager::ErrorItem error_item = {
    error_code, error_info.error_title, error_message,
    error_info.possible_cause, error_info.solution, args_map,  // ← 问题根源
    report_time};

时序分析:

【运行阶段】
libexe_graph.so 调用 ErrorManager::ReportErrMessage(error_code, args_map)
  ↓
args_map 包含来自 libexe_graph.so 的 std::string 对象
  ↓
拷贝 args_map 到 ErrorItem(std::map 深拷贝,但 string 可能共享内存)
  ↓
ErrorItem 存入单例容器 error_message_per_work_id_
  ↓
【退出阶段】
exit() 系统调用
  ↓
动态库析构顺序不确定(依赖链接顺序、加载顺序)
  ↓
libexe_graph.so 先析构(假设)
  ↓
释放 libexe_graph.so 管理的内存分配器
  ↓
ErrorManager 单例后析构
  ↓
清理 error_message_per_work_id_
  ↓
析构 ErrorItem
  ↓
析构 args_map (std::map<std::string, std::string>)
  ↓
析构 std::string(指向已释放的 libexe_graph.so 内存)
  ↓
访问无效内存 → 崩溃!

2. std::string 内存管理机制

GCC std::string 实现:

  • SSO (Small String Optimization): 长度 ≤ 15 字符,存储在对象内部
  • 动态分配: 长度 > 15 字符,动态分配内存

跨库传递问题:

  1. 不同库可能使用不同的编译器版本(ABI 不兼容)
  2. 内存分配器可能不一致(每个库有自己的内存池)
  3. 析构时释放内存的归属权混乱

关键点:

  • args_map 通过 const std::map<std::string, std::string> &args_map 引用传递
  • 第 488 行拷贝构造 args_map,但内部的 std::string 可能共享底层内存
  • 程序退出时,内存归属权混乱导致 double-free 或 UAF

3. 单例析构顺序问题

单例模式陷阱:

ErrorManager &ErrorManager::GetInstance() {
  static ErrorManager instance;  // ← 静态局部变量
  return instance;
}

析构顺序依赖:

  • 静态对象的析构顺序与构造顺序相反
  • 但跨库的静态对象析构顺序不可控
  • libexe_graph.so 和 liberror_manager.so 的析构顺序不确定

4. 缺少显式清理机制

原有代码:

~ErrorManager() = default;  // ← 隐式析构,依赖编译器生成

问题:

  • 没有显式控制容器清理时机
  • 依赖编译器自动析构,无法保证内存安全
  • 没有提供用户主动清理的接口

避免的使用方式

❌ 错误示范 1: 跨库传递复杂 STL 对象

// libexe_graph.so
std::map<std::string, std::string> args_map;
args_map["key"] = "value_from_exe_graph";  // ← 内存来自 exe_graph

// 调用 ErrorManager
ErrorManager::GetInstance().ReportErrMessage(error_code, args_map);
// ← args_map 内的 string 可能共享 exe_graph 的内存

建议: 使用 C 接口或确保深拷贝

✅ 正确示范: 使用 C 接口

// C 接口封装
extern "C" int32_t ReportPredefinedErrMsgForC(
    const char *error_code, 
    const char **key, 
    const char **value, 
    unsigned long arg_num);

注意事项

1. 跨库内存管理

潜在风险:

  • args_map 内的 std::string 可能来自其他库
  • 深拷贝不保证内存分配器一致
  • SSO 机制可能导致短字符串内嵌,长字符串外存

缓解措施:

  • 已添加显式析构函数,提前清理容器
  • 建议使用 C 接口避免跨库传递 std::string
  • 控制程序退出时的清理顺序

2. 线程安全

已修复问题:

  • ParseJsonFormatString 修改 error_map_ 时加锁
  • ReportErrMessage 访问 error_map_ 时提前加锁
  • 析构函数和 Clear() 接口都加锁

建议:

  • 多线程环境下确保调用 Clear() 前所有线程已停止
  • 避免在析构过程中并发访问单例

3. 库加载顺序

动态库析构顺序影响因素:

  • 链接顺序(-L-l 参数顺序)
  • 加载顺序(dlopen() 调用顺序)
  • 依赖关系(依赖库先加载,后析构)

建议:

  • 在程序退出前显式调用 Clear()
  • 避免依赖不确定的析构顺序

影响范围评估

影响组件

  1. ErrorManager 单例: 所有使用 ErrorManager 的代码
  2. 跨库调用者: libexe_graph.so、libge_runner.so 等
  3. 程序退出流程: 所有调用 exit() 的程序

不影响范围

  1. 正常运行阶段: 运行期间的 API 调用不受影响
  2. 单线程程序: 单线程环境下基本无影响
  3. 无跨库传递: 不传递复杂 STL 对象的调用

退出不发生coredump

测试 尝试修改如下代码后,崩溃不再出现
error_manager/error_manager.cc
ErrorManager::ErrorItem error_item = {
error_code, error_info.error_title, error_message, error_info.possible_cause, error_info.solution, {},
report_time};

Special notes for this issue/备注 (Optional / 选填)

请尽快确认修改

likedislike
Cchenqi317
6月4日 issue类型由 任务 改变为 缺陷
Cchenqi317
6月4日 修改了issue 的描述
Cchenqi317
6月4日 修改了issue 的描述
wangtao成员
6月4日 评论:

@chenqi317
建议补充下用例使用方式 和必要环境,OS,日志等信息
@zhanj @gcw_3CxwvBIO 请分析下过程

likedislike
chenqi317
6月4日 评论:

复现方法:
使用cannlab 一站式平台cpu 环境
使用ops-nn 仓
执行bash build.sh -u --ophost --noexec
./build/tests/ut/op_host/nn_op_host_ut

崩溃栈日志 如上

likedislike
wangtao成员
6月10日 评论:

@zhanj @gcw_3CxwvBIO 请更新下进展

likedislike
chenqi317
6月12日 评论:

有什么进展了吗?

likedislike
chenqi317
6月24日 评论:

最新包暂时未覆盖该问题了,先暂时关闭了

likedislike
Cchenqi317
6月24日 issue状态由 待办的 改变为 已完成
Cchenqi317
6月24日 关闭了 issue