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