Coverage Report

Created: 2026-09-28 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/system/stacktrace.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "system/stacktrace.h"
5
#include "common/spdlog.h"
6
7
#include "runtime/instance/module.h"
8
#include <cstdint>
9
#include <map>
10
#include <unordered_map>
11
12
#if WASMEDGE_OS_WINDOWS
13
#include "system/winapi.h"
14
#elif WASMEDGE_OS_LINUX
15
#include <unwind.h>
16
#elif WASMEDGE_OS_MACOS
17
#include <execinfo.h>
18
#endif
19
20
namespace WasmEdge {
21
22
0
Span<void *const> stackTrace(Span<void *> Buffer) noexcept {
23
#if WASMEDGE_OS_WINDOWS
24
  struct DbgHelp {
25
    DbgHelp() noexcept : Process(winapi::GetCurrentProcess()) {
26
      winapi::SymSetOptions(winapi::SYMOPT_DEFERRED_LOADS_);
27
      winapi::SymInitializeW(Process, nullptr, true);
28
      SelfBase = winapi::SymGetModuleBase64(
29
          Process, reinterpret_cast<winapi::DWORD64_>(&stackTrace));
30
      NtDllBase = winapi::SymGetModuleBase64(
31
          Process, reinterpret_cast<winapi::DWORD64_>(
32
                       &winapi::RtlCaptureStackBackTrace));
33
      Kernel32Base = winapi::SymGetModuleBase64(
34
          Process, reinterpret_cast<winapi::DWORD64_>(&winapi::CloseHandle));
35
    }
36
    ~DbgHelp() noexcept { winapi::SymCleanup(Process); }
37
    void refresh() noexcept { winapi::SymRefreshModuleList(Process); }
38
    winapi::HANDLE_ Process;
39
    winapi::DWORD64_ SelfBase, NtDllBase, Kernel32Base;
40
  };
41
  static DbgHelp Helper;
42
  Helper.refresh();
43
  auto Depth = static_cast<size_t>(winapi::RtlCaptureStackBackTrace(
44
      1u, static_cast<winapi::ULONG_>(Buffer.size()), Buffer.data(), nullptr));
45
  size_t NewDepth = 0;
46
  for (size_t I = 0; I < Depth; ++I) {
47
    auto Base = winapi::SymGetModuleBase64(
48
        Helper.Process, reinterpret_cast<winapi::DWORD64_>(Buffer[I]));
49
    if (Base == 0 || (Base != Helper.SelfBase && Base != Helper.NtDllBase &&
50
                      Base != Helper.Kernel32Base)) {
51
      Buffer[NewDepth++] = Buffer[I];
52
    }
53
  }
54
  return Buffer.first(static_cast<size_t>(NewDepth));
55
#elif WASMEDGE_OS_LINUX
56
  struct BacktraceState {
57
0
    Span<void *> Buffer;
58
0
    size_t Index;
59
0
  };
60
0
  BacktraceState State{Buffer, 0};
61
0
  _Unwind_Backtrace(
62
0
      [](struct _Unwind_Context *Ctx, void *Arg) noexcept {
63
0
        auto &BTState = *static_cast<BacktraceState *>(Arg);
64
0
        if (BTState.Index >= BTState.Buffer.size()) {
65
0
          return _URC_END_OF_STACK;
66
0
        }
67
0
        BTState.Buffer[BTState.Index++] =
68
0
            reinterpret_cast<void *>(_Unwind_GetIP(Ctx));
69
0
        return _URC_NO_REASON;
70
0
      },
71
0
      &State);
72
0
  return Buffer.first(State.Index);
73
#elif WASMEDGE_OS_MACOS
74
  const auto Depth = backtrace(Buffer.data(), Buffer.size());
75
  return Buffer.first(Depth);
76
#endif
77
0
}
78
79
Span<const StackTraceEntry>
80
interpreterStackTrace(const Runtime::StackManager &StackMgr,
81
0
                      Span<StackTraceEntry> Buffer) noexcept {
82
0
  size_t Index = 0;
83
0
  std::unordered_map<const Runtime::Instance::ModuleInstance *,
84
0
                     std::map<AST::InstrView::iterator, int64_t>>
85
0
      Cache;
86
0
  const auto Frames = StackMgr.getFramesSpan();
87
0
  for (size_t I = 1; I < Frames.size(); ++I) {
88
    // A native-entry frame carries the callee's own end iterator instead of a
89
    // return address in the caller, so it resolves against no module.
90
0
    if (Frames[I].NativeEntry) {
91
0
      continue;
92
0
    }
93
0
    const auto *Module = Frames[I - 1].Module;
94
0
    if (Module == nullptr) {
95
0
      continue;
96
0
    }
97
0
    auto [CacheIter, Inserted] = Cache.try_emplace(Module);
98
0
    auto &Funcs = CacheIter->second;
99
0
    if (Inserted) {
100
0
      const auto FuncInsts = Module->getFunctionInstances();
101
0
      for (size_t J = 0; J < FuncInsts.size(); ++J) {
102
0
        const auto &Func = FuncInsts[J];
103
0
        if (Func && Func->isWasmFunction()) {
104
0
          const auto &Instrs = Func->getInstrs();
105
0
          Funcs.emplace(Instrs.end(), INT64_C(-1));
106
0
          Funcs.emplace(Instrs.begin(), static_cast<int64_t>(J));
107
0
        }
108
0
      }
109
0
    }
110
0
    auto Entry = Frames[I].From;
111
0
    auto Iter = Funcs.lower_bound(Entry);
112
0
    if ((Iter == Funcs.end() || Iter->first > Entry) && Iter != Funcs.begin()) {
113
0
      --Iter;
114
0
    }
115
0
    if (Iter != Funcs.end() && Iter->first <= Entry &&
116
0
        Iter->second >= INT64_C(0) && Index < Buffer.size()) {
117
0
      Buffer[Index++] =
118
0
          StackTraceEntry{Module, static_cast<uint32_t>(Iter->second)};
119
0
    }
120
0
  }
121
0
  return Buffer.first(Index);
122
0
}
123
124
Span<const StackTraceEntry>
125
compiledStackTrace(Span<const Runtime::Instance::ModuleInstance *const> Modules,
126
                   Span<void *const> Stack,
127
0
                   Span<StackTraceEntry> Buffer) noexcept {
128
0
  struct FuncEntry {
129
0
    const Runtime::Instance::ModuleInstance *Module;
130
0
    int64_t Index;
131
0
  };
132
  // Known limitation: two instances of the same compiled module share their
133
  // code addresses, so emplace keeps whichever instance is enumerated first and
134
  // a trap in the other one is reported against it. A native frame carries no
135
  // instance identity to tell them apart.
136
0
  std::map<void *, FuncEntry> Funcs;
137
0
  for (const auto *Module : Modules) {
138
0
    if (Module == nullptr) {
139
0
      continue;
140
0
    }
141
0
    const auto FuncInsts = Module->getFunctionInstances();
142
0
    for (size_t I = 0; I < FuncInsts.size(); ++I) {
143
0
      const auto &Func = FuncInsts[I];
144
0
      if (Func && Func->isCompiledFunction() && Func->getModule() == Module) {
145
0
        Funcs.emplace(
146
0
            reinterpret_cast<void *>(Func->getFuncType().getSymbol().get()),
147
0
            FuncEntry{Module, INT64_C(-1)});
148
0
        Funcs.emplace(Func->getSymbol().get(),
149
0
                      FuncEntry{Module, static_cast<int64_t>(I)});
150
0
      }
151
0
    }
152
0
  }
153
0
  size_t Index = 0;
154
0
  for (auto Address : Stack) {
155
0
    auto Probe =
156
0
        reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(Address) - 1);
157
0
    auto Iter = Funcs.lower_bound(Probe);
158
0
    if ((Iter == Funcs.end() || Iter->first > Probe) && Iter != Funcs.begin()) {
159
0
      --Iter;
160
0
    }
161
0
    if (Iter != Funcs.end() && Iter->first < Probe &&
162
0
        Iter->second.Index >= INT64_C(0) && Index < Buffer.size()) {
163
0
      Buffer[Index++] = StackTraceEntry{
164
0
          Iter->second.Module, static_cast<uint32_t>(Iter->second.Index)};
165
0
    }
166
0
  }
167
0
  return Buffer.first(Index);
168
0
}
169
170
} // namespace WasmEdge