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 "kernel_source_symbolizer.h"
11 :
12 : #include <cerrno>
13 : #include <csignal>
14 : #include <cstdint>
15 : #include <cstdio>
16 : #include <cstdlib>
17 : #include <cstring>
18 : #include <ctime>
19 : #include <elf.h>
20 : #include <fcntl.h>
21 : #include <mutex>
22 : #include <poll.h>
23 : #include <spawn.h>
24 : #include <sstream>
25 : #include <sys/wait.h>
26 : #include <unistd.h>
27 : #include <vector>
28 :
29 : #include "securec.h"
30 : #include "mmpa_api.h"
31 : #include "path.h"
32 : #include "lib_path.h"
33 : #include "sys_utils.h"
34 : #include "log/adx_log.h"
35 : #include "log/hdc_log.h"
36 :
37 : // environ 由 <unistd.h> 声明(posix_spawn 需要当前进程环境变量表),无需再以 extern 方式引用外部变量。
38 :
39 : namespace Adx {
40 : namespace {
41 : // 异常现场,宁可快速降级也不阻塞落盘:单次子进程解析超时 3 秒。
42 : constexpr int64_t SYMBOLIZER_TIMEOUT_MS = 3000;
43 : // 超时回收:SIGTERM 后给子进程的自行退出宽限期,到期再 SIGKILL。
44 : constexpr int64_t SYMBOLIZER_TERM_GRACE_MS = 200;
45 : // 宽限期内轮询 waitpid(WNOHANG) 的睡眠间隔(10ms)。
46 : constexpr int64_t SYMBOLIZER_TERM_POLL_NS = 10 * 1000 * 1000;
47 : constexpr size_t READ_BUF_SIZE = 4096;
48 : constexpr char ENV_SYMBOLIZER[] = "ADUMP_LLVM_SYMBOLIZER";
49 : // CANN 安装路径 + 架构目录下 llvm-symbolizer 的相对路径,如 <install>/x86_64-linux/bin/llvm-symbolizer。
50 : constexpr char CANN_SYMBOLIZER_REL[] = "/bin/llvm-symbolizer";
51 : // 系统默认安装位置回退。
52 : constexpr char SYSTEM_SYMBOLIZER[] = "/usr/bin/llvm-symbolizer";
53 : constexpr char UNKNOWN_MARK[] = "??";
54 :
55 34 : int64_t NowMs()
56 : {
57 34 : struct timespec ts {};
58 34 : (void)clock_gettime(CLOCK_MONOTONIC, &ts);
59 34 : return static_cast<int64_t>(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
60 : }
61 :
62 : // 校验路径存在且可执行。
63 13 : bool IsExecutable(const std::string& path)
64 : {
65 13 : if (path.empty()) {
66 0 : return false;
67 : }
68 13 : return access(path.c_str(), X_OK) == 0;
69 : }
70 :
71 9 : std::string GetEnvValue(const char* name)
72 : {
73 : // mmGetEnv 内部对环境变量表的读取做了加锁保护,避免直接调用 getenv 的竞争条件。
74 9 : char value[MMPA_MAX_PATH] = {0};
75 9 : if (mmGetEnv(name, value, sizeof(value)) != EN_OK) {
76 2 : return std::string();
77 : }
78 7 : return SysUtils::HandleEnv(value);
79 : }
80 :
81 : // 校验 ELF64 头合法性并定位 section 名字符串表;成功时回填 strTab/strTabSize。
82 : // 以减法/除法比较,避免 e_shoff + e_shnum * sizeof(Shdr) 等加法回绕绕过越界校验。
83 4 : bool LocateElfStrTab(const char* data, size_t size, Elf64_Ehdr& ehdr, const char*& strTab, size_t& strTabSize)
84 : {
85 4 : if (memcpy_s(&ehdr, sizeof(ehdr), data, sizeof(ehdr)) != EOK) {
86 0 : return false;
87 : }
88 4 : if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
89 0 : return false;
90 : }
91 4 : if (ehdr.e_shentsize != sizeof(Elf64_Shdr) || ehdr.e_shnum == 0 || ehdr.e_shstrndx >= ehdr.e_shnum) {
92 0 : return false;
93 : }
94 4 : if (ehdr.e_shoff == 0 || ehdr.e_shoff >= size ||
95 4 : (size - ehdr.e_shoff) / sizeof(Elf64_Shdr) < static_cast<uint64_t>(ehdr.e_shnum)) {
96 0 : return false;
97 : }
98 4 : Elf64_Shdr strShdr{};
99 4 : const size_t strShdrOff =
100 4 : static_cast<size_t>(ehdr.e_shoff) + static_cast<size_t>(ehdr.e_shstrndx) * sizeof(Elf64_Shdr);
101 4 : if (memcpy_s(&strShdr, sizeof(strShdr), data + strShdrOff, sizeof(strShdr)) != EOK) {
102 0 : return false;
103 : }
104 4 : if (strShdr.sh_offset >= size || strShdr.sh_size == 0 || strShdr.sh_size > size - strShdr.sh_offset) {
105 0 : return false;
106 : }
107 4 : strTab = data + strShdr.sh_offset;
108 4 : strTabSize = static_cast<size_t>(strShdr.sh_size);
109 4 : return true;
110 : }
111 :
112 : // 从 ELF section header 表中查找名为 target 的段。
113 4 : bool ElfHasSection(const char* data, size_t size, const std::string& target)
114 : {
115 4 : if (data == nullptr || size < sizeof(Elf64_Ehdr)) {
116 0 : return false;
117 : }
118 4 : Elf64_Ehdr ehdr{};
119 4 : const char* strTab = nullptr;
120 4 : size_t strTabSize = 0;
121 4 : if (!LocateElfStrTab(data, size, ehdr, strTab, strTabSize)) {
122 0 : return false;
123 : }
124 12 : for (uint16_t i = 0; i < ehdr.e_shnum; ++i) {
125 10 : Elf64_Shdr shdr{};
126 10 : const size_t off = static_cast<size_t>(ehdr.e_shoff) + static_cast<size_t>(i) * sizeof(Elf64_Shdr);
127 10 : if (memcpy_s(&shdr, sizeof(shdr), data + off, sizeof(shdr)) != EOK) {
128 2 : return false;
129 : }
130 10 : if (shdr.sh_name >= strTabSize) {
131 0 : continue;
132 : }
133 10 : const char* nameStart = strTab + shdr.sh_name;
134 10 : if (memchr(nameStart, '\0', strTabSize - shdr.sh_name) == nullptr) {
135 0 : continue;
136 : }
137 10 : if (target == nameStart) {
138 2 : return true;
139 : }
140 : }
141 2 : return false;
142 : }
143 : } // namespace
144 :
145 : namespace {
146 : // 缓存首次解析结果。异常回调可能来自多设备/多线程,用互斥量保护解析与缓存读写,
147 : // 避免 g_toolResolved 判断与 g_cachedTool 写入之间的数据竞争。
148 : std::string g_cachedTool;
149 : bool g_toolResolved = false;
150 : std::mutex g_locateMutex;
151 :
152 : // 实际执行工具解析,返回定位到的路径(未找到则空)。调用方需持有 g_locateMutex。
153 9 : std::string ResolveSymbolizerPath()
154 : {
155 : // 1. 环境变量指定的绝对路径优先。
156 9 : const std::string envPath = GetEnvValue(ENV_SYMBOLIZER);
157 9 : if (!envPath.empty()) {
158 7 : if (IsExecutable(envPath)) {
159 6 : IDE_LOGI("Locate llvm-symbolizer from env %s: %s", ENV_SYMBOLIZER, envPath.c_str());
160 6 : return envPath;
161 : }
162 1 : IDE_LOGW("Env %s is set but not executable: %s", ENV_SYMBOLIZER, envPath.c_str());
163 : }
164 :
165 : // 2. CANN 安装路径 + 架构目录:<install>/<arch>/bin/llvm-symbolizer。
166 : // LibPath 经 dladdr 定位自身 .so,其父目录即 <install>/<arch>(如 <install>/x86_64-linux)。
167 3 : const std::string archPath = LibPath::Instance().GetInstallParentPath().GetString();
168 3 : if (!archPath.empty()) {
169 3 : const std::string candidate = archPath + CANN_SYMBOLIZER_REL;
170 3 : if (IsExecutable(candidate)) {
171 0 : IDE_LOGI("Locate llvm-symbolizer from CANN install path: %s", candidate.c_str());
172 0 : return candidate;
173 : }
174 3 : IDE_LOGD("llvm-symbolizer not found under CANN install path: %s", candidate.c_str());
175 3 : } else {
176 0 : IDE_LOGD("Cannot resolve CANN install path, skip CANN candidate for llvm-symbolizer.");
177 : }
178 :
179 : // 3. 系统默认位置回退:/usr/bin/llvm-symbolizer。
180 6 : if (IsExecutable(SYSTEM_SYMBOLIZER)) {
181 0 : IDE_LOGI("Locate llvm-symbolizer from system path: %s", SYSTEM_SYMBOLIZER);
182 0 : return SYSTEM_SYMBOLIZER;
183 : }
184 3 : IDE_LOGD("llvm-symbolizer not found under system path: %s", SYSTEM_SYMBOLIZER);
185 :
186 3 : IDE_LOGW(
187 : "llvm-symbolizer not found, skip source location. "
188 : "Set env %s or install it under CANN <arch>/bin or /usr/bin to enable it.",
189 : ENV_SYMBOLIZER);
190 3 : return std::string();
191 9 : }
192 :
193 : // 安全解析十进制无符号数:校验 endptr 与 errno,非法/越界返回 0(源码信息仅用于日志展示)。
194 12 : uint32_t ParseDecU32(const std::string& text)
195 : {
196 12 : if (text.empty()) {
197 0 : return 0;
198 : }
199 12 : errno = 0;
200 12 : char* endptr = nullptr;
201 12 : const unsigned long value = strtoul(text.c_str(), &endptr, 10);
202 12 : if (endptr == text.c_str() || *endptr != '\0' || errno == ERANGE || value > UINT32_MAX) {
203 0 : return 0;
204 : }
205 12 : return static_cast<uint32_t>(value);
206 : }
207 :
208 : // 子进程句柄:pid 与父侧管道 fd(inFd 写子 stdin,outFd 读子 stdout)。
209 : struct SymbolizerProc {
210 : pid_t pid = -1;
211 : int inFd = -1;
212 : int outFd = -1;
213 : };
214 :
215 : // 创建 stdin/stdout 管道并 posix_spawn 拉起 llvm-symbolizer;成功时回填 proc 的父侧 fd 与 pid。
216 : // posix_spawn 内部走 vfork 快路径,以声明式 file_actions 完成重定向,规避 fork-to-exec 的 async-signal 风险。
217 5 : bool SpawnSymbolizer(const std::string& tool, SymbolizerProc& proc)
218 : {
219 5 : int inPipe[2] = {-1, -1};
220 5 : int outPipe[2] = {-1, -1};
221 : // 管道创建失败依赖系统资源耗尽,无法在 UT 中稳定注入;保留清理逻辑但不计入覆盖率。
222 : // LCOV_EXCL_START
223 : if (pipe(inPipe) != 0 || pipe(outPipe) != 0) {
224 : IDE_LOGW("Symbolize: create pipe failed, errno=%d.", errno);
225 : if (inPipe[0] >= 0) {
226 : (void)close(inPipe[0]);
227 : (void)close(inPipe[1]);
228 : }
229 : return false;
230 : }
231 : // LCOV_EXCL_STOP
232 : posix_spawn_file_actions_t actions;
233 : // file_actions 初始化失败同样由系统资源状态决定,无法稳定注入。
234 : // LCOV_EXCL_START
235 : if (posix_spawn_file_actions_init(&actions) != 0) {
236 : IDE_LOGW("Symbolize: init spawn file actions failed, errno=%d.", errno);
237 : (void)close(inPipe[0]);
238 : (void)close(inPipe[1]);
239 : (void)close(outPipe[0]);
240 : (void)close(outPipe[1]);
241 : return false;
242 : }
243 : // LCOV_EXCL_STOP
244 5 : (void)posix_spawn_file_actions_adddup2(&actions, inPipe[0], STDIN_FILENO);
245 5 : (void)posix_spawn_file_actions_adddup2(&actions, outPipe[1], STDOUT_FILENO);
246 5 : (void)posix_spawn_file_actions_addclose(&actions, inPipe[0]);
247 5 : (void)posix_spawn_file_actions_addclose(&actions, inPipe[1]);
248 5 : (void)posix_spawn_file_actions_addclose(&actions, outPipe[0]);
249 5 : (void)posix_spawn_file_actions_addclose(&actions, outPipe[1]);
250 :
251 : // 无 shell、无附加参数:目标文件随每行 stdin 以 "文件" 地址 形式给出,文件名与地址均来自受控数据。
252 : // 不依赖 -f/-C/-i 约束输出格式,解析端按空行分块、只取块内位置行(file:line:col),忽略函数名与内联多帧。
253 5 : char argExe[] = "llvm-symbolizer";
254 5 : char* const argv[] = {argExe, nullptr};
255 :
256 5 : pid_t pid = -1;
257 5 : int spawnRet = posix_spawn(&pid, tool.c_str(), &actions, nullptr, argv, environ);
258 5 : (void)posix_spawn_file_actions_destroy(&actions);
259 : // 拉起工具失败取决于执行环境,测试中无法跨平台稳定注入;清理路径不计入覆盖率。
260 : // LCOV_EXCL_START
261 : if (spawnRet != 0) {
262 : IDE_LOGW("Symbolize: posix_spawn failed, ret=%d, tool=%s.", spawnRet, tool.c_str());
263 : (void)close(inPipe[0]);
264 : (void)close(inPipe[1]);
265 : (void)close(outPipe[0]);
266 : (void)close(outPipe[1]);
267 : return false;
268 : }
269 : // LCOV_EXCL_STOP
270 : // 父进程关闭子进程侧管道端,仅保留自身读写端。
271 5 : (void)close(inPipe[0]);
272 5 : (void)close(outPipe[1]);
273 5 : proc.pid = pid;
274 5 : proc.inFd = inPipe[1];
275 5 : proc.outFd = outPipe[0];
276 5 : return true;
277 : }
278 :
279 : // 读一次 stdout:追加到 output,EOF 置 outEof;遇不可恢复错误返回 false。
280 16 : bool DrainReadable(int fd, std::string& output, bool& outEof)
281 : {
282 : char buf[READ_BUF_SIZE];
283 16 : ssize_t r = read(fd, buf, sizeof(buf));
284 16 : if (r > 0) {
285 12 : output.append(buf, static_cast<size_t>(r));
286 4 : } else if (r == 0) {
287 4 : outEof = true;
288 0 : } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
289 0 : return false;
290 : }
291 16 : return true;
292 : }
293 :
294 : // 增量写 stdin:写完或出错即关闭写端并置 inClosed、inFd=-1。
295 5 : void PumpWritable(int& inFd, const std::string& input, size_t& written, bool& inClosed)
296 : {
297 5 : ssize_t w = write(inFd, input.data() + written, input.size() - written);
298 5 : if (w > 0) {
299 5 : written += static_cast<size_t>(w);
300 0 : } else if (w < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
301 0 : inClosed = true;
302 0 : (void)close(inFd);
303 0 : inFd = -1;
304 0 : return;
305 : }
306 5 : if (!inClosed && written >= input.size()) {
307 5 : inClosed = true;
308 5 : (void)close(inFd);
309 5 : inFd = -1;
310 : }
311 : }
312 :
313 : // 组装本轮 poll 的 pollfd 数组:outFd 恒在 [0] 收 POLLIN;stdin 未写完时把 inFd 加为 [1] 收 POLLOUT。
314 : // 返回待 poll 的 fd 数量,并回填 outIdx / inIdx(inIdx=-1 表示本轮不再关注 stdin)。
315 22 : nfds_t BuildPollFds(const SymbolizerProc& proc, bool inClosed, struct pollfd fds[2], int& outIdx, int& inIdx)
316 : {
317 22 : nfds_t nfds = 0;
318 22 : outIdx = static_cast<int>(nfds);
319 22 : fds[nfds].fd = proc.outFd;
320 22 : fds[nfds].events = POLLIN;
321 22 : fds[nfds].revents = 0;
322 22 : ++nfds;
323 22 : inIdx = -1;
324 22 : if (!inClosed) {
325 5 : inIdx = static_cast<int>(nfds);
326 5 : fds[nfds].fd = proc.inFd;
327 5 : fds[nfds].events = POLLOUT;
328 5 : fds[nfds].revents = 0;
329 5 : ++nfds;
330 : }
331 22 : return nfds;
332 : }
333 :
334 : // 同一 poll 循环并发驱动 stdin 写与 stdout 读,避免"先写满 stdin 再读 stdout"的父子互相背压死锁。
335 : // 两端置非阻塞并统一挂在 deadline 下;超时返回 false。返回后 proc.inFd 已关闭。
336 5 : bool PumpSymbolizerIo(SymbolizerProc& proc, const std::string& input, std::string& output)
337 : {
338 5 : (void)fcntl(proc.inFd, F_SETFL, O_NONBLOCK);
339 5 : (void)fcntl(proc.outFd, F_SETFL, O_NONBLOCK);
340 5 : size_t written = 0;
341 5 : bool inClosed = false;
342 5 : bool outEof = false;
343 5 : bool timedOut = false;
344 5 : const int64_t deadline = NowMs() + SYMBOLIZER_TIMEOUT_MS;
345 26 : while (!outEof) {
346 22 : const int64_t remain = deadline - NowMs();
347 22 : if (remain <= 0) {
348 0 : timedOut = true;
349 1 : break;
350 : }
351 : struct pollfd fds[2];
352 22 : int outIdx = -1;
353 22 : int inIdx = -1;
354 22 : const nfds_t nfds = BuildPollFds(proc, inClosed, fds, outIdx, inIdx);
355 22 : int pr = poll(fds, nfds, static_cast<int>(remain));
356 22 : if (pr < 0) {
357 0 : if (errno == EINTR) {
358 0 : continue;
359 : }
360 0 : break;
361 : }
362 22 : if (pr == 0) {
363 1 : timedOut = true;
364 1 : break;
365 : }
366 : // 优先读 stdout,避免子进程被 stdout 管道背压阻塞。
367 21 : if ((fds[outIdx].revents & (POLLIN | POLLHUP | POLLERR)) != 0 && !DrainReadable(proc.outFd, output, outEof)) {
368 0 : break;
369 : }
370 21 : if (inIdx >= 0 && (fds[inIdx].revents & (POLLOUT | POLLHUP | POLLERR)) != 0) {
371 5 : PumpWritable(proc.inFd, input, written, inClosed);
372 : }
373 : }
374 5 : if (!inClosed && proc.inFd >= 0) {
375 0 : (void)close(proc.inFd);
376 0 : proc.inFd = -1;
377 : }
378 5 : return !timedOut;
379 : }
380 :
381 : // 进程级忽略 SIGPIPE:子进程异常早退关闭 stdin 读端时,父进程 write 默认动作是被 SIGPIPE 终止,
382 : // 忽略后 write 改为返回 EPIPE,从而走 best-effort 降级而非杀死宿主进程(fujun19 检视点)。
383 : // 只需设置一次;用 call_once 保证幂等,且不覆盖用户可能已有的 SIGPIPE 处理时保持 SIG_IGN 语义。
384 5 : void IgnoreSigPipeOnce()
385 : {
386 : static std::once_flag onceFlag;
387 5 : std::call_once(onceFlag, []() {
388 1 : struct sigaction sa {};
389 1 : sa.sa_handler = SIG_IGN;
390 1 : (void)sigemptyset(&sa.sa_mask);
391 1 : sa.sa_flags = 0;
392 1 : (void)sigaction(SIGPIPE, &sa, nullptr);
393 1 : });
394 5 : }
395 :
396 : // 在宽限期内以 WNOHANG 轮询回收子进程。已回收(或不可回收 ECHILD)返回 true;
397 : // 到期仍在运行返回 false,交由调用方 SIGKILL 兜底。
398 5 : bool WaitChildExit(pid_t pid, int64_t graceMs)
399 : {
400 5 : const int64_t graceDeadline = NowMs() + graceMs;
401 : do {
402 7 : const pid_t r = waitpid(pid, nullptr, WNOHANG);
403 7 : if (r == pid || (r < 0 && errno != EINTR)) {
404 5 : return true;
405 : }
406 2 : struct timespec ts {
407 : 0, SYMBOLIZER_TERM_POLL_NS
408 : };
409 2 : (void)nanosleep(&ts, nullptr);
410 2 : } while (NowMs() < graceDeadline);
411 0 : return false;
412 : }
413 :
414 : // 统一回收子进程,遵循 G.STD.17-CPP「先通知、限时等待、再强制终止」的顺序,并把回收纳入 deadline
415 : // 避免无界阻塞的 waitpid(zhangpengpeng8 检视点):先发 SIGTERM 通知子进程自行退出(无论是否超时,
416 : // 正常路径下子进程收到 stdin EOF 本应自退,此处 SIGTERM 仅为兜底通知);随后在宽限期内 WNOHANG 轮询
417 : // 回收;到期仍未退出,说明子进程已挂死不响应优雅通知,再 SIGKILL 强制终止并阻塞回收(SIGKILL 后
418 : // 子进程必然很快退出,不会僵尸/久等)。
419 5 : void ReapChild(pid_t pid)
420 : {
421 : // 先礼:通知目标子进程停止,给足宽限期等待其自行退出。
422 5 : (void)kill(pid, SIGTERM);
423 : // 后兵:仅当宽限期内等待超时(子进程仍未退出)时才强制终止并回收;
424 : // 否则子进程已在宽限期内自行退出并被 WaitChildExit 回收,正常返回。
425 5 : if (!WaitChildExit(pid, SYMBOLIZER_TERM_GRACE_MS)) {
426 0 : (void)kill(pid, SIGKILL);
427 0 : (void)waitpid(pid, nullptr, 0);
428 : }
429 5 : }
430 :
431 : // 判断子串 [begin, end) 是否非空且全为十进制数字。
432 13 : bool IsAllDigits(const std::string& s, size_t begin, size_t end)
433 : {
434 13 : if (begin >= end) {
435 1 : return false;
436 : }
437 29 : for (size_t i = begin; i < end; ++i) {
438 17 : if (s[i] < '0' || s[i] > '9') {
439 0 : return false;
440 : }
441 : }
442 12 : return true;
443 : }
444 :
445 : // 判断是否为位置行:形如 file:line:col,即最后两个冒号分隔的字段均为数字。
446 : // 用于把位置行与函数名行区分开——未加 -C 时函数名默认仍会 demangle,可能含 '::'(如 ns::foo(int)),
447 : // 仅凭"含冒号"无法区分,故要求结尾严格为 :<数字>:<数字>。llvm-symbolizer 未知位置标记 ??:0:0 亦满足。
448 12 : bool IsLocationLine(const std::string& line, size_t& colPos, size_t& linePos)
449 : {
450 12 : colPos = line.rfind(':');
451 12 : if (colPos == std::string::npos || colPos == 0) {
452 5 : return false;
453 : }
454 7 : linePos = line.rfind(':', colPos - 1);
455 7 : if (linePos == std::string::npos) {
456 0 : return false;
457 : }
458 7 : return IsAllDigits(line, linePos + 1, colPos) && IsAllDigits(line, colPos + 1, line.size());
459 : }
460 :
461 : // 从一行 file:line:col 文本填充 res 的源码位置;从右侧解析 line 与 column,兼容路径含冒号。
462 6 : void FillLocation(const std::string& line, size_t colPos, size_t linePos, SymbolizeResult& res)
463 : {
464 6 : res.srcFile = line.substr(0, linePos);
465 6 : res.srcLine = ParseDecU32(line.substr(linePos + 1, colPos - linePos - 1));
466 6 : res.srcColumn = ParseDecU32(line.substr(colPos + 1));
467 6 : res.ok = (res.srcFile != UNKNOWN_MARK) && !res.srcFile.empty();
468 6 : }
469 :
470 : // 解析 llvm-symbolizer 默认输出:不依赖固定行数,按空行把输出切成块,第 i 块对应第 i 个偏移。
471 : // 块内可能混有函数名行与(内联展开的)多组位置行;只取块内第一条位置行(file:line:col),
472 : // 即最内层帧的源码位置,函数名行与外层内联帧一律忽略。
473 4 : void ParseSymbolizerOutput(const std::string& output, std::vector<SymbolizeResult>& results)
474 : {
475 4 : std::istringstream iss(output);
476 4 : std::string line;
477 4 : size_t idx = 0;
478 4 : bool blockHasLoc = false; // 当前块是否已取到位置行
479 4 : bool blockStarted = false; // 当前块是否已出现任何非空行
480 24 : while (idx < results.size() && std::getline(iss, line)) {
481 20 : if (line.empty()) {
482 : // 空行 = 块边界:已开始的块结束,推进到下一个偏移。
483 6 : if (blockStarted) {
484 6 : ++idx;
485 6 : blockHasLoc = false;
486 6 : blockStarted = false;
487 : }
488 6 : continue;
489 : }
490 14 : blockStarted = true;
491 14 : size_t colPos = 0;
492 14 : size_t linePos = 0;
493 : // 每块只认第一条位置行,后续内联外层帧与函数名行忽略。
494 14 : if (!blockHasLoc && IsLocationLine(line, colPos, linePos)) {
495 6 : FillLocation(line, colPos, linePos, results[idx]);
496 6 : blockHasLoc = true;
497 : }
498 : }
499 4 : }
500 : } // namespace
501 :
502 : #ifdef __ADUMP_LLT
503 24 : void KernelSourceSymbolizer::ResetLocateCacheForTest()
504 : {
505 24 : std::lock_guard<std::mutex> lock(g_locateMutex);
506 24 : g_cachedTool.clear();
507 24 : g_toolResolved = false;
508 24 : }
509 : #endif
510 :
511 9 : const std::string& KernelSourceSymbolizer::LocateSymbolizer()
512 : {
513 9 : std::lock_guard<std::mutex> lock(g_locateMutex);
514 9 : if (g_toolResolved) {
515 0 : return g_cachedTool;
516 : }
517 9 : g_cachedTool = ResolveSymbolizerPath();
518 9 : g_toolResolved = true;
519 9 : return g_cachedTool;
520 9 : }
521 :
522 2 : bool KernelSourceSymbolizer::IsAvailable() { return !LocateSymbolizer().empty(); }
523 :
524 9 : bool KernelSourceSymbolizer::HasDebugLine(const std::string& oFilePath)
525 : {
526 9 : Path path(oFilePath);
527 9 : if (!path.RealPath()) {
528 5 : IDE_LOGD("HasDebugLine: invalid path %s.", oFilePath.c_str());
529 5 : return false;
530 : }
531 4 : FILE* fp = fopen(path.GetCString(), "rb");
532 4 : if (fp == nullptr) {
533 0 : IDE_LOGD("HasDebugLine: open failed %s.", path.GetCString());
534 0 : return false;
535 : }
536 4 : std::string buf;
537 : char tmp[READ_BUF_SIZE];
538 4 : size_t n = 0;
539 8 : while ((n = fread(tmp, 1, sizeof(tmp), fp)) > 0) {
540 4 : buf.append(tmp, n);
541 : }
542 4 : (void)fclose(fp);
543 4 : return ElfHasSection(buf.data(), buf.size(), ".debug_line");
544 9 : }
545 :
546 8 : bool KernelSourceSymbolizer::Symbolize(
547 : const std::string& oFilePath, const std::vector<uint64_t>& offsets, std::vector<SymbolizeResult>& results)
548 : {
549 8 : results.clear();
550 8 : results.resize(offsets.size());
551 8 : if (offsets.empty()) {
552 1 : return false;
553 : }
554 7 : const std::string& tool = LocateSymbolizer();
555 7 : if (tool.empty()) {
556 1 : return false;
557 : }
558 : // 单 .o 一次校验:无效则整体失败,results 保持占位(ok=false)。
559 6 : Path path(oFilePath);
560 6 : if (oFilePath.empty() || !path.RealPath()) {
561 1 : IDE_LOGW("Symbolize: invalid .o path, skip. path=%s.", oFilePath.c_str());
562 1 : return false;
563 : }
564 :
565 : // 该 .o 的所有偏移按序写入 stdin:文件名加引号以容忍路径中的空格;地址十六进制。
566 5 : std::ostringstream oss;
567 18 : for (uint64_t off : offsets) {
568 8 : oss << "\"" << path.GetString() << "\" 0x" << std::hex << off << "\n";
569 : }
570 5 : return RunSymbolizer(tool, oss.str(), results);
571 6 : }
572 :
573 5 : bool KernelSourceSymbolizer::RunSymbolizer(
574 : const std::string& tool, const std::string& inputLines, std::vector<SymbolizeResult>& results)
575 : {
576 : // 写 stdin 前先忽略 SIGPIPE,子进程早退关闭读端时 write 返回 EPIPE 走降级,而非终止宿主进程。
577 5 : IgnoreSigPipeOnce();
578 5 : SymbolizerProc proc;
579 5 : if (!SpawnSymbolizer(tool, proc)) {
580 0 : return false;
581 : }
582 :
583 5 : std::string output;
584 5 : const bool ok = PumpSymbolizerIo(proc, inputLines, output);
585 5 : (void)close(proc.outFd);
586 5 : proc.outFd = -1;
587 :
588 5 : if (!ok) {
589 1 : IDE_LOGW(
590 : "Symbolize: llvm-symbolizer timed out after %ldms, terminate child pid=%d.", SYMBOLIZER_TIMEOUT_MS,
591 : proc.pid);
592 : }
593 : // 回收纳入宽限 deadline:先 SIGTERM 通知、限时等待、必要时 SIGKILL 兜底,
594 : // 即便子进程关闭 stdout 后仍挂住,也不会在此无界阻塞。
595 5 : ReapChild(proc.pid);
596 5 : if (!ok) {
597 1 : return false;
598 : }
599 :
600 : // 原样打印 llvm-symbolizer 的完整原始输出,便于现场直接查看未经加工的解析结果。
601 4 : if (!output.empty()) {
602 3 : IDE_LOGE("[Dump][Exception][Symbolize] llvm-symbolizer raw output:\n%s", output.c_str());
603 : }
604 4 : ParseSymbolizerOutput(output, results);
605 4 : return true;
606 5 : }
607 :
608 : } // namespace Adx
|