LCOV - code coverage report
Current view: top level - adump/exception - kernel_source_symbolizer.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 83.9 % 322 270
Test Date: 2026-08-17 09:38:02 Functions: 100.0 % 26 26

            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
        

Generated by: LCOV version 2.0-1