Line data Source code
1 : /**
2 : * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 : #include <elf.h>
11 : #include <functional>
12 : #include <fstream>
13 : #include <mutex>
14 : #include <memory>
15 : #include <string>
16 : #include <stack>
17 : #include <thread>
18 : #include <dirent.h>
19 : #include <sys/stat.h>
20 : #include "path.h"
21 : #include "file.h"
22 : #include "sys_utils.h"
23 : #include "str_utils.h"
24 : #include "log/adx_log.h"
25 : #include "log/hdc_log.h"
26 : #include "extra_config.h"
27 : #include "thread_manager.h"
28 : #include "adump_dsmi.h"
29 : #include "runtime/base.h"
30 : #include "exception_info_common.h"
31 : #include "kernel_symbol_locator.h"
32 : #include "kernel_info_collector.h"
33 :
34 : namespace Adx {
35 : namespace {
36 : const std::set<char> INVALID_FILE_NAME_CHAR = {' ', '.', '/', '\\'};
37 : constexpr uint64_t JSON_SUFFIX_LEN = 5; // length of ".json"
38 : } // namespace
39 :
40 32 : int32_t StartCollectKernelAsync(std::shared_ptr<KernelInfoCollector> collector, const std::string &dumpPath) {
41 32 : int32_t ret = collector->LoadKernelBinBuffer();
42 : // 先同步落 _host.o(纯内存 buffer 写,快),再把慢的 kernel_meta 搜索丢到异步线程。
43 32 : (void)collector->DumpHostKernelBin(dumpPath);
44 32 : IDE_LOGD("Start collecting kernel file.");
45 64 : std::thread collectKernel([collector, dumpPath](){
46 32 : int32_t tid = mmGetTid();
47 32 : ThreadManager::Instance().TaskAdd(tid);
48 32 : (void)collector->StartCollectKernel(dumpPath);
49 32 : ThreadManager::Instance().TaskDone(tid);
50 32 : });
51 32 : collectKernel.detach();
52 32 : return ret;
53 32 : }
54 :
55 131 : void KernelInfoCollector::LoadKernelInfo(const rtExceptionArgsInfo &argsInfo)
56 : {
57 131 : kernelBinHandle_ = nullptr;
58 131 : kernelBinData_.clear();
59 131 : kernelBinSize_ = 0;
60 131 : kernelName_.clear();
61 :
62 131 : kernelBinHandle_ = argsInfo.exceptionKernelInfo.bin; // binHandle
63 131 : kernelBinSize_ = argsInfo.exceptionKernelInfo.binSize;
64 131 : if ((argsInfo.exceptionKernelInfo.kernelName != nullptr) && (argsInfo.exceptionKernelInfo.kernelNameSize != 0)) {
65 68 : kernelName_ = std::string(argsInfo.exceptionKernelInfo.kernelName, argsInfo.exceptionKernelInfo.kernelNameSize);
66 : }
67 131 : IDE_LOGI("Kernel handle: %p, kernel size: %u, name addr: %p, name size: %u, kernel name: %s", kernelBinHandle_,
68 : kernelBinSize_, argsInfo.exceptionKernelInfo.kernelName, argsInfo.exceptionKernelInfo.kernelNameSize,
69 : kernelName_.c_str());
70 131 : }
71 :
72 5 : int32_t KernelInfoCollector::InitFromBinHandle(rtBinHandle BinHandle, const std::string &kernelName)
73 : {
74 5 : kernelBinHandle_ = BinHandle;
75 5 : kernelName_ = kernelName;
76 :
77 5 : return LoadKernelBinBuffer();
78 : }
79 :
80 87 : int32_t KernelInfoCollector::LoadKernelBinBuffer()
81 : {
82 87 : if (kernelBinHandle_ == nullptr) {
83 50 : return ADUMP_SUCCESS;
84 : }
85 37 : std::string binData;
86 37 : uint32_t binSize = 0;
87 37 : int32_t ret = ExceptionInfoCommon::GetBinDataFromHandle(kernelBinHandle_, binData, binSize);
88 37 : if (ret != ADUMP_SUCCESS) {
89 2 : kernelBinData_ = "";
90 2 : return ADUMP_FAILED;
91 : }
92 35 : kernelBinSize_ = binSize;
93 35 : kernelBinData_ = binData;
94 35 : return ADUMP_SUCCESS;
95 37 : }
96 :
97 65 : std::string KernelInfoCollector::GetProcessedKernelName() const
98 : {
99 65 : return ExceptionInfoCommon::GetKernelNameWithoutMixSuffix(kernelName_);
100 : }
101 :
102 35 : int32_t KernelInfoCollector::StartCollectKernel(const std::string &dumpPath) const
103 : {
104 35 : if (kernelName_.empty() || kernelBinHandle_ == nullptr || kernelBinSize_ == 0) {
105 12 : IDE_LOGI("Kernel name or kernel bin is empty, skip dump kernel.");
106 12 : return ADUMP_SUCCESS;
107 : }
108 :
109 23 : std::string kernelName = GetProcessedKernelName();
110 :
111 : // _host.o 已由调用方通过 DumpHostKernelBin 单独同步落盘,这里只做慢的 kernel_meta 搜索拷贝。
112 23 : int32_t ret = ADUMP_SUCCESS;
113 23 : if (CollectKernelFile(kernelName, dumpPath) != ADUMP_SUCCESS) {
114 6 : IDE_LOGE("Collect kernel file failed.");
115 6 : ret = ADUMP_FAILED;
116 : }
117 23 : return ret;
118 23 : }
119 :
120 138791 : bool KernelInfoCollector::ContainsString(const std::string &filePath, const std::string &targetString) const
121 : {
122 138791 : Path jsonFilePath(filePath);
123 138955 : if (!jsonFilePath.RealPath()) {
124 0 : IDE_LOGD("The json file path[%s] is invalid, please check the path.", jsonFilePath.GetCString());
125 0 : return false;
126 : }
127 :
128 138654 : std::ifstream file(jsonFilePath.GetString());
129 138956 : if (!file.is_open()) {
130 10663 : IDE_LOGD("The json file path[%s] open failed, please check the permission.", jsonFilePath.GetCString());
131 10663 : return false;
132 : }
133 128078 : std::string line;
134 110005752 : while (std::getline(file, line)) {
135 110184784 : if (line.find(targetString) == std::string::npos) {
136 110205585 : continue;
137 : }
138 0 : if (IsTargetLine(line, "kernelName", targetString)) {
139 10 : file.close();
140 10 : return true;
141 : }
142 : }
143 122126 : file.close();
144 128227 : return false;
145 138900 : }
146 :
147 16 : bool KernelInfoCollector::IsTargetLine(const std::string ¤tLine, const std::string &key,
148 : const std::string &value) const
149 : {
150 16 : size_t curPlace = 0;
151 16 : if (GetFirstItem(currentLine, curPlace) != key) {
152 6 : return false;
153 : }
154 10 : if (GetFirstItem(currentLine, curPlace) != value) {
155 0 : return false;
156 : }
157 10 : return true;
158 : }
159 :
160 26 : std::string KernelInfoCollector::GetFirstItem(const std::string &curLine, size_t &curPlace) const
161 : {
162 26 : size_t head = curLine.find_first_of('"', curPlace);
163 26 : if (head == std::string::npos) {
164 0 : return "";
165 : }
166 26 : size_t end = curLine.find_first_of('"', head + 1);
167 26 : if (end == std::string::npos) {
168 0 : return "";
169 : }
170 26 : curPlace = end + 1;
171 26 : return StrUtils::Trim(curLine.substr(head + 1, end - head - 1));
172 : }
173 :
174 85 : std::string KernelInfoCollector::SearchJsonFiles(const std::string &rootPath, const std::string &targetString) const
175 : {
176 85 : std::stack<std::string> directories;
177 85 : directories.push(rootPath);
178 :
179 47109 : while (!directories.empty()) {
180 46952 : std::string currentDir = directories.top();
181 46971 : directories.pop();
182 :
183 46931 : DIR *dir = opendir(currentDir.c_str());
184 47036 : if (!dir) {
185 28 : IDE_LOGW("Unable to open directory[%s].", currentDir.c_str());
186 28 : continue;
187 : }
188 :
189 : struct dirent *entry;
190 619857 : while ((entry = readdir(dir)) != nullptr) {
191 572252 : std::string name = entry->d_name;
192 1046046 : if (name == "." || name == ".." ||
193 476275 : ((currentDir == "./") && (name.find("kernel_meta") == std::string::npos))) {
194 93741 : continue;
195 : }
196 :
197 474517 : std::string fullPath = currentDir + "/" + name;
198 : struct stat fileStat;
199 477835 : if (stat(fullPath.c_str(), &fileStat) == -1) {
200 0 : IDE_LOGD("Unable to stat file[%s].", fullPath.c_str());
201 0 : continue;
202 : }
203 :
204 479180 : if (S_ISDIR(fileStat.st_mode)) {
205 46992 : directories.push(fullPath);
206 46979 : continue;
207 : }
208 863122 : if (!S_ISREG(fileStat.st_mode) || name.size() <= JSON_SUFFIX_LEN ||
209 863181 : name.substr(name.size() - JSON_SUFFIX_LEN) != ".json") {
210 293121 : continue;
211 : }
212 :
213 : // If the file is a JSON file, check whether the file contains the target character string.
214 138995 : if (ContainsString(fullPath, targetString)) {
215 10 : (void)closedir(dir);
216 10 : return fullPath;
217 : }
218 1051367 : }
219 :
220 47617 : (void)closedir(dir);
221 47441 : }
222 :
223 77 : IDE_LOGI("Can not find kernel file in %s, targetString: %s", rootPath.c_str(), targetString.c_str());
224 150 : return "";
225 90 : }
226 :
227 : /**
228 : * @brief : dump the kernel bin file runtime load from host
229 : * @param [in] : dumpPath path of the exception dump file
230 : * @return : ADUMP_SUCCESS succeed; ADUMP_FAILED failed
231 : * @note : 从 StartCollectKernel 拆出,供调用方在慢搜索/修正 PC 之前单独同步落 _host.o。
232 : */
233 88 : int32_t KernelInfoCollector::DumpHostKernelBin(const std::string &dumpPath) const
234 : {
235 : // 与拆分前 StartCollectKernel 入口保持一致的前置校验:kernelName 为空会退化成非唯一的 "_host.o",
236 : // 不同异常/算子会互相覆盖并污染幂等判断,故此处直接跳过。
237 88 : if (kernelName_.empty() || kernelBinHandle_ == nullptr || kernelBinSize_ == 0) {
238 52 : IDE_LOGI("Kernel name or kernel bin is empty, skip dump host kernel bin.");
239 52 : return ADUMP_SUCCESS;
240 : }
241 36 : if (kernelBinData_.empty()) {
242 2 : IDE_LOGE("Kernel bin data is empty.");
243 2 : return ADUMP_FAILED;
244 : }
245 34 : std::string kernelName = GetProcessedKernelName();
246 34 : Path hostKernelBinPath(dumpPath);
247 34 : std::string hostKernelBinName = StrUtils::Replace(kernelName, INVALID_FILE_NAME_CHAR, '_') + "_host.o";
248 34 : hostKernelBinPath.Concat(hostKernelBinName);
249 : // 幂等:_host.o 可能已由调用方在符号化前提前同步落盘。仅当磁盘上文件大小与待写入一致时才跳过,
250 : // 避免上次部分写(如磁盘满)残留的截断/空文件被 Exist() 误判为已完成而永久跳过重写。
251 : struct stat hostBinStat;
252 50 : if (hostKernelBinPath.Exist() && stat(hostKernelBinPath.GetCString(), &hostBinStat) == 0 &&
253 16 : static_cast<uint64_t>(hostBinStat.st_size) == static_cast<uint64_t>(kernelBinSize_)) {
254 15 : IDE_LOGI("[Dump][Exception] host kernel bin already dumped, skip. file: %s", hostKernelBinPath.GetCString());
255 15 : return ADUMP_SUCCESS;
256 : }
257 19 : File hostKernelBinFile(hostKernelBinPath.GetString(), M_WRONLY | M_CREAT | M_TRUNC);
258 19 : int32_t ret = hostKernelBinFile.IsFileOpen();
259 19 : if (ret != ADUMP_SUCCESS) {
260 0 : IDE_LOGE("Open host kernel bin file[%s] failed.", hostKernelBinPath.GetCString());
261 0 : return ADUMP_FAILED;
262 : }
263 : static std::mutex KernelBinLock;
264 19 : std::lock_guard<std::mutex> lock(KernelBinLock);
265 19 : auto writeSize = hostKernelBinFile.Write(kernelBinData_.c_str(), static_cast<int64_t>(kernelBinSize_));
266 19 : if (writeSize < 0) {
267 : // 不清理残留文件(与拆分前行为一致):M_TRUNC 已把文件截为 0,失败后盘上会留下空/截断的 _host.o。
268 : // 下次调用因大小不匹配会重写,不会误判为完整;但符号化阶段若在此期间读取会拿到残缺文件,故打印告警提醒。
269 1 : IDE_LOGE("Write host kernel bin file[%s] failed, an incomplete _host.o may remain on disk.",
270 : hostKernelBinPath.GetCString());
271 1 : return ADUMP_FAILED;
272 : }
273 : // File::Write 内部循环处理短写、返回的是最后一次 mmWrite 的长度而非累计值,不能用它判完整性;
274 : // 改为写后 stat 校验最终文件大小,截断/短写(如磁盘满)时文件大小不足即判失败,避免残留截断文件被幂等误判为完整。
275 34 : if (stat(hostKernelBinPath.GetCString(), &hostBinStat) != 0 ||
276 16 : static_cast<uint64_t>(hostBinStat.st_size) != static_cast<uint64_t>(kernelBinSize_)) {
277 : // 同上:残留的截断文件不主动删除,靠下次大小校验重写;此处打印告警提醒盘上存在不完整的 _host.o。
278 3 : IDE_LOGE("Write host kernel bin file[%s] incomplete, fileSize=%lld, expect=%u, "
279 : "an incomplete _host.o may remain on disk.",
280 : hostKernelBinPath.GetCString(), static_cast<long long>(hostBinStat.st_size), kernelBinSize_);
281 3 : return ADUMP_FAILED;
282 : }
283 15 : IDE_LOGE("[Dump][Exception] dump host kernel to file, file: %s", hostKernelBinPath.GetCString());
284 15 : (void)mmChmod(hostKernelBinPath.GetCString(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
285 :
286 15 : return ADUMP_SUCCESS;
287 34 : }
288 :
289 13 : std::vector<std::string> KernelInfoCollector::SplitString(const std::string &str, char delimiter) const
290 : {
291 13 : std::vector<std::string> result;
292 13 : std::stringstream ss(str);
293 13 : std::string item;
294 :
295 39 : while (std::getline(ss, item, delimiter)) {
296 26 : result.push_back(item);
297 : }
298 :
299 13 : return result;
300 13 : }
301 :
302 : /*
303 : 按照指定的路径优先级来进行.o和.json文件搜索
304 : */
305 31 : std::vector<std::string> KernelInfoCollector::GetSearchPath() const
306 : {
307 31 : std::string envStr;
308 31 : std::vector<std::string> searchPath;
309 :
310 31 : ADX_GET_ENV(MM_ENV_ASCEND_CACHE_PATH, envStr);
311 31 : if (!envStr.empty()) {
312 13 : searchPath.emplace_back(envStr);
313 13 : IDE_LOGI("Add ASCEND_CACHE_PATH[%s] to search path.", envStr.c_str());
314 : }
315 :
316 31 : ADX_GET_ENV(MM_ENV_HOME, envStr);
317 31 : if (!envStr.empty()) {
318 26 : envStr += "/atc_data";
319 26 : searchPath.emplace_back(envStr);
320 26 : IDE_LOGI("Add HOME[%s] to search path.", envStr.c_str());
321 : }
322 :
323 : // custom install path
324 31 : ADX_GET_ENV(MM_ENV_ASCEND_CUSTOM_OPP_PATH, envStr);
325 31 : if (!envStr.empty()) {
326 13 : std::vector<std::string> customPaths = SplitString(envStr, ':');
327 39 : for (const auto &customPath : customPaths) {
328 26 : searchPath.emplace_back(customPath);
329 26 : IDE_LOGI("Add ASCEND_CUSTOM_OPP_PATH[%s] to search path.", customPath.c_str());
330 : }
331 13 : }
332 :
333 31 : ADX_GET_ENV(MM_ENV_ASCEND_OPP_PATH, envStr);
334 31 : if (!envStr.empty()) {
335 26 : std::string vendorsPath = envStr + "/vendors";
336 26 : searchPath.emplace_back(vendorsPath);
337 26 : IDE_LOGI("Add ASCEND_OPP_PATH[%s] to search path.", vendorsPath.c_str());
338 :
339 26 : std::string buildInPath = envStr + "/built-in";
340 26 : searchPath.emplace_back(buildInPath);
341 26 : IDE_LOGI("Add ASCEND_OPP_PATH[%s] to search path.", buildInPath.c_str());
342 26 : }
343 :
344 : // kernel meta generate in current path
345 31 : searchPath.emplace_back("./");
346 31 : IDE_LOGI("Add current path[./] to search path.");
347 :
348 31 : ADX_GET_ENV(MM_ENV_ASCEND_WORK_PATH, envStr);
349 31 : if (!envStr.empty()) {
350 11 : envStr += "/kernel_meta";
351 11 : searchPath.emplace_back(envStr);
352 : }
353 31 : IDE_LOGI("Add current path[%s] to search path.", envStr.c_str());
354 :
355 31 : return searchPath;
356 31 : }
357 :
358 23 : int32_t KernelInfoCollector::CollectKernelFile(const std::string &kernelName, const std::string &dumpPath) const
359 : {
360 23 : std::vector<std::string> searchPath = GetSearchPath();
361 :
362 23 : bool failFlag = false;
363 98 : for (const auto &path : searchPath) {
364 82 : std::string jsonFilePath = SearchJsonFiles(path, kernelName);
365 82 : if (jsonFilePath.empty()) {
366 75 : continue;
367 : }
368 :
369 7 : Path jsonPath(jsonFilePath);
370 7 : Path dumpJsonPath(dumpPath);
371 7 : dumpJsonPath.Concat(jsonPath.GetFileName());
372 : static std::mutex KernelfileLock;
373 7 : std::lock_guard<std::mutex> lock(KernelfileLock);
374 7 : if (File::Copy(jsonFilePath, dumpJsonPath.GetString()) != ADUMP_SUCCESS) {
375 0 : IDE_LOGE("Copy kernel file[%s] to dump path[%s] failed.", jsonFilePath.c_str(), dumpJsonPath.GetCString());
376 0 : failFlag = true;
377 : } else {
378 7 : IDE_LOGE("[Dump][Exception] dump kernel json to file, file: %s", dumpJsonPath.GetCString());
379 7 : (void)mmChmod(dumpJsonPath.GetCString(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
380 : }
381 :
382 7 : std::string kernelBinPath = jsonFilePath.substr(0, jsonFilePath.size() - JSON_SUFFIX_LEN) + ".o";
383 7 : Path kernelPath(kernelBinPath);
384 7 : Path dumpKernelPath(dumpPath);
385 7 : dumpKernelPath.Concat(kernelPath.GetFileName());
386 7 : if (File::Copy(kernelBinPath, dumpKernelPath.GetString()) != ADUMP_SUCCESS) {
387 6 : IDE_LOGE("Copy kernel file[%s] to dump path[%s] failed.", kernelBinPath.c_str(),
388 : dumpKernelPath.GetCString());
389 6 : failFlag = true;
390 : } else {
391 1 : IDE_LOGE("[Dump][Exception] dump kernel to file, file: %s", dumpKernelPath.GetCString());
392 1 : (void)mmChmod(dumpKernelPath.GetCString(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
393 : }
394 7 : break;
395 89 : }
396 23 : if (failFlag) {
397 6 : return ADUMP_FAILED;
398 : }
399 :
400 17 : return ADUMP_SUCCESS;
401 23 : }
402 :
403 : } // namespace Adx
|