227 lines
6.1 KiB
C++
227 lines
6.1 KiB
C++
#pragma once
|
||
|
||
// Host-side summarizer for Wasmtime trap/error messages (WASM-PROPOSAL
|
||
// Phase 4: trap → error-page path). Turns a raw runtime message — which for
|
||
// stack exhaustion contains hundreds of identical frames — into a compact,
|
||
// error-page-ready summary: headline cause, optional detail (e.g. faulting
|
||
// address), and a backtrace with consecutive repeated frames collapsed.
|
||
//
|
||
// Pure string processing with no wasm-runtime dependency, so it is testable
|
||
// from the native suite and reusable by both the phase-4 spike runner and
|
||
// the future wasm worker. Tolerant of unrecognized input: an unparsed
|
||
// message is passed through as the cause, never dropped.
|
||
|
||
#include <cstdlib>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#if defined(__GNUG__)
|
||
#include <cxxabi.h>
|
||
#endif
|
||
|
||
struct WasmTraceSummary
|
||
{
|
||
std::string cause; // "wasm trap: ..." headline, or the raw message if unparsed
|
||
std::string detail; // secondary cause lines, e.g. "memory fault at wasm address ..."
|
||
std::vector<std::string> frames; // collapsed display lines
|
||
size_t total_frames = 0;
|
||
bool parsed = false; // a wasm backtrace section was recognized
|
||
};
|
||
|
||
inline std::string wasm_trace_demangle(const std::string& name)
|
||
{
|
||
#if defined(__GNUG__)
|
||
// frame names look like "module!symbol"; demangle the symbol part
|
||
auto bang = name.find('!');
|
||
std::string prefix = bang == std::string::npos ? "" : name.substr(0, bang + 1);
|
||
std::string symbol = bang == std::string::npos ? name : name.substr(bang + 1);
|
||
if(symbol.rfind("_Z", 0) == 0)
|
||
{
|
||
int status = 0;
|
||
char* demangled = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
|
||
if(status == 0 && demangled)
|
||
{
|
||
std::string result = prefix + demangled;
|
||
free(demangled);
|
||
return(result);
|
||
}
|
||
if(demangled)
|
||
free(demangled);
|
||
}
|
||
#endif
|
||
return(name);
|
||
}
|
||
|
||
inline WasmTraceSummary wasm_trace_summarize(const std::string& message, size_t max_frame_lines = 12)
|
||
{
|
||
WasmTraceSummary summary;
|
||
|
||
struct Frame
|
||
{
|
||
std::string name;
|
||
std::string address;
|
||
};
|
||
std::vector<Frame> raw_frames;
|
||
std::vector<std::string> cause_lines;
|
||
|
||
auto trim = [](const std::string& s) {
|
||
size_t a = s.find_first_not_of(" \t\r");
|
||
if(a == std::string::npos)
|
||
return(std::string());
|
||
size_t b = s.find_last_not_of(" \t\r");
|
||
return(s.substr(a, b - a + 1));
|
||
};
|
||
|
||
// a frame or numbered-cause line starts with "N:"; strip that prefix
|
||
auto strip_index = [&](const std::string& line, bool& had_index) {
|
||
had_index = false;
|
||
size_t i = 0;
|
||
while(i < line.size() && line[i] >= '0' && line[i] <= '9')
|
||
i++;
|
||
if(i == 0 || i >= line.size() || line[i] != ':')
|
||
return(line);
|
||
had_index = true;
|
||
return(trim(line.substr(i + 1)));
|
||
};
|
||
|
||
bool in_backtrace = false;
|
||
bool in_caused_by = false;
|
||
size_t pos = 0;
|
||
while(pos <= message.size())
|
||
{
|
||
size_t eol = message.find('\n', pos);
|
||
std::string line = trim(message.substr(pos, (eol == std::string::npos ? message.size() : eol) - pos));
|
||
pos = eol == std::string::npos ? message.size() + 1 : eol + 1;
|
||
if(line.empty())
|
||
continue;
|
||
|
||
if(line.find("wasm backtrace") != std::string::npos)
|
||
{
|
||
summary.parsed = true;
|
||
in_backtrace = true;
|
||
in_caused_by = false;
|
||
continue;
|
||
}
|
||
if(line.rfind("Caused by", 0) == 0)
|
||
{
|
||
in_backtrace = false;
|
||
in_caused_by = true;
|
||
continue;
|
||
}
|
||
|
||
bool had_index = false;
|
||
std::string body = strip_index(line, had_index);
|
||
if(in_backtrace && had_index)
|
||
{
|
||
Frame frame;
|
||
// optional "0xADDR - " prefix before the frame name
|
||
if(body.rfind("0x", 0) == 0)
|
||
{
|
||
size_t dash = body.find(" - ");
|
||
if(dash != std::string::npos)
|
||
{
|
||
frame.address = body.substr(0, dash);
|
||
body = trim(body.substr(dash + 3));
|
||
}
|
||
}
|
||
frame.name = wasm_trace_demangle(body);
|
||
raw_frames.push_back(frame);
|
||
continue;
|
||
}
|
||
if(in_caused_by || line.rfind("wasm trap:", 0) == 0)
|
||
{
|
||
cause_lines.push_back(body);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
summary.total_frames = raw_frames.size();
|
||
|
||
// headline = the "wasm trap:" line; everything else in Caused by → detail
|
||
for(auto& line : cause_lines)
|
||
{
|
||
if(line.rfind("wasm trap:", 0) == 0)
|
||
summary.cause = line;
|
||
else
|
||
summary.detail += (summary.detail.empty() ? "" : "; ") + line;
|
||
}
|
||
if(summary.cause.empty())
|
||
summary.cause = summary.parsed && !cause_lines.empty() ? cause_lines.front() : trim(message);
|
||
|
||
// collapse consecutive identical frame names
|
||
struct Group
|
||
{
|
||
std::string name;
|
||
std::string address;
|
||
size_t first = 0;
|
||
size_t count = 0;
|
||
};
|
||
std::vector<Group> groups;
|
||
for(size_t i = 0; i < raw_frames.size(); i++)
|
||
{
|
||
if(!groups.empty() && groups.back().name == raw_frames[i].name)
|
||
{
|
||
groups.back().count++;
|
||
continue;
|
||
}
|
||
Group group;
|
||
group.name = raw_frames[i].name;
|
||
group.address = raw_frames[i].address;
|
||
group.first = i;
|
||
group.count = 1;
|
||
groups.push_back(group);
|
||
}
|
||
|
||
auto group_line = [](const Group& g) {
|
||
std::string line;
|
||
if(g.count == 1)
|
||
{
|
||
line = "#" + std::to_string(g.first);
|
||
if(!g.address.empty())
|
||
line += " " + g.address;
|
||
line += " " + g.name;
|
||
}
|
||
else
|
||
{
|
||
line = "#" + std::to_string(g.first) + "..#" + std::to_string(g.first + g.count - 1)
|
||
+ " " + g.name + " ×" + std::to_string(g.count);
|
||
}
|
||
return(line);
|
||
};
|
||
|
||
if(groups.size() <= max_frame_lines)
|
||
{
|
||
for(auto& g : groups)
|
||
summary.frames.push_back(group_line(g));
|
||
}
|
||
else
|
||
{
|
||
// keep the top of the stack and the outermost group
|
||
for(size_t i = 0; i + 2 < max_frame_lines && i < groups.size(); i++)
|
||
summary.frames.push_back(group_line(groups[i]));
|
||
summary.frames.push_back("... " + std::to_string(groups.size() - (max_frame_lines - 1)) + " more frame groups ...");
|
||
summary.frames.push_back(group_line(groups.back()));
|
||
}
|
||
|
||
return(summary);
|
||
}
|
||
|
||
inline std::string wasm_trace_format(const WasmTraceSummary& summary)
|
||
{
|
||
std::string out = summary.cause;
|
||
if(!summary.detail.empty())
|
||
out += "\n " + summary.detail;
|
||
if(!summary.frames.empty())
|
||
{
|
||
out += "\nbacktrace (" + std::to_string(summary.total_frames) + " frame" + (summary.total_frames == 1 ? "" : "s") + "):";
|
||
for(auto& line : summary.frames)
|
||
out += "\n " + line;
|
||
}
|
||
return(out);
|
||
}
|
||
|
||
inline std::string wasm_trace_collapse(const std::string& message, size_t max_frame_lines = 12)
|
||
{
|
||
return(wasm_trace_format(wasm_trace_summarize(message, max_frame_lines)));
|
||
}
|