blob: 87626078407046f87955dc9008dc78f19e036315 [file] [log] [blame]
/**
* @license
* Copyright 2019 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
var LibraryStackTrace = {
$jsStackTrace: () => new Error().stack.toString(),
$getCallstack__deps: ['$jsStackTrace',
#if ASSERTIONS
'$warnOnce'
#endif
],
$getCallstack__docs: '/** @param {number=} flags */',
$getCallstack: (flags) => {
var callstack = jsStackTrace();
#if ASSERTIONS
if (flags & {{{ cDefs.EM_LOG_C_STACK }}}) {
warnOnce('emscripten_log with EM_LOG_C_STACK no longer has any effect');
}
#endif
// Process all lines:
var lines = callstack.split('\n');
callstack = '';
// Extract components of form:
// ' Object._main@http://server.com:4324:12'
var firefoxRe = new RegExp('\\s*(.*?)@(.*?):([0-9]+):([0-9]+)');
// Extract components of form:
// ' at Object._main (http://server.com/file.html:4324:12)'
var chromeRe = new RegExp('\\s*at (.*?) \\\((.*):(.*):(.*)\\\)');
for (var line of lines) {
var symbolName = '';
var file = '';
var lineno = 0;
var column = 0;
var parts = chromeRe.exec(line);
if (parts?.length == 5) {
symbolName = parts[1];
file = parts[2];
lineno = parts[3];
column = parts[4];
} else {
parts = firefoxRe.exec(line);
if (parts?.length >= 4) {
symbolName = parts[1];
file = parts[2];
lineno = parts[3];
// Old Firefox doesn't carry column information, but in new FF30, it
// is present. See https://bugzilla.mozilla.org/show_bug.cgi?id=762556
column = parts[4]|0;
} else {
// Was not able to extract this line for demangling/sourcemapping
// purposes. Output it as-is.
callstack += line + '\n';
continue;
}
}
// Find the symbols in the callstack that corresponds to the functions that
// report callstack information, and remove everything up to these from the
// output.
if (symbolName == '_emscripten_log' || symbolName == '_emscripten_get_callstack') {
callstack = '';
continue;
}
if ((flags & {{{ cDefs.EM_LOG_C_STACK | cDefs.EM_LOG_JS_STACK }}})) {
if (flags & {{{ cDefs.EM_LOG_NO_PATHS }}}) {
file = file.substring(file.replace(/\\/g, "/").lastIndexOf('/')+1);
}
callstack += ` at ${symbolName} (${file}:${lineno}:${column})\n`;
}
}
// Trim extra whitespace at the end of the output.
callstack = callstack.replace(/\s+$/, '');
return callstack;
},
emscripten_get_callstack__deps: ['$getCallstack', '$lengthBytesUTF8', '$stringToUTF8'],
emscripten_get_callstack: (flags, str, maxbytes) => {
var callstack = getCallstack(flags);
// User can query the required amount of bytes to hold the callstack.
if (!str || maxbytes <= 0) {
return lengthBytesUTF8(callstack)+1;
}
// Output callstack string as C string to HEAP.
var bytesWrittenExcludingNull = stringToUTF8(callstack, str, maxbytes);
// Return number of bytes written, including null.
return bytesWrittenExcludingNull+1;
},
// Generates a representation of the program counter from a line of stack trace.
// The exact return value depends in whether we are running WASM or JS, and whether
// the engine supports offsets into WASM. See the function body for details.
$convertFrameToPC__docs: '/** @returns {number} */',
$convertFrameToPC__internal: true,
$convertFrameToPC: (frame) => {
var match;
if (match = /\bwasm-function\[\d+\]:(0x[0-9a-f]+)/.exec(frame)) {
// some engines give the binary offset directly, so we use that as return address
return +match[1];
} else if (match = /\bwasm-function\[(\d+)\]:(\d+)/.exec(frame)) {
// Older versions of v8 give function index and offset in the function,
// so we try using the offset converter. If that doesn't work,
// we pack index and offset into a "return address"
#if !USE_OFFSET_CONVERTER
abort('Legacy backtrace format detected but -sUSE_OFFSET_CONVERTER not present.')
#else
#if ASSERTIONS
assert(wasmOffsetConverter, 'wasmOffsetConverter global not found');
#endif
return wasmOffsetConverter.convert(+match[1], +match[2]);
#endif
} else if (match = /:(\d+):\d+(?:\)|$)/.exec(frame)) {
// If we are in js, we can use the js line number as the "return address".
// This should work for wasm2js. We tag the high bit to distinguish this
// from wasm addresses.
return 0x80000000 | +match[1];
}
// return 0 if we can't find any
return 0;
},
// Returns a representation of a call site of the caller of this function, in a manner
// similar to __builtin_return_address. If level is 0, we return the call site of the
// caller of this function.
emscripten_return_address__deps: ['$convertFrameToPC', '$jsStackTrace'],
emscripten_return_address: (level) => {
var callstack = jsStackTrace().split('\n');
if (callstack[0] == 'Error') {
callstack.shift();
}
// skip this function and the caller to get caller's return address
#if MEMORY64
// MEMORY64 injects and extra wrapper within emscripten_return_address
// to handle BigInt conversions.
var caller = callstack[level + 4];
#else
var caller = callstack[level + 3];
#endif
return convertFrameToPC(caller);
},
$UNWIND_CACHE: {},
// This function pulls the JavaScript stack trace and updates UNWIND_CACHE so
// that our representation of the program counter is mapped to the line of the
// stack trace for every line in the stack trace. This allows
// emscripten_pc_get_* to lookup the line of the stack trace from the PC and
// return meaningful information.
//
// Additionally, it saves a copy of the entire stack trace and the return
// address of the caller. This is because there are two common forms of a
// stack trace. The first form starts the stack trace at the caller of the
// function requesting a stack trace. In this case, the function can simply
// walk down the stack from the return address using emscripten_return_address
// with increasing values for level. The second form starts the stack trace
// at the current function. This requires a helper function to get the program
// counter. This helper function will return the return address. This is the
// program counter at the call site. But there is a problem: when calling into
// code that performs stack unwinding, the program counter has changed since
// execution continued from calling the helper function. So we can't just walk
// down the stack and expect to see the PC value we got. By caching the call
// stack, we can call emscripten_stack_unwind with the PC value and use that
// to unwind the cached stack. Naturally, the PC helper function will have to
// call emscripten_stack_snapshot to cache the stack. We also return the
// return address of the caller so the PC helper function does not need to
// call emscripten_return_address, saving a lot of time.
//
// One might expect that a sensible solution is to call the stack unwinder and
// explicitly tell it how many functions to skip from the stack. However,
// existing libraries do not work this way. For example, compiler-rt's
// sanitizer_common library has macros GET_CALLER_PC_BP_SP and
// GET_CURRENT_PC_BP_SP, which obtains the PC value for the two common cases
// stated above, respectively. Then, it passes the PC, BP, SP values along
// until some other function uses them to unwind. On standard machines, the
// stack can be unwound by treating BP as a linked list. This makes PC
// unnecessary to walk the stack, since walking is done with BP, which remains
// valid until the function returns. But on Emscripten, BP does not exist, at
// least in JavaScript frames, so we have to rely on PC values. Therefore, we
// must be able to unwind from a PC value that may no longer be on the
// execution stack, and so we are forced to cache the entire call stack.
emscripten_stack_snapshot__deps: ['$convertFrameToPC', '$UNWIND_CACHE', '$saveInUnwindCache', '$jsStackTrace'],
emscripten_stack_snapshot: () => {
var callstack = jsStackTrace().split('\n');
if (callstack[0] == 'Error') {
callstack.shift();
}
saveInUnwindCache(callstack);
// Caches the stack snapshot so that emscripten_stack_unwind_buffer() can
// unwind from this spot.
UNWIND_CACHE.last_addr = convertFrameToPC(callstack[3]);
UNWIND_CACHE.last_stack = callstack;
return UNWIND_CACHE.last_addr;
},
$saveInUnwindCache__deps: ['$UNWIND_CACHE', '$convertFrameToPC'],
$saveInUnwindCache__internal: true,
$saveInUnwindCache: (callstack) => {
callstack.forEach((frame) => {
var pc = convertFrameToPC(frame);
if (pc) {
UNWIND_CACHE[pc] = frame;
}
});
},
// Unwinds the stack from a cached PC value. See emscripten_stack_snapshot for
// how this is used. addr must be the return address of the last call to
// emscripten_stack_snapshot, or this function will instead use the current
// call stack.
emscripten_stack_unwind_buffer__deps: ['$UNWIND_CACHE', '$saveInUnwindCache', '$convertFrameToPC', '$jsStackTrace'],
emscripten_stack_unwind_buffer: (addr, buffer, count) => {
var stack;
if (UNWIND_CACHE.last_addr == addr) {
stack = UNWIND_CACHE.last_stack;
} else {
stack = jsStackTrace().split('\n');
if (stack[0] == 'Error') {
stack.shift();
}
saveInUnwindCache(stack);
}
var offset = 3;
while (stack[offset] && convertFrameToPC(stack[offset]) != addr) {
++offset;
}
for (var i = 0; i < count && stack[i+offset]; ++i) {
{{{ makeSetValue('buffer', 'i*4', 'convertFrameToPC(stack[i + offset])', 'i32') }}};
}
return i;
},
// Look up the function name from our stack frame cache with our PC representation.
emscripten_pc_get_function__deps: ['$UNWIND_CACHE', 'free', '$stringToNewUTF8'],
// Don't treat allocation of _emscripten_pc_get_function.ret as a leak
emscripten_pc_get_function__noleakcheck: true,
emscripten_pc_get_function: (pc) => {
var name;
if (pc & 0x80000000) {
// If this is a JavaScript function, try looking it up in the unwind cache.
var frame = UNWIND_CACHE[pc];
if (!frame) return 0;
var match;
if (match = /^\s+at (.*) \(.*\)$/.exec(frame)) {
name = match[1];
} else if (match = /^(.+?)@/.exec(frame)) {
name = match[1];
} else {
return 0;
}
} else {
#if !USE_OFFSET_CONVERTER
abort('Cannot use emscripten_pc_get_function on native functions without -sUSE_OFFSET_CONVERTER');
return 0;
#else
name = wasmOffsetConverter.getName(pc);
#endif
}
_free(_emscripten_pc_get_function.ret ?? 0);
_emscripten_pc_get_function.ret = stringToNewUTF8(name);
return _emscripten_pc_get_function.ret;
},
$convertPCtoSourceLocation__deps: ['$UNWIND_CACHE'],
$convertPCtoSourceLocation: (pc) => {
if (UNWIND_CACHE.last_get_source_pc == pc) return UNWIND_CACHE.last_source;
var match;
var source;
#if LOAD_SOURCE_MAP
if (wasmSourceMap) {
source = wasmSourceMap.lookup(pc);
}
#endif
if (!source) {
var frame = UNWIND_CACHE[pc];
if (!frame) return null;
// Example: at callMain (a.out.js:6335:22)
if (match = /\((.*):(\d+):(\d+)\)$/.exec(frame)) {
source = {file: match[1], line: match[2], column: match[3]};
// Example: [email protected]:1337:42
} else if (match = /@(.*):(\d+):(\d+)/.exec(frame)) {
source = {file: match[1], line: match[2], column: match[3]};
}
}
UNWIND_CACHE.last_get_source_pc = pc;
UNWIND_CACHE.last_source = source;
return source;
},
// Look up the file name from our stack frame cache with our PC representation.
emscripten_pc_get_file__deps: ['$convertPCtoSourceLocation', 'free', '$stringToNewUTF8'],
// Don't treat allocation of _emscripten_pc_get_file.ret as a leak
emscripten_pc_get_file__noleakcheck: true,
emscripten_pc_get_file: (pc) => {
var result = convertPCtoSourceLocation(pc);
if (!result) return 0;
_free(_emscripten_pc_get_file.ret ?? 0);
_emscripten_pc_get_file.ret = stringToNewUTF8(result.file);
return _emscripten_pc_get_file.ret;
},
// Look up the line number from our stack frame cache with our PC representation.
emscripten_pc_get_line__deps: ['$convertPCtoSourceLocation'],
emscripten_pc_get_line: (pc) => {
var result = convertPCtoSourceLocation(pc);
return result ? result.line : 0;
},
// Look up the column number from our stack frame cache with our PC representation.
emscripten_pc_get_column__deps: ['$convertPCtoSourceLocation'],
emscripten_pc_get_column: (pc) => {
var result = convertPCtoSourceLocation(pc);
return result ? result.column || 0 : 0;
},
}
addToLibrary(LibraryStackTrace);