Resolve wasm traps to unit source
This commit is contained in:
+7
-1
@@ -46,6 +46,8 @@ UCE also requires two non-vendored dependencies. WASI SDK is load-bearing at run
|
||||
- `/opt/wasi-sdk/bin/clang++`
|
||||
- `/opt/wasi-sdk/bin/wasm-ld`
|
||||
- `/opt/wasi-sdk/bin/llvm-objcopy`
|
||||
- `/opt/wasi-sdk/bin/llvm-nm`
|
||||
- `/opt/wasi-sdk/bin/llvm-dwarfdump`
|
||||
|
||||
You can use different install locations by setting environment variables before building and in the systemd service environment:
|
||||
|
||||
@@ -100,6 +102,8 @@ The expected directories are:
|
||||
/opt/wasi-sdk/bin/clang++
|
||||
/opt/wasi-sdk/bin/wasm-ld
|
||||
/opt/wasi-sdk/bin/llvm-objcopy
|
||||
/opt/wasi-sdk/bin/llvm-nm
|
||||
/opt/wasi-sdk/bin/llvm-dwarfdump
|
||||
```
|
||||
|
||||
Install the WASI SDK:
|
||||
@@ -135,6 +139,8 @@ test -f /opt/wasmtime/lib/libwasmtime.so
|
||||
/opt/wasi-sdk/bin/clang++ --version
|
||||
/opt/wasi-sdk/bin/wasm-ld --version
|
||||
/opt/wasi-sdk/bin/llvm-objcopy --version
|
||||
/opt/wasi-sdk/bin/llvm-nm --version
|
||||
/opt/wasi-sdk/bin/llvm-dwarfdump --version
|
||||
```
|
||||
|
||||
If your paths differ, export the variables for manual builds:
|
||||
@@ -687,7 +693,7 @@ Common compile footguns:
|
||||
|
||||
- `WASM_COMPILE_SCRIPT` is unset or points at a removed script such as `scripts/compile`; set it to `scripts/compile_wasm_unit`.
|
||||
- `scripts/check_unit_wasm.py` is missing or not executable; `scripts/compile_wasm_unit` calls it after linking each unit.
|
||||
- `WASI_SDK` does not point at the pinned tree with `clang++`, `wasm-ld`, `llvm-objcopy`, and `llvm-nm`; run `scripts/install_wasi_sdk.sh --check-only`.
|
||||
- `WASI_SDK` does not point at the pinned tree with `clang++`, `wasm-ld`, `llvm-objcopy`, `llvm-nm`, and `llvm-dwarfdump`; run `scripts/install_wasi_sdk.sh --check-only`.
|
||||
- `WASMTIME_HOME` does not point at a tree with Wasmtime headers and `libwasmtime.so`.
|
||||
- A previous failed compile left stale `.compile.txt`, `.wasm-check.txt`, or partial `.wasm` files under `BIN_DIRECTORY`.
|
||||
|
||||
|
||||
@@ -31,9 +31,13 @@ UCE expects these executables on each deployment host:
|
||||
/opt/wasi-sdk/bin/wasm-ld
|
||||
/opt/wasi-sdk/bin/llvm-objcopy
|
||||
/opt/wasi-sdk/bin/llvm-nm
|
||||
/opt/wasi-sdk/bin/llvm-dwarfdump
|
||||
```
|
||||
|
||||
`llvm-nm` is used by `scripts/check_unit_wasm.py`, which is called by `scripts/compile_wasm_unit` after linking each unit.
|
||||
`llvm-dwarfdump` is used at unit compile time to extract the compact, out-of-band
|
||||
source map before debug sections are stripped from the runtime artifact. The
|
||||
map is consulted only on a wasm trap.
|
||||
|
||||
## Upgrade policy
|
||||
|
||||
|
||||
@@ -222,8 +222,8 @@ Freshness still stats every distinct source on every entry check. Exact repeated
|
||||
load paths are deduplicated before canonicalization, while distinct aliases are
|
||||
resolved independently so symlink retargets remain immediately visible.
|
||||
When a current serialized module exists, the worker scans wasm section headers
|
||||
and reads only `dylink.0` and `uce.abi`; it does not fault the multi-megabyte code
|
||||
and data bodies into every new worker. A missing/stale/invalid serialized module
|
||||
and reads only `dylink.0`, `uce.abi`, and the tiny `uce.module` identity; it does
|
||||
not fault the code and data bodies into every new worker. A missing/stale/invalid serialized module
|
||||
still reads, validates, compiles, and republishes the complete wasm artifact.
|
||||
The proactive compiler also creates that serialization immediately after source
|
||||
compilation, keeping first-worker native compilation off the request path.
|
||||
@@ -257,6 +257,19 @@ intentional unreadable-source regression request as an expected source-read
|
||||
failure, keeping production log scans focused on real runtime failures while
|
||||
preserving the failing compile result and diagnostic artifacts.
|
||||
|
||||
Unit linking retains DWARF only long enough for
|
||||
`scripts/build_unit_source_map.py` to extract a compact address/file/line table.
|
||||
The published `.wasm` is debug-stripped and the table is stored beside it as
|
||||
`.wasm.source-map`, keyed to the exact temporary module identity recorded in
|
||||
the wasm's `uce.module` custom section. Normal module loading never reads this
|
||||
sidecar. On a Wasmtime trap, the worker uses structured frame module offsets to
|
||||
load only the matching map and appends source locations to the error. A missing,
|
||||
stale, or malformed map is deliberately non-fatal: the ordinary named wasm
|
||||
backtrace remains available. Generated C++ uses a `#line` directive naming the
|
||||
original `.uce` file, so application frames resolve to application source rather
|
||||
than the generated cache file. Artifact invalidation removes the wasm, serialized
|
||||
module, and source map together.
|
||||
|
||||
---
|
||||
|
||||
## 4. The workspace runtime
|
||||
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract a compact address-to-source table before a unit's DWARF is stripped."""
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
DIRECTORY = re.compile(r'^include_directories\[\s*(\d+)\] = (".*")$')
|
||||
FILE = re.compile(r'^file_names\[\s*(\d+)\]:$')
|
||||
ROW = re.compile(r'^0x([0-9a-fA-F]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+')
|
||||
|
||||
|
||||
def quoted(value: str) -> str:
|
||||
return ast.literal_eval(value)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--dwarfdump", required=True)
|
||||
parser.add_argument("--wasm", required=True)
|
||||
parser.add_argument("--module", required=True)
|
||||
parser.add_argument("--output", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
text = subprocess.run(
|
||||
[args.dwarfdump, "--debug-line", args.wasm],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
).stdout
|
||||
directories: dict[int, str] = {}
|
||||
files: dict[int, tuple[str, int]] = {}
|
||||
rows: list[tuple[int, int, int, int]] = []
|
||||
current_file = 0
|
||||
for line in text.splitlines():
|
||||
if match := DIRECTORY.match(line):
|
||||
directories[int(match.group(1))] = quoted(match.group(2))
|
||||
continue
|
||||
if match := FILE.match(line):
|
||||
current_file = int(match.group(1))
|
||||
continue
|
||||
stripped = line.strip()
|
||||
if current_file and stripped.startswith("name: "):
|
||||
files[current_file] = (quoted(stripped[6:]), 0)
|
||||
continue
|
||||
if current_file and stripped.startswith("dir_index: "):
|
||||
name, _ = files[current_file]
|
||||
files[current_file] = (name, int(stripped[11:]))
|
||||
continue
|
||||
if match := ROW.match(line):
|
||||
rows.append(tuple(int(value, 16 if index == 0 else 10) for index, value in enumerate(match.groups())))
|
||||
|
||||
paths: dict[int, str] = {}
|
||||
for file_id, (name, directory_id) in files.items():
|
||||
directory = directories.get(directory_id, "")
|
||||
path = name if os.path.isabs(name) else os.path.normpath(os.path.join(directory, name))
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.abspath(path)
|
||||
if "\t" in path or "\n" in path or "\r" in path:
|
||||
raise ValueError(f"source-map path contains a control character: {path!r}")
|
||||
paths[file_id] = path
|
||||
|
||||
with open(args.output, "w", encoding="utf-8") as output:
|
||||
output.write(f"UCE_SOURCE_MAP_V1\t{args.module}\n")
|
||||
for file_id, path in sorted(paths.items()):
|
||||
output.write(f"F\t{file_id}\t{path}\n")
|
||||
for address, line, column, file_id in rows:
|
||||
output.write(f"L\t{address:x}\t{file_id}\t{line}\t{column}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -182,6 +182,9 @@ def main() -> int:
|
||||
for needle in required:
|
||||
if needle not in abi_text:
|
||||
errors.append(f"uce.abi missing {needle!r}")
|
||||
module_payloads = customs.get("uce.module", [])
|
||||
if not module_payloads or not module_payloads[-1]:
|
||||
errors.append("missing uce.module custom section")
|
||||
export_names = {name for name, _ in exports}
|
||||
forbidden_exports = {"uce_alloc", "uce_free"}
|
||||
for name in sorted(export_names & forbidden_exports):
|
||||
|
||||
@@ -15,7 +15,9 @@ ABI_VERSION=${UCE_UNIT_ABI_VERSION:-6}
|
||||
ROOT=$(pwd)
|
||||
OBJ_FN="$DEST_DIR/$PP_FN.wasm.o"
|
||||
ABI_TMP="$DEST_DIR/$PP_FN.uce-abi.txt"
|
||||
MODULE_TMP="$DEST_DIR/$PP_FN.uce-module.txt"
|
||||
WASM_TMP="$DEST_DIR/$WASM_FN.tmp.$$"
|
||||
MAP_TMP="$DEST_DIR/$WASM_FN.source-map.tmp.$$"
|
||||
PCH_ENABLED=${UCE_WASM_UNIT_PCH:-1}
|
||||
PCH_DIR=${UCE_WASM_PCH_DIR:-/tmp/uce/wasm-w2/pch}
|
||||
COMMON_FLAGS=(
|
||||
@@ -33,7 +35,7 @@ COMMON_FLAGS=(
|
||||
-DPLATFORM_NAME=\"wasm32-wasip1\"
|
||||
)
|
||||
|
||||
if [ ! -x "$SDK/bin/clang++" ] || [ ! -x "$SDK/bin/wasm-ld" ] || [ ! -x "$SDK/bin/llvm-objcopy" ]; then
|
||||
if [ ! -x "$SDK/bin/clang++" ] || [ ! -x "$SDK/bin/wasm-ld" ] || [ ! -x "$SDK/bin/llvm-objcopy" ] || [ ! -x "$SDK/bin/llvm-dwarfdump" ]; then
|
||||
echo "wasi-sdk tools not found; set WASI_SDK" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -45,7 +47,7 @@ PCH_KEY=$(printf '%s\n%s\n%s\n%s\n' "$ABI_VERSION" "$TOOLCHAIN_ID" "$HEADER_HASH
|
||||
PCH_FN="$PCH_DIR/uce_lib-wasm-unit-$PCH_KEY.pch"
|
||||
|
||||
mkdir -p "$DEST_DIR" >/dev/null 2>&1
|
||||
trap 'rm -f "$OBJ_FN" "$ABI_TMP" "$WASM_TMP"' EXIT
|
||||
trap 'rm -f "$OBJ_FN" "$ABI_TMP" "$MODULE_TMP" "$WASM_TMP" "$MAP_TMP"' EXIT
|
||||
|
||||
build_pch_if_needed() {
|
||||
if [ "$PCH_ENABLED" = "0" ]; then
|
||||
@@ -67,6 +69,7 @@ unit_abi_version=$ABI_VERSION
|
||||
toolchain=$TOOLCHAIN_ID
|
||||
source=$SRC_FN
|
||||
EOF
|
||||
printf '%s' "$(basename "$WASM_TMP")" > "$MODULE_TMP"
|
||||
|
||||
build_pch_if_needed
|
||||
PCH_FLAGS=()
|
||||
@@ -93,9 +96,19 @@ fi
|
||||
--export-if-defined=__uce_once \
|
||||
--export-if-defined=__uce_init
|
||||
|
||||
"$SDK/bin/llvm-objcopy" --strip-debug --add-section=uce.abi="$ABI_TMP" "$WASM_TMP"
|
||||
python3 scripts/build_unit_source_map.py \
|
||||
--dwarfdump "$SDK/bin/llvm-dwarfdump" \
|
||||
--wasm "$WASM_TMP" \
|
||||
--module "$(basename "$WASM_TMP")" \
|
||||
--output "$MAP_TMP"
|
||||
|
||||
"$SDK/bin/llvm-objcopy" --strip-debug \
|
||||
--add-section=uce.abi="$ABI_TMP" \
|
||||
--add-section=uce.module="$MODULE_TMP" \
|
||||
"$WASM_TMP"
|
||||
|
||||
python3 scripts/check_unit_wasm.py "$WASM_TMP" --abi-version "$ABI_VERSION" --llvm-nm "$SDK/bin/llvm-nm"
|
||||
mv "$MAP_TMP" "$DEST_DIR/$WASM_FN.source-map"
|
||||
mv "$WASM_TMP" "$DEST_DIR/$WASM_FN"
|
||||
|
||||
rm -f "$OBJ_FN" "$ABI_TMP"
|
||||
rm -f "$OBJ_FN" "$ABI_TMP" "$MODULE_TMP"
|
||||
|
||||
@@ -53,7 +53,7 @@ require_command() {
|
||||
|
||||
verify_tree() {
|
||||
local root="$1"
|
||||
for tool in clang++ wasm-ld llvm-objcopy llvm-nm; do
|
||||
for tool in clang++ wasm-ld llvm-objcopy llvm-nm llvm-dwarfdump; do
|
||||
if [[ ! -x "$root/bin/$tool" ]]; then
|
||||
echo "Missing WASI SDK tool: $root/bin/$tool" >&2
|
||||
return 1
|
||||
|
||||
@@ -77,5 +77,6 @@ if [[ "$action" == "run" ]]; then
|
||||
scripts/test_raw_http_request_log.sh
|
||||
scripts/test_component_resolution_ttl.sh
|
||||
scripts/test_unit_export_surface.sh
|
||||
scripts/test_wasm_source_locations.sh
|
||||
scripts/test_socket_activation.sh
|
||||
fi
|
||||
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
|
||||
entry_wasm="$artifact_dir/entry.uce.wasm"
|
||||
named_wasm="$artifact_dir/named.uce.wasm"
|
||||
python3 - "$entry_wasm" "$named_wasm" <<'PY'
|
||||
python3 - "$entry_wasm" "$named_wasm" "$absolute_source_dir" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -60,9 +60,10 @@ cases = [
|
||||
(Path(sys.argv[1]), {"__wasm_call_ctors", "__uce_set_current_request", "__uce_cli", "visibility_shared"}, b"uce-private-unused-marker-8f61d2"),
|
||||
(Path(sys.argv[2]), {"__wasm_call_ctors", "__uce_set_current_request", "__uce_component_NAMED", "named_shared"}, b"uce-named-unused-marker-4ae973"),
|
||||
]
|
||||
source_dir = Path(sys.argv[3])
|
||||
for path, allowed, unused_marker in cases:
|
||||
data = path.read_bytes()
|
||||
_, imports, exports = collect(path)
|
||||
customs, imports, exports = collect(path)
|
||||
names = {name for name, _ in exports}
|
||||
unexpected = names - allowed
|
||||
missing = allowed - names
|
||||
@@ -74,6 +75,18 @@ for path, allowed, unused_marker in cases:
|
||||
raise SystemExit(f"{path}: retained {len(imports)} imports (expected fewer than 40)")
|
||||
if path.stat().st_size >= 1024 * 1024:
|
||||
raise SystemExit(f"{path}: artifact is {path.stat().st_size} bytes (expected under 1 MiB)")
|
||||
source_map = Path(str(path) + ".source-map")
|
||||
if not source_map.is_file() or source_map.stat().st_size >= 256 * 1024:
|
||||
raise SystemExit(f"{path}: missing or oversized source map")
|
||||
module = customs["uce.module"][-1].decode()
|
||||
lines = source_map.read_text().splitlines()
|
||||
if not lines or lines[0] != f"UCE_SOURCE_MAP_V1\t{module}":
|
||||
raise SystemExit(f"{path}: source map does not match wasm module identity")
|
||||
expected_source = str(source_dir / path.name.removesuffix(".wasm"))
|
||||
if not any(line.startswith("F\t") and line.endswith("\t" + expected_source) for line in lines):
|
||||
raise SystemExit(f"{path}: source map does not identify {expected_source}")
|
||||
if not any(line.startswith("L\t") for line in lines):
|
||||
raise SystemExit(f"{path}: source map has no address rows")
|
||||
PY
|
||||
|
||||
echo "unit export surface passed"
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
test_name="wasm-source-location-test-$$"
|
||||
site_directory="${UCE_TEST_SITE_DIRECTORY:-site}"
|
||||
bin_directory="${UCE_TEST_BIN_DIRECTORY:-/tmp/uce/work}"
|
||||
if [[ -r /etc/uce/settings.cfg ]]; then
|
||||
configured_site_directory=$(awk -F= '/^[[:space:]]*HTTP_DOCUMENT_ROOT[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
|
||||
configured_bin_directory=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
|
||||
[[ -n "${UCE_TEST_SITE_DIRECTORY:-}" ]] || site_directory="${configured_site_directory:-$site_directory}"
|
||||
[[ -n "${UCE_TEST_BIN_DIRECTORY:-}" ]] || bin_directory="${configured_bin_directory:-$bin_directory}"
|
||||
fi
|
||||
source_dir="$site_directory/$test_name"
|
||||
absolute_source_dir=""
|
||||
artifact_dir=""
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$source_dir"
|
||||
[[ -z "$artifact_dir" ]] || rm -rf "$artifact_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
mkdir -p "$source_dir"
|
||||
absolute_source_dir=$(realpath "$source_dir")
|
||||
artifact_dir="$bin_directory$absolute_source_dir"
|
||||
printf '%s\n' 'CLI(Request& context) { __builtin_trap(); }' >"$source_dir/entry.uce"
|
||||
|
||||
set +e
|
||||
output=$(scripts/uce-cli "/$test_name/entry.uce" 2>&1)
|
||||
status=$?
|
||||
set -e
|
||||
if [[ $status -eq 0 ]]; then
|
||||
echo "trapping wasm unit unexpectedly succeeded" >&2
|
||||
exit 1
|
||||
fi
|
||||
expected="$absolute_source_dir/entry.uce:1:25"
|
||||
if [[ "$output" != *'wasm `unreachable` instruction executed'* || "$output" != *"source locations:"* || "$output" != *"$expected"* ]]; then
|
||||
echo "wasm trap omitted its source location:" >&2
|
||||
echo "$output" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm "$artifact_dir/entry.uce.wasm.source-map"
|
||||
set +e
|
||||
without_map=$(scripts/uce-cli "/$test_name/entry.uce" 2>&1)
|
||||
status=$?
|
||||
set -e
|
||||
if [[ $status -eq 0 || "$without_map" != *'wasm `unreachable` instruction executed'* || "$without_map" == *"source locations:"* ]]; then
|
||||
echo "missing-map trap fallback failed:" >&2
|
||||
echo "$without_map" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "wasm source locations passed"
|
||||
@@ -497,8 +497,8 @@ String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* s
|
||||
"#include \"uce_lib.h\" \n"+
|
||||
file_get_contents(
|
||||
context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]
|
||||
)+
|
||||
"#line 1\n";
|
||||
)+
|
||||
"#line 1 " + json_escape(su->file_name) + "\n";
|
||||
CompilerCodeState code_state;
|
||||
String current_line = "";
|
||||
String literal_buffer = "";
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
const u64 UCE_UNIT_ABI_VERSION = 9;
|
||||
const u64 UCE_UNIT_ABI_VERSION = 10;
|
||||
|
||||
struct SharedUnitFilesystemState
|
||||
{
|
||||
@@ -294,10 +294,16 @@ String compiler_cached_wasm_path(String wasm_path)
|
||||
return(wasm_path + ".cwasm");
|
||||
}
|
||||
|
||||
String compiler_source_map_path(String wasm_path)
|
||||
{
|
||||
return(wasm_path + ".source-map");
|
||||
}
|
||||
|
||||
void compiler_unlink_unit_wasm_artifacts(SharedUnit* su)
|
||||
{
|
||||
file_unlink(su->wasm_name);
|
||||
file_unlink(compiler_cached_wasm_path(su->wasm_name));
|
||||
file_unlink(compiler_source_map_path(su->wasm_name));
|
||||
}
|
||||
|
||||
String compiler_wasm_compile_script(Request* context)
|
||||
@@ -937,7 +943,7 @@ void compile_shared_unit(Request* context, SharedUnit* su)
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->wasm_check_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su));
|
||||
file_unlink(su->wasm_name);
|
||||
compiler_unlink_unit_wasm_artifacts(su);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", su->compiler_messages);
|
||||
printf("%s \n", compiler_format_source_read_failure(context, su, su->compiler_messages).c_str());
|
||||
compiler_mark_source_generation(context);
|
||||
|
||||
+136
-3
@@ -69,9 +69,24 @@ struct WasmAbiInfo
|
||||
{
|
||||
u32 version = 0;
|
||||
String toolchain;
|
||||
String module_name;
|
||||
bool found = false;
|
||||
};
|
||||
|
||||
struct WasmSourceMap
|
||||
{
|
||||
struct Row
|
||||
{
|
||||
u64 address = 0;
|
||||
u32 file = 0;
|
||||
u32 line = 0;
|
||||
u32 column = 0;
|
||||
};
|
||||
String module_name;
|
||||
std::map<u32, String> files;
|
||||
std::vector<Row> rows;
|
||||
};
|
||||
|
||||
enum class WasmUnitImportKind
|
||||
{
|
||||
Memory,
|
||||
@@ -725,6 +740,8 @@ static bool wasm_parse_sections(const std::vector<u8>& bytes, WasmDylinkInfo& dy
|
||||
line_start = line_end + 1;
|
||||
}
|
||||
}
|
||||
else if(name == "uce.module")
|
||||
abi.module_name.assign((const char*)bytes.data() + cursor, end - cursor);
|
||||
}
|
||||
pos = end;
|
||||
}
|
||||
@@ -748,6 +765,53 @@ static bool wasm_read_file(const String& path, std::vector<u8>& out)
|
||||
return((bool)in);
|
||||
}
|
||||
|
||||
static bool wasm_source_map_load(const String& path, WasmSourceMap& map)
|
||||
{
|
||||
std::ifstream input(path);
|
||||
if(!input)
|
||||
return(false);
|
||||
String line;
|
||||
if(!std::getline(input, line) || line.rfind("UCE_SOURCE_MAP_V1\t", 0) != 0)
|
||||
return(false);
|
||||
map.module_name = line.substr(18);
|
||||
while(std::getline(input, line))
|
||||
{
|
||||
auto fields = split(line, "\t");
|
||||
if(fields.size() == 3 && fields[0] == "F")
|
||||
map.files[(u32)strtoul(fields[1].c_str(), 0, 10)] = fields[2];
|
||||
else if(fields.size() == 5 && fields[0] == "L")
|
||||
{
|
||||
WasmSourceMap::Row row;
|
||||
row.address = strtoull(fields[1].c_str(), 0, 16);
|
||||
row.file = (u32)strtoul(fields[2].c_str(), 0, 10);
|
||||
row.line = (u32)strtoul(fields[3].c_str(), 0, 10);
|
||||
row.column = (u32)strtoul(fields[4].c_str(), 0, 10);
|
||||
map.rows.push_back(row);
|
||||
}
|
||||
}
|
||||
return(!map.rows.empty());
|
||||
}
|
||||
|
||||
static String wasm_source_map_lookup(const WasmSourceMap& map, u64 address)
|
||||
{
|
||||
const WasmSourceMap::Row* found = 0;
|
||||
for(auto& row : map.rows)
|
||||
{
|
||||
if(row.address > address)
|
||||
break;
|
||||
found = &row;
|
||||
}
|
||||
if(!found || found->line == 0)
|
||||
return("");
|
||||
auto file = map.files.find(found->file);
|
||||
if(file == map.files.end())
|
||||
return("");
|
||||
String result = file->second + ":" + std::to_string(found->line);
|
||||
if(found->column)
|
||||
result += ":" + std::to_string(found->column);
|
||||
return(result);
|
||||
}
|
||||
|
||||
static bool wasm_pread_all(int fd, u64 offset, u8* out, size_t size, u64& bytes_read)
|
||||
{
|
||||
size_t done = 0;
|
||||
@@ -858,7 +922,7 @@ static bool wasm_read_metadata_file(const String& path, std::vector<u8>& metadat
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(name == "dylink.0" || name == "uce.abi")
|
||||
if(name == "dylink.0" || name == "uce.abi" || name == "uce.module")
|
||||
{
|
||||
if(section_size > 1024 * 1024)
|
||||
{
|
||||
@@ -1771,9 +1835,78 @@ private:
|
||||
return(pos == String::npos ? String("") : path.substr(0, pos));
|
||||
}
|
||||
|
||||
static String trap_text(const wasmtime::TrapError& error)
|
||||
String trap_text(const wasmtime::TrapError& error)
|
||||
{
|
||||
return(wasm_trace_collapse(String(error.message())));
|
||||
String result = wasm_trace_collapse(String(error.message()));
|
||||
struct Frame
|
||||
{
|
||||
String module;
|
||||
String function;
|
||||
u64 offset = 0;
|
||||
};
|
||||
std::vector<Frame> frames;
|
||||
auto collect = [&](const wasmtime::Trace& trace) {
|
||||
if(trace.size() == 0)
|
||||
return;
|
||||
for(auto& frame : trace)
|
||||
{
|
||||
Frame item;
|
||||
if(auto name = frame.module_name())
|
||||
item.module = String(*name);
|
||||
if(auto name = frame.func_name())
|
||||
item.function = wasm_trace_demangle(String(*name));
|
||||
item.offset = frame.module_offset();
|
||||
frames.push_back(item);
|
||||
}
|
||||
};
|
||||
if(auto* trap = std::get_if<wasmtime::Trap>(&error.data))
|
||||
{
|
||||
auto trace = trap->trace();
|
||||
collect(trace);
|
||||
}
|
||||
else if(auto* runtime_error = std::get_if<wasmtime::Error>(&error.data))
|
||||
{
|
||||
auto trace = runtime_error->trace();
|
||||
collect(trace);
|
||||
}
|
||||
|
||||
std::map<String, WasmSourceMap> maps;
|
||||
std::set<String> unavailable;
|
||||
std::vector<String> locations;
|
||||
for(size_t index = 0; index < frames.size() && locations.size() < 12; index++)
|
||||
{
|
||||
const WasmUnitModule* unit = 0;
|
||||
for(auto& loaded : units)
|
||||
if(loaded.mod->abi.module_name == frames[index].module)
|
||||
{
|
||||
unit = loaded.mod.get();
|
||||
break;
|
||||
}
|
||||
if(!unit)
|
||||
continue;
|
||||
String map_path = unit->wasm_path + ".source-map";
|
||||
if(unavailable.find(map_path) != unavailable.end())
|
||||
continue;
|
||||
auto loaded_map = maps.find(map_path);
|
||||
if(loaded_map == maps.end())
|
||||
{
|
||||
WasmSourceMap source_map;
|
||||
if(!wasm_source_map_load(map_path, source_map) || source_map.module_name != frames[index].module)
|
||||
{
|
||||
unavailable.insert(map_path);
|
||||
continue;
|
||||
}
|
||||
loaded_map = maps.emplace(map_path, std::move(source_map)).first;
|
||||
}
|
||||
String location = wasm_source_map_lookup(loaded_map->second, frames[index].offset);
|
||||
if(location == "")
|
||||
continue;
|
||||
String label = frames[index].function == "" ? "wasm function" : frames[index].function;
|
||||
locations.push_back("#" + std::to_string(index) + " " + label + " at " + location);
|
||||
}
|
||||
if(!locations.empty())
|
||||
result += "\nsource locations:\n " + join(locations, "\n ");
|
||||
return(result);
|
||||
}
|
||||
|
||||
// ---- guest memory access (pointer re-derived per call: it moves) ------
|
||||
|
||||
Reference in New Issue
Block a user