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 32 : int64_t NowMs()
56 : {
57 32 : struct timespec ts{};
58 32 : (void)clock_gettime(CLOCK_MONOTONIC, &ts);
59 32 : 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 = static_cast<size_t>(ehdr.e_shoff) +
100 4 : 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("llvm-symbolizer not found, skip source location. "
187 : "Set env %s or install it under CANN <arch>/bin or /usr/bin to enable it.", ENV_SYMBOLIZER);
188 3 : return std::string();
189 9 : }
190 :
191 : // 安全解析十进制无符号数:校验 endptr 与 errno,非法/越界返回 0(源码信息仅用于日志展示)。
192 12 : uint32_t ParseDecU32(const std::string &text)
193 : {
194 12 : if (text.empty()) {
195 0 : return 0;
196 : }
197 12 : errno = 0;
198 12 : char *endptr = nullptr;
199 12 : const unsigned long value = strtoul(text.c_str(), &endptr, 10);
200 12 : if (endptr == text.c_str() || *endptr != '\0' || errno == ERANGE || value > UINT32_MAX) {
201 0 : return 0;
202 : }
203 12 : return static_cast<uint32_t>(value);
204 : }
205 :
206 : // 子进程句柄:pid 与父侧管道 fd(inFd 写子 stdin,outFd 读子 stdout)。
207 : struct SymbolizerProc {
208 : pid_t pid = -1;
209 : int inFd = -1;
210 : int outFd = -1;
211 : };
212 :
213 : // 创建 stdin/stdout 管道并 posix_spawn 拉起 llvm-symbolizer;成功时回填 proc 的父侧 fd 与 pid。
214 : // posix_spawn 内部走 vfork 快路径,以声明式 file_actions 完成重定向,规避 fork-to-exec 的 async-signal 风险。
215 5 : bool SpawnSymbolizer(const std::string &tool, SymbolizerProc &proc)
216 : {
217 5 : int inPipe[2] = {-1, -1};
218 5 : int outPipe[2] = {-1, -1};
219 5 : if (pipe(inPipe) != 0 || pipe(outPipe) != 0) {
220 0 : IDE_LOGW("Symbolize: create pipe failed, errno=%d.", errno);
221 0 : if (inPipe[0] >= 0) { (void)close(inPipe[0]); (void)close(inPipe[1]); }
222 0 : return false;
223 : }
224 : posix_spawn_file_actions_t actions;
225 5 : if (posix_spawn_file_actions_init(&actions) != 0) {
226 0 : IDE_LOGW("Symbolize: init spawn file actions failed, errno=%d.", errno);
227 0 : (void)close(inPipe[0]); (void)close(inPipe[1]);
228 0 : (void)close(outPipe[0]); (void)close(outPipe[1]);
229 0 : return false;
230 : }
231 5 : (void)posix_spawn_file_actions_adddup2(&actions, inPipe[0], STDIN_FILENO);
232 5 : (void)posix_spawn_file_actions_adddup2(&actions, outPipe[1], STDOUT_FILENO);
233 5 : (void)posix_spawn_file_actions_addclose(&actions, inPipe[0]);
234 5 : (void)posix_spawn_file_actions_addclose(&actions, inPipe[1]);
235 5 : (void)posix_spawn_file_actions_addclose(&actions, outPipe[0]);
236 5 : (void)posix_spawn_file_actions_addclose(&actions, outPipe[1]);
237 :
238 : // 无 shell、无附加参数:目标文件随每行 stdin 以 "文件" 地址 形式给出,文件名与地址均来自受控数据。
239 : // 不依赖 -f/-C/-i 约束输出格式,解析端按空行分块、只取块内位置行(file:line:col),忽略函数名与内联多帧。
240 5 : char argExe[] = "llvm-symbolizer";
241 5 : char *const argv[] = {argExe, nullptr};
242 :
243 5 : pid_t pid = -1;
244 5 : int spawnRet = posix_spawn(&pid, tool.c_str(), &actions, nullptr, argv, environ);
245 5 : (void)posix_spawn_file_actions_destroy(&actions);
246 5 : if (spawnRet != 0) {
247 0 : IDE_LOGW("Symbolize: posix_spawn failed, ret=%d, tool=%s.", spawnRet, tool.c_str());
248 0 : (void)close(inPipe[0]); (void)close(inPipe[1]);
249 0 : (void)close(outPipe[0]); (void)close(outPipe[1]);
250 0 : return false;
251 : }
252 : // 父进程关闭子进程侧管道端,仅保留自身读写端。
253 5 : (void)close(inPipe[0]);
254 5 : (void)close(outPipe[1]);
255 5 : proc.pid = pid;
256 5 : proc.inFd = inPipe[1];
257 5 : proc.outFd = outPipe[0];
258 5 : return true;
259 : }
260 :
261 : // 读一次 stdout:追加到 output,EOF 置 outEof;遇不可恢复错误返回 false。
262 14 : bool DrainReadable(int fd, std::string &output, bool &outEof)
263 : {
264 : char buf[READ_BUF_SIZE];
265 14 : ssize_t r = read(fd, buf, sizeof(buf));
266 14 : if (r > 0) {
267 10 : output.append(buf, static_cast<size_t>(r));
268 4 : } else if (r == 0) {
269 4 : outEof = true;
270 0 : } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
271 0 : return false;
272 : }
273 14 : return true;
274 : }
275 :
276 : // 增量写 stdin:写完或出错即关闭写端并置 inClosed、inFd=-1。
277 5 : void PumpWritable(int &inFd, const std::string &input, size_t &written, bool &inClosed)
278 : {
279 5 : ssize_t w = write(inFd, input.data() + written, input.size() - written);
280 5 : if (w > 0) {
281 5 : written += static_cast<size_t>(w);
282 0 : } else if (w < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
283 0 : inClosed = true;
284 0 : (void)close(inFd);
285 0 : inFd = -1;
286 0 : return;
287 : }
288 5 : if (!inClosed && written >= input.size()) {
289 5 : inClosed = true;
290 5 : (void)close(inFd);
291 5 : inFd = -1;
292 : }
293 : }
294 :
295 : // 组装本轮 poll 的 pollfd 数组:outFd 恒在 [0] 收 POLLIN;stdin 未写完时把 inFd 加为 [1] 收 POLLOUT。
296 : // 返回待 poll 的 fd 数量,并回填 outIdx / inIdx(inIdx=-1 表示本轮不再关注 stdin)。
297 20 : nfds_t BuildPollFds(const SymbolizerProc &proc, bool inClosed, struct pollfd fds[2], int &outIdx, int &inIdx)
298 : {
299 20 : nfds_t nfds = 0;
300 20 : outIdx = static_cast<int>(nfds);
301 20 : fds[nfds].fd = proc.outFd;
302 20 : fds[nfds].events = POLLIN;
303 20 : fds[nfds].revents = 0;
304 20 : ++nfds;
305 20 : inIdx = -1;
306 20 : if (!inClosed) {
307 5 : inIdx = static_cast<int>(nfds);
308 5 : fds[nfds].fd = proc.inFd;
309 5 : fds[nfds].events = POLLOUT;
310 5 : fds[nfds].revents = 0;
311 5 : ++nfds;
312 : }
313 20 : return nfds;
314 : }
315 :
316 : // 同一 poll 循环并发驱动 stdin 写与 stdout 读,避免"先写满 stdin 再读 stdout"的父子互相背压死锁。
317 : // 两端置非阻塞并统一挂在 deadline 下;超时返回 false。返回后 proc.inFd 已关闭。
318 5 : bool PumpSymbolizerIo(SymbolizerProc &proc, const std::string &input, std::string &output)
319 : {
320 5 : (void)fcntl(proc.inFd, F_SETFL, O_NONBLOCK);
321 5 : (void)fcntl(proc.outFd, F_SETFL, O_NONBLOCK);
322 5 : size_t written = 0;
323 5 : bool inClosed = false;
324 5 : bool outEof = false;
325 5 : bool timedOut = false;
326 5 : const int64_t deadline = NowMs() + SYMBOLIZER_TIMEOUT_MS;
327 24 : while (!outEof) {
328 20 : const int64_t remain = deadline - NowMs();
329 20 : if (remain <= 0) {
330 0 : timedOut = true;
331 1 : break;
332 : }
333 : struct pollfd fds[2];
334 20 : int outIdx = -1;
335 20 : int inIdx = -1;
336 20 : const nfds_t nfds = BuildPollFds(proc, inClosed, fds, outIdx, inIdx);
337 20 : int pr = poll(fds, nfds, static_cast<int>(remain));
338 20 : if (pr < 0) {
339 0 : if (errno == EINTR) {
340 0 : continue;
341 : }
342 0 : break;
343 : }
344 20 : if (pr == 0) {
345 1 : timedOut = true;
346 1 : break;
347 : }
348 : // 优先读 stdout,避免子进程被 stdout 管道背压阻塞。
349 33 : if ((fds[outIdx].revents & (POLLIN | POLLHUP | POLLERR)) != 0 &&
350 14 : !DrainReadable(proc.outFd, output, outEof)) {
351 0 : break;
352 : }
353 19 : if (inIdx >= 0 && (fds[inIdx].revents & (POLLOUT | POLLHUP | POLLERR)) != 0) {
354 5 : PumpWritable(proc.inFd, input, written, inClosed);
355 : }
356 : }
357 5 : if (!inClosed && proc.inFd >= 0) {
358 0 : (void)close(proc.inFd);
359 0 : proc.inFd = -1;
360 : }
361 5 : return !timedOut;
362 : }
363 :
364 : // 进程级忽略 SIGPIPE:子进程异常早退关闭 stdin 读端时,父进程 write 默认动作是被 SIGPIPE 终止,
365 : // 忽略后 write 改为返回 EPIPE,从而走 best-effort 降级而非杀死宿主进程(fujun19 检视点)。
366 : // 只需设置一次;用 call_once 保证幂等,且不覆盖用户可能已有的 SIGPIPE 处理时保持 SIG_IGN 语义。
367 5 : void IgnoreSigPipeOnce()
368 : {
369 : static std::once_flag onceFlag;
370 5 : std::call_once(onceFlag, []() {
371 1 : struct sigaction sa{};
372 1 : sa.sa_handler = SIG_IGN;
373 1 : (void)sigemptyset(&sa.sa_mask);
374 1 : sa.sa_flags = 0;
375 1 : (void)sigaction(SIGPIPE, &sa, nullptr);
376 1 : });
377 5 : }
378 :
379 : // 在宽限期内以 WNOHANG 轮询回收子进程。已回收(或不可回收 ECHILD)返回 true;
380 : // 到期仍在运行返回 false,交由调用方 SIGKILL 兜底。
381 5 : bool WaitChildExit(pid_t pid, int64_t graceMs)
382 : {
383 5 : const int64_t graceDeadline = NowMs() + graceMs;
384 : do {
385 7 : const pid_t r = waitpid(pid, nullptr, WNOHANG);
386 7 : if (r == pid || (r < 0 && errno != EINTR)) {
387 5 : return true;
388 : }
389 2 : struct timespec ts{0, SYMBOLIZER_TERM_POLL_NS};
390 2 : (void)nanosleep(&ts, nullptr);
391 2 : } while (NowMs() < graceDeadline);
392 0 : return false;
393 : }
394 :
395 : // 统一回收子进程,遵循 G.STD.17-CPP「先通知、限时等待、再强制终止」的顺序,并把回收纳入 deadline
396 : // 避免无界阻塞的 waitpid(zhangpengpeng8 检视点):先发 SIGTERM 通知子进程自行退出(无论是否超时,
397 : // 正常路径下子进程收到 stdin EOF 本应自退,此处 SIGTERM 仅为兜底通知);随后在宽限期内 WNOHANG 轮询
398 : // 回收;到期仍未退出,说明子进程已挂死不响应优雅通知,再 SIGKILL 强制终止并阻塞回收(SIGKILL 后
399 : // 子进程必然很快退出,不会僵尸/久等)。
400 5 : void ReapChild(pid_t pid)
401 : {
402 : // 先礼:通知目标子进程停止,给足宽限期等待其自行退出。
403 5 : (void)kill(pid, SIGTERM);
404 : // 后兵:仅当宽限期内等待超时(子进程仍未退出)时才强制终止并回收;
405 : // 否则子进程已在宽限期内自行退出并被 WaitChildExit 回收,正常返回。
406 5 : if (!WaitChildExit(pid, SYMBOLIZER_TERM_GRACE_MS)) {
407 0 : (void)kill(pid, SIGKILL);
408 0 : (void)waitpid(pid, nullptr, 0);
409 : }
410 5 : }
411 :
412 : // 判断子串 [begin, end) 是否非空且全为十进制数字。
413 13 : bool IsAllDigits(const std::string &s, size_t begin, size_t end)
414 : {
415 13 : if (begin >= end) {
416 1 : return false;
417 : }
418 29 : for (size_t i = begin; i < end; ++i) {
419 17 : if (s[i] < '0' || s[i] > '9') {
420 0 : return false;
421 : }
422 : }
423 12 : return true;
424 : }
425 :
426 : // 判断是否为位置行:形如 file:line:col,即最后两个冒号分隔的字段均为数字。
427 : // 用于把位置行与函数名行区分开——未加 -C 时函数名默认仍会 demangle,可能含 '::'(如 ns::foo(int)),
428 : // 仅凭"含冒号"无法区分,故要求结尾严格为 :<数字>:<数字>。llvm-symbolizer 未知位置标记 ??:0:0 亦满足。
429 12 : bool IsLocationLine(const std::string &line, size_t &colPos, size_t &linePos)
430 : {
431 12 : colPos = line.rfind(':');
432 12 : if (colPos == std::string::npos || colPos == 0) {
433 5 : return false;
434 : }
435 7 : linePos = line.rfind(':', colPos - 1);
436 7 : if (linePos == std::string::npos) {
437 0 : return false;
438 : }
439 7 : return IsAllDigits(line, linePos + 1, colPos) && IsAllDigits(line, colPos + 1, line.size());
440 : }
441 :
442 : // 从一行 file:line:col 文本填充 res 的源码位置;从右侧解析 line 与 column,兼容路径含冒号。
443 6 : void FillLocation(const std::string &line, size_t colPos, size_t linePos, SymbolizeResult &res)
444 : {
445 6 : res.srcFile = line.substr(0, linePos);
446 6 : res.srcLine = ParseDecU32(line.substr(linePos + 1, colPos - linePos - 1));
447 6 : res.srcColumn = ParseDecU32(line.substr(colPos + 1));
448 6 : res.ok = (res.srcFile != UNKNOWN_MARK) && !res.srcFile.empty();
449 6 : }
450 :
451 : // 解析 llvm-symbolizer 默认输出:不依赖固定行数,按空行把输出切成块,第 i 块对应第 i 个偏移。
452 : // 块内可能混有函数名行与(内联展开的)多组位置行;只取块内第一条位置行(file:line:col),
453 : // 即最内层帧的源码位置,函数名行与外层内联帧一律忽略。
454 4 : void ParseSymbolizerOutput(const std::string &output, std::vector<SymbolizeResult> &results)
455 : {
456 4 : std::istringstream iss(output);
457 4 : std::string line;
458 4 : size_t idx = 0;
459 4 : bool blockHasLoc = false; // 当前块是否已取到位置行
460 4 : bool blockStarted = false; // 当前块是否已出现任何非空行
461 24 : while (idx < results.size() && std::getline(iss, line)) {
462 20 : if (line.empty()) {
463 : // 空行 = 块边界:已开始的块结束,推进到下一个偏移。
464 6 : if (blockStarted) {
465 6 : ++idx;
466 6 : blockHasLoc = false;
467 6 : blockStarted = false;
468 : }
469 6 : continue;
470 : }
471 14 : blockStarted = true;
472 14 : size_t colPos = 0;
473 14 : size_t linePos = 0;
474 : // 每块只认第一条位置行,后续内联外层帧与函数名行忽略。
475 14 : if (!blockHasLoc && IsLocationLine(line, colPos, linePos)) {
476 6 : FillLocation(line, colPos, linePos, results[idx]);
477 6 : blockHasLoc = true;
478 : }
479 : }
480 4 : }
481 : } // namespace
482 :
483 : #ifdef __ADUMP_LLT
484 24 : void KernelSourceSymbolizer::ResetLocateCacheForTest()
485 : {
486 24 : std::lock_guard<std::mutex> lock(g_locateMutex);
487 24 : g_cachedTool.clear();
488 24 : g_toolResolved = false;
489 24 : }
490 : #endif
491 :
492 9 : const std::string &KernelSourceSymbolizer::LocateSymbolizer()
493 : {
494 9 : std::lock_guard<std::mutex> lock(g_locateMutex);
495 9 : if (g_toolResolved) {
496 0 : return g_cachedTool;
497 : }
498 9 : g_cachedTool = ResolveSymbolizerPath();
499 9 : g_toolResolved = true;
500 9 : return g_cachedTool;
501 9 : }
502 :
503 2 : bool KernelSourceSymbolizer::IsAvailable()
504 : {
505 2 : return !LocateSymbolizer().empty();
506 : }
507 :
508 9 : bool KernelSourceSymbolizer::HasDebugLine(const std::string &oFilePath)
509 : {
510 9 : Path path(oFilePath);
511 9 : if (!path.RealPath()) {
512 5 : IDE_LOGD("HasDebugLine: invalid path %s.", oFilePath.c_str());
513 5 : return false;
514 : }
515 4 : FILE *fp = fopen(path.GetCString(), "rb");
516 4 : if (fp == nullptr) {
517 0 : IDE_LOGD("HasDebugLine: open failed %s.", path.GetCString());
518 0 : return false;
519 : }
520 4 : std::string buf;
521 : char tmp[READ_BUF_SIZE];
522 4 : size_t n = 0;
523 8 : while ((n = fread(tmp, 1, sizeof(tmp), fp)) > 0) {
524 4 : buf.append(tmp, n);
525 : }
526 4 : (void)fclose(fp);
527 4 : return ElfHasSection(buf.data(), buf.size(), ".debug_line");
528 9 : }
529 :
530 8 : bool KernelSourceSymbolizer::Symbolize(const std::string &oFilePath, const std::vector<uint64_t> &offsets,
531 : std::vector<SymbolizeResult> &results)
532 : {
533 8 : results.clear();
534 8 : results.resize(offsets.size());
535 8 : if (offsets.empty()) {
536 1 : return false;
537 : }
538 7 : const std::string &tool = LocateSymbolizer();
539 7 : if (tool.empty()) {
540 1 : return false;
541 : }
542 : // 单 .o 一次校验:无效则整体失败,results 保持占位(ok=false)。
543 6 : Path path(oFilePath);
544 6 : if (oFilePath.empty() || !path.RealPath()) {
545 1 : IDE_LOGW("Symbolize: invalid .o path, skip. path=%s.", oFilePath.c_str());
546 1 : return false;
547 : }
548 :
549 : // 该 .o 的所有偏移按序写入 stdin:文件名加引号以容忍路径中的空格;地址十六进制。
550 5 : std::ostringstream oss;
551 13 : for (uint64_t off : offsets) {
552 8 : oss << "\"" << path.GetString() << "\" 0x" << std::hex << off << "\n";
553 : }
554 5 : return RunSymbolizer(tool, oss.str(), results);
555 6 : }
556 :
557 5 : bool KernelSourceSymbolizer::RunSymbolizer(const std::string &tool, const std::string &inputLines,
558 : std::vector<SymbolizeResult> &results)
559 : {
560 : // 写 stdin 前先忽略 SIGPIPE,子进程早退关闭读端时 write 返回 EPIPE 走降级,而非终止宿主进程。
561 5 : IgnoreSigPipeOnce();
562 5 : SymbolizerProc proc;
563 5 : if (!SpawnSymbolizer(tool, proc)) {
564 0 : return false;
565 : }
566 :
567 5 : std::string output;
568 5 : const bool ok = PumpSymbolizerIo(proc, inputLines, output);
569 5 : (void)close(proc.outFd);
570 5 : proc.outFd = -1;
571 :
572 5 : if (!ok) {
573 1 : IDE_LOGW("Symbolize: llvm-symbolizer timed out after %ldms, terminate child pid=%d.",
574 : SYMBOLIZER_TIMEOUT_MS, proc.pid);
575 : }
576 : // 回收纳入宽限 deadline:先 SIGTERM 通知、限时等待、必要时 SIGKILL 兜底,
577 : // 即便子进程关闭 stdout 后仍挂住,也不会在此无界阻塞。
578 5 : ReapChild(proc.pid);
579 5 : if (!ok) {
580 1 : return false;
581 : }
582 :
583 : // 原样打印 llvm-symbolizer 的完整原始输出,便于现场直接查看未经加工的解析结果。
584 4 : if (!output.empty()) {
585 3 : IDE_LOGE("[Dump][Exception][Symbolize] llvm-symbolizer raw output:\n%s", output.c_str());
586 : }
587 4 : ParseSymbolizerOutput(output, results);
588 4 : return true;
589 5 : }
590 :
591 : } // namespace Adx
|