| 修复A2A3环境下EsTranspose和aclrtGetArgsFromExceptionInfo符号未定义问题
Co-authored-by: y00951989<yangshengjun3@huawei.com>
# message auto-generated for no-merge-commit merge:
!5077 merge A2A3_pr into master
修复A2A3环境下EsTranspose和aclrtGetArgsFromExceptionInfo符号未定义问题
Created-by: yangshengjun703
Commit-by: y00951989
Merged-by: cann-robot
Description: ## 描述
解决A2A3环境下EsTranspose和aclrtGetArgsFromExceptionInfo符号未定义问题
## 关联Issue
关联的Issue [https://gitcode.com/cann/ops-transformer/issues/2334](url)
## 测试
完成线上A5 A2 A3 rdv 及 本地问题单复现场景A2A3的用例测试,精度通过
# Ascend C 算子代码检视报告
## 1. 检视概要
**检视文件**:C:\y00951989\5077.diff(171行)
**检视模式**:PR 检视(C++安全检视)
**检视范围**:
- **文件1**:mc2/common/utils/mc2_exception_dump.h - 异常处理动态库加载改造
- **文件2**:mc2/matmul_allto_all/op_graph/fusion_pass/matmul_all_to_all_transpose_a5_fusion_pass.cpp - fusion pass 动态库加载
**核心变更**:
- 使用 dlopen/dlsym 动态加载 API 函数,替代直接调用
- 目的:解决符号依赖问题,提升版本兼容性
**问题统计**:
- **CRITICAL级别**:2个(空指针解引用、资源泄露)
- **MEDIUM级别**:2个(外部输入校验、LOG安全)
- **LOW级别**:0个
**检视结论**:该PR引入动态库加载机制解决版本兼容性问题,但存在**2个CRITICAL级别安全风险**,必须在合并前修复。
---
## 2. 问题详情(按严重级别排序)
### CRITICAL-1:空指针解引用风险(cpp-secure 3.5)
**严重级别**:CRITICAL(高)
**假设检验过程**:
| 步骤 | 假设与证据 | 自信值 |
|------|-----------|-------|
| **H0** | func 指针在使用前已判空 | 0% |
| **证据1** | 行152:static EsTransposeFunc func = GetEsTransposeFunc(); 从 GetEsTransposeFunc() 获取指针 | +40% |
| **证据2** | GetEsTransposeFunc() 可能返回 nullptr(dlopen 或 dlsym 失败时,第139、145行) | +30% |
| **证据3** | TransposeDL 函数中第154行直接调用 func(...),未进行判空检查 | +30% |
| **H1** | **func 指针可能为 nullptr,直接调用会导致程序崩溃** | **100%** |
**关联条款**:cpp-secure 3.5(指针使用前判空)、TOPK-1(必须校验函数返回值)
**代码路径**:mc2/matmul_allto_all/op_graph/fusion_pass/matmul_all_to_all_transpose_a5_fusion_pass.cpp(diff行152-154)
**问题类型**:内存安全 - 空指针解引用
**问题代码片段**:
```cpp
// 行150-157(TransposeDL 函数)
ge::es::EsTensorHolder TransposeDL(const ge::es::EsTensorLike& x, const ge::es::EsTensorLike& perm)
{
static EsTransposeFunc func = GetEsTransposeFunc(); // ❌ 可能返回 nullptr
auto* builder = ge::es::ResolveBuilder(x, perm);
auto result = func(x.ToTensorHolder(builder).GetCTensorHolder(), // ❌ 直接调用,未判空
perm.ToTensorHolder(builder).GetCTensorHolder());
return result;
}
```
**修改建议**:
```cpp
// ✅ 正确:添加判空检查
ge::es::EsTensorHolder TransposeDL(const ge::es::EsTensorLike& x, const ge::es::EsTensorLike& perm)
{
static EsTransposeFunc func = GetEsTransposeFunc();
if (func == nullptr) {
OPS_LOG_E("MatmulAllToAllTransposeA5FusionPass", "EsTranspose function not loaded");
return ge::es::EsTensorHolder(); // 返回空对象或抛出异常
}
auto* builder = ge::es::ResolveBuilder(x, perm);
auto result = func(x.ToTensorHolder(builder).GetCTensorHolder(),
perm.ToTensorHolder(builder).GetCTensorHolder());
return result;
}
```
---
### CRITICAL-2:资源泄露风险(cpp-secure 5.2)
**严重级别**:CRITICAL(高)
**假设检验过程**:
| 步骤 | 假设与证据 | 自信值 |
|------|-----------|-------|
| **H0** | dlopen handle 资源正确释放 | 0% |
| **证据1** | 行136:dlopen("libes_math.so", RTLD_LAZY | RTLD_GLOBAL) 使用 RTLD_LAZY | RTLD_GLOBAL 标志 | +40% |
| **证据2** | RTLD_LAZY | RTLD_GLOBAL 标志会增加动态库的引用计数 | +30% |
| **证据3** | 整个函数作用域内无 dlclose 调用(第136-148行) | +30% |
| **证据4** | handle 为局部变量,函数返回后丢失,无法后续释放 | +20% |
| **H1** | **dlopen 增加了引用计数,但未配对 dlclose,导致资源泄露** | **100%** |
**关联条款**:cpp-secure 5.2(资源申请和释放必须匹配)
**代码路径**:mc2/matmul_allto_all/op_graph/fusion_pass/matmul_all_to_all_transpose_a5_fusion_pass.cpp(diff行136-148)
**问题类型**:资源泄露 - dlopen未配对dlclose
**问题分析**:
- RTLD_LAZY | RTLD_GLOBAL 标志会加载库并增加引用计数
- 函数返回后 handle 局部变量丢失,无法后续调用 dlclose
- 如果库被多次加载(虽然 static func 缓存了结果,但每次检查都会执行 dlopen),会导致库无法被正常卸载
**修改建议**:
**方案1:使用 RTLD_NOLOAD,仅查询不增加引用计数**(推荐)
```cpp
// ✅ 正确:使用 RTLD_NOLOAD,不增加引用计数
EsTransposeFunc GetEsTransposeFunc()
{
void* handle = dlopen("libes_math.so", RTLD_NOLOAD | RTLD_LAZY); // 仅查询
if (!handle) {
OPS_LOG_E("MatmulAllToAllTransposeA5FusionPass", "dlopen failed: %s", dlerror());
return nullptr;
}
dlerror();
auto func = reinterpret_cast<EsTransposeFunc>(dlsym(handle, "EsTranspose"));
if (dlerror() != nullptr) {
OPS_LOG_E("MatmulAllToAllTransposeA5FusionPass", "dlsym EsTranspose failed");
return nullptr;
}
return func; // RTLD_NOLOAD 不增加引用计数,无需 dlclose
}
```
---
### MEDIUM-1:外部输入参数未判空(cpp-secure 4.1)
**严重级别**:MEDIUM(中)
**假设检验过程**:
| 步骤 | 假设与证据 | 自信值 |
|------|-----------|-------|
| **H0** | args 参数正确校验 | 0% |
| **证据1** | args 参数作为 aclrtExceptionInfo 指针,属于外部输入(来自异常回调) | +40% |
| **证据2** | diff 第81行直接使用 args 参数调用函数,未显示判空检查 | +30% |
| **证据3** | 虽然理论上异常回调不应传入 nullptr,但规范要求对外部输入必须校验 | +20% |
| **H1** | **args 参数作为外部输入,建议添加判空检查** | **70%** |
**关联条款**:cpp-secure 4.1(外部输入数据合法性校验)
**代码路径**:mc2/common/utils/mc2_exception_dump.h(diff行81)
**问题类型**:外部输入校验 - 参数未判空
**问题代码片段**:
```cpp
// 行81(Mc2ExceptionImpl 函数)
auto ret = aclrtGetArgsFromExceptionInfoFunc(args, &devArgsPtr, &devArgsLen); // args 未判空
```
**修改建议**:
```cpp
// ✅ 正确:添加参数校验
inline void Mc2ExceptionImpl(aclrtExceptionInfo *args, void *userdata, const char *op)
{
// 添加外部输入校验
if (args == nullptr) {
OP_LOGE(OP_NAME, "args is nullptr, invalid exception info");
return;
}
// Get addr of hccl context from ExceptionInfo
void* devArgsPtr = nullptr;
uint32_t devArgsLen = 0;
...
}
```
---
### MEDIUM-2:LOG API 潜在空指针风险(cpp-secure 11.1)
**严重级别**:MEDIUM(中)
**假设检验过程**:
| 步骤 | 假设与证据 | 自信值 |
|------|-----------|-------|
| **H0** | dlerror() 返回值作为 LOG 参数安全 | 0% |
| **证据1** | dlerror() 函数在无错误时返回 nullptr | +40% |
| **证据2** | 代码中直接将 dlerror() 作为 %s 参数传入 OP_LOGE(第34、37、55、60行) | +30% |
| **证据3** | 虽然在错误分支调用理论上应返回错误字符串,但不同平台行为可能有差异 | +20% |
| **H1** | **dlerror() 可能返回 nullptr,作为 %s 参数可能导致段错误** | **50%** |
**结论**:自信值 **50%** < 60%,无法推翻原假设H0,但存在**需关注**风险点。
**关联条款**:cpp-secure 11.1(LOG API 禁止传入空指针作为字符串参数)
**代码路径**:mc2/common/utils/mc2_exception_dump.h(diff行34、37、55、60)
**问题类型**:LOG安全 - 潜在空指针风险
**问题代码片段**:
```cpp
// 行34、37、55、60
OP_LOGE(OP_NAME, "dlsym aclrtGetArgsFromExceptionInfo failed: %s", dlerror()); // ❌ dlerror() 可能为 nullptr
OP_LOGE(OP_NAME, "dlopen failed: %s", dlerror()); // ❌ dlerror() 可能为 nullptr
```
**修改建议**:
```cpp
// ✅ 正确:判空或使用安全格式
// 方案1:判空
const char* errMsg = dlerror();
OP_LOGE(OP_NAME, "dlopen failed: %s", errMsg ? errMsg : "unknown error");
// 方案2:使用安全格式
OP_LOGE(OP_NAME, "dlopen failed, error code: %p", dlerror()); // 打印指针地址
```
---
## 3. 通过条款
以下条款经检视后确认代码符合规范:
### TOPK-13:禁止 thread_local 变量
- ✅ **通过**:代码中使用 static 变量,未使用 thread_local 关键字
### TOPK-1:必须校验函数返回值
- ✅ **通过**:dlopen、dlsym、aclrtGetArgsFromExceptionInfo、acldumpGetPath 等关键函数均有返回值校验
### cpp-secure 3.1:禁止使用未初始化的变量
- ✅ **通过**:所有 static 变量均显式初始化(func = nullptr、initialized = false)
### cpp-secure 11.3:LOG API 参数类型必须与格式化说明符匹配
- ✅ **通过**:%s 对应 dlerror() 返回值(char*),%d 对应 aclError 类型(整型),类型匹配
### mc2_exception_dump.h 中的 RTLD_NOLOAD 使用
- ✅ **合理**:GetAclrtGetArgsFromExceptionInfoFunc() 和 GetAcldumpGetPathFunc() 使用 RTLD_NOLOAD | RTLD_LAZY,仅查询库是否已加载,不增加引用计数,无需 dlclose
---
## 4. 整体评价
### 优点总结:
1. **架构设计合理**:使用动态加载机制解决版本兼容性问题,避免硬编码符号依赖
2. **函数指针管理规范**:使用 static 变量缓存函数指针,避免重复加载开销
3. **错误处理完善**:dlopen/dlsym 失败时记录日志,提供错误信息
4. **返回值校验完整**:关键函数调用均有返回值校验和错误处理
5. **RTLD_NOLOAD 使用正确**:mc2_exception_dump.h 中使用 RTLD_NOLOAD 避免增加引用计数
### 需改进项总结:
**必须修改(CRITICAL)**:
1. ✅ TransposeDL 函数添加 func 指针判空检查(防止空指针解引用崩溃)
2. ✅ GetEsTransposeFunc() 修改为 RTLD_NOLOAD 或添加 dlclose(防止资源泄露)
**建议修改(MEDIUM)**:
1. ✅ Mc2ExceptionImpl 函数添加 args 参数判空检查(外部输入校验)
2. ✅ LOG 宏中的 dlerror() 返回值判空或使用安全格式(防止段错误)
### 综合评分:
| 维度 | 评分 | 说明 |
|------|------|------|
| **安全编码** | 60/100 | 2个CRITICAL安全问题需立即修复 |
| **API使用** | 75/100 | dlopen/dlsym 使用基本规范,但存在资源泄露风险 |
| **性能优化** | 90/100 | static 缓存设计合理,避免重复加载 |
| **代码规范** | 85/100 | 注释清晰,格式规范 |
| **版本兼容性** | 95/100 | 动态加载机制解决符号依赖问题 |
**总分**:**81/100**
---
## 5. 结论与建议
### 检视结论:
该PR引入动态库加载机制(dlopen/dlsym)解决版本兼容性问题,架构设计合理。但存在**2个CRITICAL级别的安全风险**(空指针解引用、资源泄露),必须在合并前修复。
### 修改要求:
**必须修复(阻塞合并)**:
1. ✅ **TransposeDL 函数添加判空检查**(cpp-secure 3.5)
- 添加 if (func == nullptr) 检查,防止空指针解引用崩溃
- 返回空对象或抛出异常作为错误处理
2. ✅ **GetEsTransposeFunc() 资源泄露修复**(cpp-secure 5.2)
- **方案1(推荐)**:改用 RTLD_NOLOAD | RTLD_LAZY,仅查询不增加引用计数
- **方案2**:缓存 handle 并添加清理函数,确保引用计数匹配
**建议修复(推荐)**:
1. ✅ Mc2ExceptionImpl 函数添加 args 参数校验(cpp-secure 4.1)
2. ✅ LOG 宏中 dlerror() 返回值判空(cpp-secure 11.1)
### 建议操作:
1. **立即修复 CRITICAL 问题**:优先修复空指针解引用和资源泄露问题
2. **补充单元测试**:测试 dlopen/dlsym 失败场景,验证错误处理逻辑
3. **添加文档说明**:在 README 或代码注释中说明动态加载机制的使用场景和注意事项
4. **代码 Review**:邀请团队成员 Review 修复后的代码
5. **性能测试**:验证动态加载对性能的影响(虽然 static 缓存可避免重复加载)
---
**报告生成时间**:2026-05-08
**检视人**:AI Code Reviewer (ascendc-ops-reviewer)
**检视模式**:PR检视(C++安全检视)
**规范版本**:cpp-secure.md、ascendc-topk.md
**检视状态**:已完成,发现2个
See merge request: cann/ops-transformer!5077 | 21 天前 |