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 : {
42 32 : int32_t ret = collector->LoadKernelBinBuffer();
43 : // _host.o 已由编排层(DumpHostKernelBinBeforeSymbolize)在符号化前无条件同步落盘,
44 : // 这里不再重复落盘,只把慢的 kernel_meta 搜索拷贝丢到异步线程。
45 32 : IDE_LOGD("Start collecting kernel file.");
46 64 : std::thread collectKernel([collector, dumpPath]() {
47 32 : int32_t tid = mmGetTid();
48 32 : ThreadManager::Instance().TaskAdd(tid);
49 32 : (void)collector->StartCollectKernel(dumpPath);
50 32 : ThreadManager::Instance().TaskDone(tid);
51 32 : });
52 32 : collectKernel.detach();
53 32 : return ret;
54 32 : }
55 :
56 134 : void KernelInfoCollector::LoadKernelInfo(const rtExceptionArgsInfo& argsInfo)
57 : {
58 134 : kernelBinHandle_ = nullptr;
59 134 : kernelBinData_.clear();
60 134 : kernelBinSize_ = 0;
61 134 : kernelName_.clear();
62 :
63 134 : kernelBinHandle_ = argsInfo.exceptionKernelInfo.bin; // binHandle
64 134 : kernelBinSize_ = argsInfo.exceptionKernelInfo.binSize;
65 134 : if ((argsInfo.exceptionKernelInfo.kernelName != nullptr) && (argsInfo.exceptionKernelInfo.kernelNameSize != 0)) {
66 74 : kernelName_ = std::string(argsInfo.exceptionKernelInfo.kernelName, argsInfo.exceptionKernelInfo.kernelNameSize);
67 : }
68 134 : IDE_LOGI(
69 : "Kernel handle: %p, kernel size: %u, name addr: %p, name size: %u, kernel name: %s", kernelBinHandle_,
70 : kernelBinSize_, argsInfo.exceptionKernelInfo.kernelName, argsInfo.exceptionKernelInfo.kernelNameSize,
71 : kernelName_.c_str());
72 134 : }
73 :
74 8 : int32_t KernelInfoCollector::InitFromBinHandle(rtBinHandle BinHandle, const std::string& kernelName)
75 : {
76 8 : kernelBinHandle_ = BinHandle;
77 8 : kernelName_ = kernelName;
78 :
79 8 : return LoadKernelBinBuffer();
80 : }
81 :
82 91 : int32_t KernelInfoCollector::LoadKernelBinBuffer()
83 : {
84 91 : if (kernelBinHandle_ == nullptr) {
85 50 : return ADUMP_SUCCESS;
86 : }
87 41 : std::string binData;
88 41 : uint32_t binSize = 0;
89 41 : int32_t ret = ExceptionInfoCommon::GetBinDataFromHandle(kernelBinHandle_, binData, binSize);
90 41 : if (ret != ADUMP_SUCCESS) {
91 2 : kernelBinData_ = "";
92 2 : return ADUMP_FAILED;
93 : }
94 39 : kernelBinSize_ = binSize;
95 39 : kernelBinData_ = binData;
96 39 : return ADUMP_SUCCESS;
97 41 : }
98 :
99 50 : std::string KernelInfoCollector::GetProcessedKernelName() const
100 : {
101 50 : return ExceptionInfoCommon::GetKernelNameWithoutMixSuffix(kernelName_);
102 : }
103 :
104 35 : int32_t KernelInfoCollector::StartCollectKernel(const std::string& dumpPath) const
105 : {
106 35 : if (kernelName_.empty() || kernelBinHandle_ == nullptr || kernelBinSize_ == 0) {
107 12 : IDE_LOGI("Kernel name or kernel bin is empty, skip dump kernel.");
108 12 : return ADUMP_SUCCESS;
109 : }
110 :
111 23 : std::string kernelName = GetProcessedKernelName();
112 :
113 : // _host.o 已由调用方通过 DumpHostKernelBin 单独同步落盘,这里只做慢的 kernel_meta 搜索拷贝。
114 23 : int32_t ret = ADUMP_SUCCESS;
115 23 : if (CollectKernelFile(kernelName, dumpPath) != ADUMP_SUCCESS) {
116 7 : IDE_LOGE("Collect kernel file failed.");
117 7 : ret = ADUMP_FAILED;
118 : }
119 23 : return ret;
120 23 : }
121 :
122 139373 : bool KernelInfoCollector::ContainsString(const std::string& filePath, const std::string& targetString) const
123 : {
124 139373 : Path jsonFilePath(filePath);
125 139575 : if (!jsonFilePath.RealPath()) {
126 6401 : IDE_LOGD("The json file path[%s] is invalid, please check the path.", jsonFilePath.GetCString());
127 6401 : return false;
128 : }
129 :
130 133056 : std::ifstream file(jsonFilePath.GetString());
131 133393 : if (!file.is_open()) {
132 10724 : IDE_LOGD("The json file path[%s] open failed, please check the permission.", jsonFilePath.GetCString());
133 10724 : return false;
134 : }
135 122669 : std::string line;
136 100509233 : while (std::getline(file, line)) {
137 101742363 : if (line.find(targetString) == std::string::npos) {
138 100178524 : continue;
139 : }
140 197255 : if (IsTargetLine(line, "kernelName", targetString)) {
141 10 : file.close();
142 10 : return true;
143 : }
144 : }
145 94815 : file.close();
146 122600 : return false;
147 139735 : }
148 :
149 16 : bool KernelInfoCollector::IsTargetLine(
150 : const std::string& currentLine, const std::string& key, const std::string& value) const
151 : {
152 16 : size_t curPlace = 0;
153 16 : if (GetFirstItem(currentLine, curPlace) != key) {
154 6 : return false;
155 : }
156 10 : if (GetFirstItem(currentLine, curPlace) != value) {
157 0 : return false;
158 : }
159 10 : return true;
160 : }
161 :
162 26 : std::string KernelInfoCollector::GetFirstItem(const std::string& curLine, size_t& curPlace) const
163 : {
164 26 : size_t head = curLine.find_first_of('"', curPlace);
165 26 : if (head == std::string::npos) {
166 0 : return "";
167 : }
168 26 : size_t end = curLine.find_first_of('"', head + 1);
169 26 : if (end == std::string::npos) {
170 0 : return "";
171 : }
172 26 : curPlace = end + 1;
173 26 : return StrUtils::Trim(curLine.substr(head + 1, end - head - 1));
174 : }
175 :
176 85 : std::string KernelInfoCollector::SearchJsonFiles(const std::string& rootPath, const std::string& targetString) const
177 : {
178 85 : std::stack<std::string> directories;
179 85 : directories.push(rootPath);
180 :
181 50080 : while (!directories.empty()) {
182 49964 : std::string currentDir = directories.top();
183 49959 : directories.pop();
184 :
185 50037 : DIR* dir = opendir(currentDir.c_str());
186 50103 : if (!dir) {
187 28 : IDE_LOGW("Unable to open directory[%s].", currentDir.c_str());
188 28 : continue;
189 : }
190 :
191 : struct dirent* entry;
192 643409 : while ((entry = readdir(dir)) != nullptr) {
193 595442 : std::string name = entry->d_name;
194 1077861 : if (name == "." || name == ".." ||
195 489522 : ((currentDir == "./") && (name.find("kernel_meta") == std::string::npos))) {
196 99429 : continue;
197 : }
198 :
199 486170 : std::string fullPath = currentDir + "/" + name;
200 : struct stat fileStat;
201 484777 : if (stat(fullPath.c_str(), &fileStat) == -1) {
202 1 : IDE_LOGD("Unable to stat file[%s].", fullPath.c_str());
203 1 : continue;
204 : }
205 :
206 495436 : if (S_ISDIR(fileStat.st_mode)) {
207 50014 : directories.push(fullPath);
208 49909 : continue;
209 : }
210 885187 : if (!S_ISREG(fileStat.st_mode) || name.size() <= JSON_SUFFIX_LEN ||
211 884818 : name.substr(name.size() - JSON_SUFFIX_LEN) != ".json") {
212 304167 : continue;
213 : }
214 :
215 : // If the file is a JSON file, check whether the file contains the target character string.
216 139382 : if (ContainsString(fullPath, targetString)) {
217 10 : (void)closedir(dir);
218 10 : return fullPath;
219 : }
220 1086963 : }
221 :
222 50189 : (void)closedir(dir);
223 50010 : }
224 :
225 77 : IDE_LOGI("Can not find kernel file in %s, targetString: %s", rootPath.c_str(), targetString.c_str());
226 150 : return "";
227 84 : }
228 :
229 19 : std::string KernelInfoCollector::GetHostOFilePath(const std::string& dumpPath) const
230 : {
231 19 : std::string kernelName = GetProcessedKernelName();
232 19 : Path hostKernelBinPath(dumpPath);
233 19 : std::string hostKernelBinName = StrUtils::Replace(kernelName, INVALID_FILE_NAME_CHAR, '_') + "_host.o";
234 19 : hostKernelBinPath.Concat(hostKernelBinName);
235 38 : return hostKernelBinPath.GetString();
236 19 : }
237 :
238 : /**
239 : * @brief : dump the kernel bin file runtime load from host
240 : * @param [in] : dumpPath path of the exception dump file
241 : * @param [out] : outHostOPath 绝对/拼接后的 _host.o 落盘路径,供后续 symbolize 使用
242 : * @return : ADUMP_SUCCESS succeed; ADUMP_FAILED failed
243 : * @note : 从 StartCollectKernel 拆出,供调用方在慢搜索/修正 PC 之前单独同步落 _host.o。
244 : */
245 57 : int32_t KernelInfoCollector::DumpHostKernelBin(const std::string& dumpPath, std::string& outHostOPath) const
246 : {
247 57 : outHostOPath.clear();
248 : // 与拆分前 StartCollectKernel 入口保持一致的前置校验:kernelName 为空会退化成非唯一的 "_host.o",
249 : // 不同异常/算子会互相覆盖并污染幂等判断,故此处直接跳过。
250 57 : if (kernelName_.empty() || kernelBinHandle_ == nullptr || kernelBinSize_ == 0) {
251 42 : IDE_LOGI("Kernel name or kernel bin is empty, skip dump host kernel bin.");
252 42 : return ADUMP_SUCCESS;
253 : }
254 15 : if (kernelBinData_.empty()) {
255 0 : IDE_LOGE("Kernel bin data is empty.");
256 0 : return ADUMP_FAILED;
257 : }
258 15 : std::string hostKernelBinPath = GetHostOFilePath(dumpPath);
259 : // 幂等:_host.o 可能已由调用方在符号化前提前同步落盘。仅当磁盘上文件大小与待写入一致时才跳过,
260 : // 避免上次部分写(如磁盘满)残留的截断/空文件被 Exist() 误判为已完成而永久跳过重写。
261 : struct stat hostBinStat;
262 20 : if (stat(hostKernelBinPath.c_str(), &hostBinStat) == 0 &&
263 5 : static_cast<uint64_t>(hostBinStat.st_size) == static_cast<uint64_t>(kernelBinSize_)) {
264 4 : IDE_LOGI("[Dump][Exception] host kernel bin already dumped, skip. file: %s", hostKernelBinPath.c_str());
265 4 : outHostOPath = hostKernelBinPath;
266 4 : return ADUMP_SUCCESS;
267 : }
268 11 : File hostKernelBinFile(hostKernelBinPath, M_WRONLY | M_CREAT | M_TRUNC);
269 11 : int32_t ret = hostKernelBinFile.IsFileOpen();
270 11 : if (ret != ADUMP_SUCCESS) {
271 0 : IDE_LOGE("Open host kernel bin file[%s] failed.", hostKernelBinPath.c_str());
272 0 : return ADUMP_FAILED;
273 : }
274 : static std::mutex KernelBinLock;
275 11 : std::lock_guard<std::mutex> lock(KernelBinLock);
276 11 : auto writeSize = hostKernelBinFile.Write(kernelBinData_.c_str(), static_cast<int64_t>(kernelBinSize_));
277 11 : if (writeSize < 0) {
278 : // 不清理残留文件(与拆分前行为一致):M_TRUNC 已把文件截为 0,失败后盘上会留下空/截断的 _host.o。
279 : // 下次调用因大小不匹配会重写,不会误判为完整;但符号化阶段若在此期间读取会拿到残缺文件,故打印告警提醒。
280 0 : IDE_LOGE(
281 : "Write host kernel bin file[%s] failed, an incomplete _host.o may remain on disk.",
282 : hostKernelBinPath.c_str());
283 0 : return ADUMP_FAILED;
284 : }
285 : // File::Write 内部循环处理短写、返回的是最后一次 mmWrite 的长度而非累计值,不能用它判完整性;
286 : // 改为写后 stat 校验最终文件大小,截断/短写(如磁盘满)时文件大小不足即判失败,避免残留截断文件被幂等误判为完整。
287 22 : if (stat(hostKernelBinPath.c_str(), &hostBinStat) != 0 ||
288 11 : static_cast<uint64_t>(hostBinStat.st_size) != static_cast<uint64_t>(kernelBinSize_)) {
289 : // 同上:残留的截断文件不主动删除,靠下次大小校验重写;此处打印告警提醒盘上存在不完整的 _host.o。
290 1 : IDE_LOGE(
291 : "Write host kernel bin file[%s] incomplete, fileSize=%lld, expect=%u, "
292 : "an incomplete _host.o may remain on disk.",
293 : hostKernelBinPath.c_str(), static_cast<long long>(hostBinStat.st_size), kernelBinSize_);
294 1 : return ADUMP_FAILED;
295 : }
296 10 : IDE_LOGE("[Dump][Exception] dump host kernel to file, file: %s", hostKernelBinPath.c_str());
297 10 : (void)mmChmod(hostKernelBinPath.c_str(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
298 10 : outHostOPath = hostKernelBinPath;
299 :
300 10 : return ADUMP_SUCCESS;
301 15 : }
302 :
303 13 : std::vector<std::string> KernelInfoCollector::SplitString(const std::string& str, char delimiter) const
304 : {
305 13 : std::vector<std::string> result;
306 13 : std::stringstream ss(str);
307 13 : std::string item;
308 :
309 39 : while (std::getline(ss, item, delimiter)) {
310 26 : result.push_back(item);
311 : }
312 :
313 13 : return result;
314 13 : }
315 :
316 : /*
317 : 按照指定的路径优先级来进行.o和.json文件搜索
318 : */
319 31 : std::vector<std::string> KernelInfoCollector::GetSearchPath() const
320 : {
321 31 : std::string envStr;
322 31 : std::vector<std::string> searchPath;
323 :
324 31 : ADX_GET_ENV(MM_ENV_ASCEND_CACHE_PATH, envStr);
325 31 : if (!envStr.empty()) {
326 13 : searchPath.emplace_back(envStr);
327 13 : IDE_LOGI("Add ASCEND_CACHE_PATH[%s] to search path.", envStr.c_str());
328 : }
329 :
330 31 : ADX_GET_ENV(MM_ENV_HOME, envStr);
331 31 : if (!envStr.empty()) {
332 26 : envStr += "/atc_data";
333 26 : searchPath.emplace_back(envStr);
334 26 : IDE_LOGI("Add HOME[%s] to search path.", envStr.c_str());
335 : }
336 :
337 : // custom install path
338 31 : ADX_GET_ENV(MM_ENV_ASCEND_CUSTOM_OPP_PATH, envStr);
339 31 : if (!envStr.empty()) {
340 13 : std::vector<std::string> customPaths = SplitString(envStr, ':');
341 52 : for (const auto& customPath : customPaths) {
342 26 : searchPath.emplace_back(customPath);
343 26 : IDE_LOGI("Add ASCEND_CUSTOM_OPP_PATH[%s] to search path.", customPath.c_str());
344 : }
345 13 : }
346 :
347 31 : ADX_GET_ENV(MM_ENV_ASCEND_OPP_PATH, envStr);
348 31 : if (!envStr.empty()) {
349 26 : std::string vendorsPath = envStr + "/vendors";
350 26 : searchPath.emplace_back(vendorsPath);
351 26 : IDE_LOGI("Add ASCEND_OPP_PATH[%s] to search path.", vendorsPath.c_str());
352 :
353 26 : std::string buildInPath = envStr + "/built-in";
354 26 : searchPath.emplace_back(buildInPath);
355 26 : IDE_LOGI("Add ASCEND_OPP_PATH[%s] to search path.", buildInPath.c_str());
356 26 : }
357 :
358 : // kernel meta generate in current path
359 31 : searchPath.emplace_back("./");
360 31 : IDE_LOGI("Add current path[./] to search path.");
361 :
362 31 : ADX_GET_ENV(MM_ENV_ASCEND_WORK_PATH, envStr);
363 31 : if (!envStr.empty()) {
364 11 : envStr += "/kernel_meta";
365 11 : searchPath.emplace_back(envStr);
366 : }
367 31 : IDE_LOGI("Add current path[%s] to search path.", envStr.c_str());
368 :
369 31 : return searchPath;
370 31 : }
371 :
372 23 : int32_t KernelInfoCollector::CollectKernelFile(const std::string& kernelName, const std::string& dumpPath) const
373 : {
374 23 : std::vector<std::string> searchPath = GetSearchPath();
375 :
376 23 : bool failFlag = false;
377 121 : for (const auto& path : searchPath) {
378 82 : std::string jsonFilePath = SearchJsonFiles(path, kernelName);
379 82 : if (jsonFilePath.empty()) {
380 75 : continue;
381 : }
382 :
383 7 : Path jsonPath(jsonFilePath);
384 7 : Path dumpJsonPath(dumpPath);
385 7 : dumpJsonPath.Concat(jsonPath.GetFileName());
386 : static std::mutex KernelfileLock;
387 7 : std::lock_guard<std::mutex> lock(KernelfileLock);
388 7 : if (File::Copy(jsonFilePath, dumpJsonPath.GetString()) != ADUMP_SUCCESS) {
389 3 : IDE_LOGE("Copy kernel file[%s] to dump path[%s] failed.", jsonFilePath.c_str(), dumpJsonPath.GetCString());
390 3 : failFlag = true;
391 : } else {
392 4 : IDE_LOGE("[Dump][Exception] dump kernel json to file, file: %s", dumpJsonPath.GetCString());
393 4 : (void)mmChmod(dumpJsonPath.GetCString(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
394 : }
395 :
396 7 : std::string kernelBinPath = jsonFilePath.substr(0, jsonFilePath.size() - JSON_SUFFIX_LEN) + ".o";
397 7 : Path kernelPath(kernelBinPath);
398 7 : Path dumpKernelPath(dumpPath);
399 7 : dumpKernelPath.Concat(kernelPath.GetFileName());
400 7 : if (File::Copy(kernelBinPath, dumpKernelPath.GetString()) != ADUMP_SUCCESS) {
401 7 : IDE_LOGE(
402 : "Copy kernel file[%s] to dump path[%s] failed.", kernelBinPath.c_str(), dumpKernelPath.GetCString());
403 7 : failFlag = true;
404 : } else {
405 0 : IDE_LOGE("[Dump][Exception] dump kernel to file, file: %s", dumpKernelPath.GetCString());
406 0 : (void)mmChmod(dumpKernelPath.GetCString(), M_IRUSR); // 安全要求,落盘文件置为最小权限:用户只读, 400
407 : }
408 7 : break;
409 89 : }
410 23 : if (failFlag) {
411 7 : return ADUMP_FAILED;
412 : }
413 :
414 16 : return ADUMP_SUCCESS;
415 23 : }
416 :
417 : } // namespace Adx
|