LCOV - code coverage report
Current view: top level - adump/exception - kernel_source_symbolizer.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 86.6 % 306 265
Test Date: 2026-08-31 10:09:28 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           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
        

Generated by: LCOV version 2.0-1