Coverage Report

Created: 2026-11-12 06:35

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 <pthread.h>
16
#include <unwind.h>
17
#elif WASMEDGE_OS_MACOS
18
#include <execinfo.h>
19
#include <pthread.h>
20
#endif
21
22
namespace WasmEdge {
23
24
0
Span<void *const> stackTrace(Span<void *> Buffer) noexcept {
25
#if WASMEDGE_OS_WINDOWS
26
  struct DbgHelp {
27
    DbgHelp() noexcept : Process(winapi::GetCurrentProcess()) {
28
      winapi::SymSetOptions(winapi::SYMOPT_DEFERRED_LOADS_);
29
      winapi::SymInitializeW(Process, nullptr, true);
30
      SelfBase = winapi::SymGetModuleBase64(
31
          Process, reinterpret_cast<winapi::DWORD64_>(&stackTrace));
32
      NtDllBase = winapi::SymGetModuleBase64(
33
          Process, reinterpret_cast<winapi::DWORD64_>(
34
                       &winapi::RtlCaptureStackBackTrace));
35
      Kernel32Base = winapi::SymGetModuleBase64(
36
          Process, reinterpret_cast<winapi::DWORD64_>(&winapi::CloseHandle));
37
    }
38
    ~DbgHelp() noexcept { winapi::SymCleanup(Process); }
39
    void refresh() noexcept { winapi::SymRefreshModuleList(Process); }
40
    winapi::HANDLE_ Process;
41
    winapi::DWORD64_ SelfBase, NtDllBase, Kernel32Base;
42
  };
43
  static DbgHelp Helper;
44
  Helper.refresh();
45
  auto Depth = static_cast<size_t>(winapi::RtlCaptureStackBackTrace(
46
      1u, static_cast<winapi::ULONG_>(Buffer.size()), Buffer.data(), nullptr));
47
  size_t NewDepth = 0;
48
  for (size_t I = 0; I < Depth; ++I) {
49
    auto Base = winapi::SymGetModuleBase64(
50
        Helper.Process, reinterpret_cast<winapi::DWORD64_>(Buffer[I]));
51
    if (Base == 0 || (Base != Helper.SelfBase && Base != Helper.NtDllBase &&
52
                      Base != Helper.Kernel32Base)) {
53
      Buffer[NewDepth++] = Buffer[I];
54
    }
55
  }
56
  return Buffer.first(static_cast<size_t>(NewDepth));
57
#elif WASMEDGE_OS_LINUX
58
  struct BacktraceState {
59
0
    Span<void *> Buffer;
60
0
    size_t Index;
61
0
  };
62
0
  BacktraceState State{Buffer, 0};
63
0
  _Unwind_Backtrace(
64
0
      [](struct _Unwind_Context *Ctx, void *Arg) noexcept {
65
0
        auto &BTState = *static_cast<BacktraceState *>(Arg);
66
0
        if (BTState.Index >= BTState.Buffer.size()) {
67
0
          return _URC_END_OF_STACK;
68
0
        }
69
0
        BTState.Buffer[BTState.Index++] =
70
0
            reinterpret_cast<void *>(_Unwind_GetIP(Ctx));
71
0
        return _URC_NO_REASON;
72
0
      },
73
0
      &State);
74
0
  return Buffer.first(State.Index);
75
#elif WASMEDGE_OS_MACOS
76
  const auto Depth = backtrace(Buffer.data(), Buffer.size());
77
  return Buffer.first(Depth);
78
#endif
79
0
}
80
81
0
std::pair<uintptr_t, uintptr_t> getThreadStackBounds() noexcept {
82
#if WASMEDGE_OS_WINDOWS
83
#if NTDDI_VERSION >= NTDDI_WIN8
84
  winapi::ULONG_PTR_ Low = 0;
85
  winapi::ULONG_PTR_ High = 0;
86
  winapi::GetCurrentThreadStackLimits(&Low, &High);
87
  return {Low, High};
88
#else
89
  return {0, 0};
90
#endif
91
#elif WASMEDGE_OS_LINUX
92
  // pthread_getattr_np scans /proc/self/maps for the main thread, so query it
93
  // once per thread.
94
0
  thread_local const std::pair<uintptr_t, uintptr_t> Bounds = []() noexcept {
95
0
    pthread_attr_t Attr;
96
0
    if (pthread_getattr_np(pthread_self(), &Attr) != 0) {
97
0
      return std::pair<uintptr_t, uintptr_t>{0, 0};
98
0
    }
99
0
    void *Addr = nullptr;
100
0
    size_t Size = 0;
101
0
    const bool HasStack = pthread_attr_getstack(&Attr, &Addr, &Size) == 0;
102
0
    pthread_attr_destroy(&Attr);
103
0
    if (!HasStack) {
104
0
      return std::pair<uintptr_t, uintptr_t>{0, 0};
105
0
    }
106
0
    const auto Low = reinterpret_cast<uintptr_t>(Addr);
107
0
    return std::pair<uintptr_t, uintptr_t>{Low, Low + Size};
108
0
  }();
109
0
  return Bounds;
110
#elif WASMEDGE_OS_MACOS
111
  const pthread_t Self = pthread_self();
112
  const auto High = reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(Self));
113
  return {High - pthread_get_stacksize_np(Self), High};
114
#endif
115
0
}
116
117
Span<const StackTraceEntry>
118
interpreterStackTrace(const Runtime::StackManager &StackMgr,
119
0
                      Span<StackTraceEntry> Buffer) noexcept {
120
0
  size_t Index = 0;
121
0
  std::unordered_map<const Runtime::Instance::ModuleInstance *,
122
0
                     std::map<AST::InstrView::iterator, int64_t>>
123
0
      Cache;
124
0
  const auto Frames = StackMgr.getFramesSpan();
125
0
  for (size_t I = 1; I < Frames.size(); ++I) {
126
    // A native-entry frame carries the callee's own end iterator instead of a
127
    // return address in the caller, so it resolves against no module.
128
0
    if (Frames[I].NativeEntry) {
129
0
      continue;
130
0
    }
131
0
    const auto *Module = Frames[I - 1].Module;
132
0
    if (Module == nullptr) {
133
0
      continue;
134
0
    }
135
0
    auto [CacheIter, Inserted] = Cache.try_emplace(Module);
136
0
    auto &Funcs = CacheIter->second;
137
0
    if (Inserted) {
138
0
      const auto FuncInsts = Module->getFunctionInstances();
139
0
      for (size_t J = 0; J < FuncInsts.size(); ++J) {
140
0
        const auto &Func = FuncInsts[J];
141
0
        if (Func && Func->isWasmFunction()) {
142
0
          const auto &Instrs = Func->getInstrs();
143
0
          Funcs.emplace(Instrs.end(), INT64_C(-1));
144
0
          Funcs.emplace(Instrs.begin(), static_cast<int64_t>(J));
145
0
        }
146
0
      }
147
0
    }
148
0
    auto Entry = Frames[I].From;
149
0
    auto Iter = Funcs.lower_bound(Entry);
150
0
    if ((Iter == Funcs.end() || Iter->first > Entry) && Iter != Funcs.begin()) {
151
0
      --Iter;
152
0
    }
153
0
    if (Iter != Funcs.end() && Iter->first <= Entry &&
154
0
        Iter->second >= INT64_C(0) && Index < Buffer.size()) {
155
0
      Buffer[Index++] =
156
0
          StackTraceEntry{Module, static_cast<uint32_t>(Iter->second)};
157
0
    }
158
0
  }
159
0
  return Buffer.first(Index);
160
0
}
161
162
Span<const StackTraceEntry>
163
compiledStackTrace(Span<const Runtime::Instance::ModuleInstance *const> Modules,
164
                   Span<void *const> Stack,
165
0
                   Span<StackTraceEntry> Buffer) noexcept {
166
0
  struct FuncEntry {
167
0
    const Runtime::Instance::ModuleInstance *Module;
168
0
    int64_t Index;
169
0
  };
170
  // Known limitation: two instances of the same compiled module share their
171
  // code addresses, so emplace keeps whichever instance is enumerated first and
172
  // a trap in the other one is reported against it. A native frame carries no
173
  // instance identity to tell them apart.
174
0
  std::map<void *, FuncEntry> Funcs;
175
0
  for (const auto *Module : Modules) {
176
0
    if (Module == nullptr) {
177
0
      continue;
178
0
    }
179
0
    const auto FuncInsts = Module->getFunctionInstances();
180
0
    for (size_t I = 0; I < FuncInsts.size(); ++I) {
181
0
      const auto &Func = FuncInsts[I];
182
0
      if (Func && Func->isCompiledFunction() && Func->getModule() == Module) {
183
0
        Funcs.emplace(
184
0
            reinterpret_cast<void *>(Func->getFuncType().getSymbol().get()),
185
0
            FuncEntry{Module, INT64_C(-1)});
186
0
        Funcs.emplace(Func->getSymbol().get(),
187
0
                      FuncEntry{Module, static_cast<int64_t>(I)});
188
0
      }
189
0
    }
190
0
  }
191
0
  size_t Index = 0;
192
0
  for (auto Address : Stack) {
193
0
    auto Probe =
194
0
        reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(Address) - 1);
195
0
    auto Iter = Funcs.lower_bound(Probe);
196
0
    if ((Iter == Funcs.end() || Iter->first > Probe) && Iter != Funcs.begin()) {
197
0
      --Iter;
198
0
    }
199
0
    if (Iter != Funcs.end() && Iter->first < Probe &&
200
0
        Iter->second.Index >= INT64_C(0) && Index < Buffer.size()) {
201
0
      Buffer[Index++] = StackTraceEntry{
202
0
          Iter->second.Module, static_cast<uint32_t>(Iter->second.Index)};
203
0
    }
204
0
  }
205
0
  return Buffer.first(Index);
206
0
}
207
208
} // namespace WasmEdge