diff --git a/compiler-rt/__trap.c b/compiler-rt/__trap.c new file mode 100644 index 0000000000000..8c714078ae91a --- /dev/null +++ b/compiler-rt/__trap.c @@ -0,0 +1,3 @@ +void __trap() { + __builtin_trap(); +} diff --git a/compiler-rt/emscripten_exception_builtins.c b/compiler-rt/emscripten_exception_builtins.c new file mode 100644 index 0000000000000..97fcb87b73c3a --- /dev/null +++ b/compiler-rt/emscripten_exception_builtins.c @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + * + * Support functions for emscripten setjmp/longjmp and exception handling + * support. References to the things below are generated in the LLVM backend. + * See: https://llvm.org/doxygen/WebAssemblyLowerEmscriptenEHSjLj_8cpp.html + */ + +#include +#include + +thread_local uintptr_t __THREW__ = 0; +thread_local int __threwValue = 0; + +void setThrew(uintptr_t threw, int value) { + if (__THREW__ == 0) { + __THREW__ = threw; + __threwValue = value; + } +} diff --git a/compiler-rt/emscripten_setjmp.c b/compiler-rt/emscripten_setjmp.c new file mode 100644 index 0000000000000..9c4afc3620d81 --- /dev/null +++ b/compiler-rt/emscripten_setjmp.c @@ -0,0 +1,93 @@ +/* +* Copyright 2020 The Emscripten Authors. All rights reserved. +* Emscripten is available under two separate licenses, the MIT license and the +* University of Illinois/NCSA Open Source License. Both these licenses can be +* found in the LICENSE file. +*/ + +#include +#include +#include +#include + +// 0 - Nothing thrown +// 1 - Exception thrown +// Other values - jmpbuf pointer in the case that longjmp was thrown +static uintptr_t setjmpId = 0; + +typedef struct TableEntry { + uintptr_t id; + uint32_t label; +} TableEntry; + +extern void setTempRet0(uint32_t value); +extern void setThrew(uintptr_t threw, int value); + +TableEntry* saveSetjmp(uintptr_t* env, uint32_t label, TableEntry* table, uint32_t size) { + // Not particularly fast: slow table lookup of setjmpId to label. But setjmp + // prevents relooping anyhow, so slowness is to be expected. And typical case + // is 1 setjmp per invocation, or less. + uint32_t i = 0; + setjmpId++; + *env = setjmpId; + while (i < size) { + if (table[i].id == 0) { + table[i].id = setjmpId; + table[i].label = label; + // prepare next slot + table[i + 1].id = 0; + setTempRet0(size); + return table; + } + i++; + } + // grow the table + size *= 2; + table = (TableEntry*)realloc(table, sizeof(TableEntry) * (size +1)); + table = saveSetjmp(env, label, table, size); + setTempRet0(size); // FIXME: unneeded? + return table; +} + +uint32_t testSetjmp(uintptr_t id, TableEntry* table, uint32_t size) { + uint32_t i = 0; + while (i < size) { + uintptr_t curr = table[i].id; + if (curr == 0) break; + if (curr == id) { + return table[i].label; + } + i++; + } + return 0; +} + +#if !defined(__USING_WASM_SJLJ__) + +#include "emscripten_internal.h" + +void emscripten_longjmp(uintptr_t env, int val) { + setThrew(env, val); + _emscripten_throw_longjmp(); +} +#endif + +#ifdef __USING_WASM_SJLJ__ + +struct __WasmLongjmpArgs { + void *env; + int val; +}; + +thread_local struct __WasmLongjmpArgs __wasm_longjmp_args; + +// Wasm EH allows us to throw and catch multiple values, but that requires +// multivalue support in the toolchain, whch is not reliable at the time. +// TODO Consider switching to throwing two values at the same time later. +void __wasm_longjmp(void *env, int val) { + __wasm_longjmp_args.env = env; + __wasm_longjmp_args.val = val; + __builtin_wasm_throw(1, &__wasm_longjmp_args); +} + +#endif diff --git a/compiler-rt/emscripten_tempret.s b/compiler-rt/emscripten_tempret.s new file mode 100644 index 0000000000000..4e3b307a03e72 --- /dev/null +++ b/compiler-rt/emscripten_tempret.s @@ -0,0 +1,26 @@ +.globaltype tempRet0, i32 +tempRet0: + +.globl setTempRet0 +setTempRet0: + .functype setTempRet0 (i32) -> () + local.get 0 + global.set tempRet0 + end_function + +.globl getTempRet0 +getTempRet0: + .functype getTempRet0 () -> (i32) + global.get tempRet0 + end_function + +# These aliases exist solely for LegalizeJSInterface pass in binaryen +# They get exported by emcc and the exports are then removed by the +# binaryen pass +.globl __get_temp_ret +.type __get_temp_ret, @function +__get_temp_ret = getTempRet0 + +.globl __set_temp_ret +.type __set_temp_ret, @function +__set_temp_ret = setTempRet0 diff --git a/compiler-rt/lib/asan/asan_emscripten.cpp b/compiler-rt/lib/asan/asan_emscripten.cpp new file mode 100644 index 0000000000000..90b3c5bbae8ee --- /dev/null +++ b/compiler-rt/lib/asan/asan_emscripten.cpp @@ -0,0 +1,116 @@ +#include "asan_interceptors.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_poisoning.h" +#include "asan_stack.h" +#include "asan_thread.h" +#include "lsan/lsan_common.h" // for CAN_SANITIZE_LEAKS + +#if SANITIZER_EMSCRIPTEN +#include +#include +#include +#include +#include +#define __ATTRP_C11_THREAD ((void*)(uptr)-1) + +namespace __asan { + +void InitializeShadowMemory() { + // Poison the shadow memory of the shadow area at the start of the address + // space. This helps catching null pointer dereference. + FastPoisonShadow(kLowShadowBeg, kLowShadowEnd - kLowShadowBeg, 0xff); + + // Assert that the shadow region is large enough. We don't want to start + // running into the static data region which starts right after the shadow + // region. + uptr max_address = + (__builtin_wasm_memory_size(0) * uint64_t(WASM_PAGE_SIZE)) - 1; + uptr max_shadow_address = MEM_TO_SHADOW(max_address); + // TODO(sbc): In the growable memory case we should really be checking this + // every time we grow. + assert(max_shadow_address <= kLowShadowEnd && "shadow region is too small"); +} + +void AsanCheckDynamicRTPrereqs() {} +void AsanCheckIncompatibleRT() {} +void InitializePlatformInterceptors() {} +void InitializePlatformExceptionHandlers() {} +bool IsSystemHeapAddress (uptr addr) { return false; } + +void *AsanDoesNotSupportStaticLinkage() { + // On Linux, this is some magic that fails linking with -static. + // On Emscripten, we have to do static linking, so we stub this out. + return nullptr; +} + +void InitializeAsanInterceptors() {} + +void FlushUnneededASanShadowMemory(uptr p, uptr size) {} + +extern "C" { +int emscripten_builtin_pthread_create(pthread_t *thread, + const pthread_attr_t *attr, + void *(*callback)(void *), void *arg); +} + +static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { + AsanThread *t = (AsanThread *)arg; + SetCurrentThread(t); + return t->ThreadStart(GetTid()); +} + +INTERCEPTOR(int, pthread_create, pthread_t *thread, + const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg) { + EnsureMainThreadIDIsCorrect(); + // Strict init-order checking is thread-hostile. + if (flags()->strict_init_order) + StopInitOrderChecking(); + GET_STACK_TRACE_THREAD; + int detached = 0; + if (attr && attr != __ATTRP_C11_THREAD) + pthread_attr_getdetachstate(attr, &detached); + + u32 current_tid = GetCurrentTidOrInvalid(); + AsanThread *t = + AsanThread::Create(start_routine, arg, current_tid, &stack, detached); + + int result; + { + // Ignore all allocations made by pthread_create: thread stack/TLS may be + // stored by pthread for future reuse even after thread destruction, and + // the linked list it's stored in doesn't even hold valid pointers to the + // objects, the latter are calculated by obscure pointer arithmetic. +#if CAN_SANITIZE_LEAKS + __lsan::ScopedInterceptorDisabler disabler; +#endif + result = REAL(pthread_create)(thread, attr, asan_thread_start, t); + } + if (result != 0) { + // If the thread didn't start delete the AsanThread to avoid leaking it. + // Note AsanThreadContexts never get destroyed so the AsanThreadContext + // that was just created for the AsanThread is wasted. + t->Destroy(); + } + return result; +} + +} // namespace __asan + +namespace __lsan { + +#ifndef __EMSCRIPTEN_PTHREADS__ +// XXX HACK: Emscripten treats thread_local variables the same as globals in +// non-threaded builds, so a hack was introduced where we skip the allocator +// cache in the common module. Now we have to define this symbol to keep that +// hack working when using LSan as part of ASan without threads. +void GetAllocatorCacheRange(uptr *begin, uptr *end) { + *begin = *end = 0; +} +#endif + +u32 GetCurrentThread() { return __asan::GetCurrentThread()->tid(); } + +} // namespace __lsan + +#endif // SANITIZER_EMSCRIPTEN diff --git a/compiler-rt/lib/asan/asan_mapping_emscripten.h b/compiler-rt/lib/asan/asan_mapping_emscripten.h new file mode 100644 index 0000000000000..59ab527f408b9 --- /dev/null +++ b/compiler-rt/lib/asan/asan_mapping_emscripten.h @@ -0,0 +1,88 @@ +//===-- asan_mapping_emscripten.h -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of AddressSanitizer, an address sanity checker. +// +// Emscripten-specific definitions for ASan memory mapping. +//===----------------------------------------------------------------------===// +#ifndef ASAN_MAPPING_EMSCRIPTEN_H +#define ASAN_MAPPING_EMSCRIPTEN_H + +extern char __global_base; + +#define kLowMemBeg ((uptr) &__global_base) +#define kLowMemEnd ((kLowShadowBeg << ASAN_SHADOW_SCALE) - 1) + +#define kLowShadowBeg 0 +#define kLowShadowEnd ((uptr) &__global_base - 1) + +#define kHighMemBeg 0 + +#define kHighShadowBeg 0 +#define kHighShadowEnd 0 + +#define kMidShadowBeg 0 +#define kMidShadowEnd 0 + +#define kShadowGapBeg (kLowMemEnd + 1) +#define kShadowGapEnd 0xFFFFFFFF + +#define kShadowGap2Beg 0 +#define kShadowGap2End 0 + +#define kShadowGap3Beg 0 +#define kShadowGap3End 0 + +// The first 1/8 of the shadow memory space is shadowing itself. +// This allows attempted accesses into the shadow memory, as well as null +// pointer dereferences, to be detected properly. +// The shadow memory of the shadow memory is poisoned. +#define MEM_TO_SHADOW(mem) ((mem) >> ASAN_SHADOW_SCALE) +#define SHADOW_TO_MEM(mem) ((mem) << ASAN_SHADOW_SCALE) + +namespace __asan { + +static inline bool AddrIsInLowMem(uptr a) { + PROFILE_ASAN_MAPPING(); + return a >= kLowMemBeg && a <= kLowMemEnd; +} + +static inline bool AddrIsInLowShadow(uptr a) { + PROFILE_ASAN_MAPPING(); + return a >= kLowShadowBeg && a <= kLowShadowEnd; +} + +static inline bool AddrIsInMidMem(uptr a) { + PROFILE_ASAN_MAPPING(); + return false; +} + +static inline bool AddrIsInMidShadow(uptr a) { + PROFILE_ASAN_MAPPING(); + return false; +} + +static inline bool AddrIsInHighMem(uptr a) { + PROFILE_ASAN_MAPPING(); + return false; +} + +static inline bool AddrIsInHighShadow(uptr a) { + PROFILE_ASAN_MAPPING(); + return false; +} + +static inline bool AddrIsInShadowGap(uptr a) { + PROFILE_ASAN_MAPPING(); + return a >= kShadowGapBeg; +} + +} // namespace __asan + +#endif // ASAN_MAPPING_EMSCRIPTEN_H diff --git a/compiler-rt/lib/lsan/lsan_common_emscripten.cpp b/compiler-rt/lib/lsan/lsan_common_emscripten.cpp new file mode 100644 index 0000000000000..f2b1301577238 --- /dev/null +++ b/compiler-rt/lib/lsan/lsan_common_emscripten.cpp @@ -0,0 +1,188 @@ +//=-- lsan_common_emscripten.cc--------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of LeakSanitizer. +// Implementation of common leak checking functionality. +// Emscripten-specific code. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" +#include "lsan_common.h" +#include "lsan_thread.h" + +#if CAN_SANITIZE_LEAKS && SANITIZER_EMSCRIPTEN +#include + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_getauxval.h" +#include "sanitizer_common/sanitizer_linux.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_thread_registry.h" + +#define LOG_THREADS(...) \ + do { \ + if (flags()->log_threads) Report(__VA_ARGS__); \ + } while (0) + +namespace __lsan { + +static const char kLinkerName[] = "ld"; + +static char linker_placeholder[sizeof(LoadedModule)] ALIGNED(64); +static LoadedModule *linker = nullptr; + +static bool IsLinker(const LoadedModule& module) { + return false; +} + +thread_local int disable_counter; +bool DisabledInThisThread() { return disable_counter > 0; } +void DisableInThisThread() { disable_counter++; } +void EnableInThisThread() { + if (disable_counter == 0) { + DisableCounterUnderflow(); + } + disable_counter--; +} + +void InitializePlatformSpecificModules() { + ListOfModules modules; + modules.init(); + for (LoadedModule &module : modules) { + if (!IsLinker(module)) + continue; + if (linker == nullptr) { + linker = reinterpret_cast(linker_placeholder); + *linker = module; + module = LoadedModule(); + } else { + VReport(1, "LeakSanitizer: Multiple modules match \"%s\". " + "TLS and other allocations originating from linker might be " + "falsely reported as leaks.\n", kLinkerName); + linker->clear(); + linker = nullptr; + return; + } + } + if (linker == nullptr) { + VReport(1, "LeakSanitizer: Dynamic linker not found. TLS and other " + "allocations originating from linker might be falsely reported " + "as leaks.\n"); + } +} + +extern "C" { + extern char __global_base; + extern char __data_end; +} + +// Scans global variables for heap pointers. +void ProcessGlobalRegions(Frontier *frontier) { + if (!flags()->use_globals) return; + ScanGlobalRange((uptr) &__global_base, (uptr) &__data_end, frontier); +} + +LoadedModule *GetLinker() { return linker; } + +void ProcessPlatformSpecificAllocations(Frontier *frontier) {} + +// While calling Die() here is undefined behavior and can potentially +// cause race conditions, it isn't possible to intercept exit on Emscripten, +// so we have no choice but to call Die() from the atexit handler. +void HandleLeaks() { + if (common_flags()->exitcode) Die(); +} + +void LockStuffAndStopTheWorld(StopTheWorldCallback callback, + CheckForLeaksParam *argument) { + // Currently, on Emscripten this does nothing and just calls the callback. + // This works fine on a single-threaded environment. + LockThreadRegistry(); + LockAllocator(); + StopTheWorld(callback, argument); + UnlockAllocator(); + UnlockThreadRegistry(); +} + +u32 GetCurrentThread(); + +// This is based on ProcessThreads in lsan_common.cc. +// We changed this to be a callback that gets called per thread by +// ThreadRegistry::RunCallbackForEachThreadLocked. +// We do not scan registers or DTLS since we do not have those. +// Finally, we can only obtain the stack pointer for the current thread, +// so we scan the full stack for other threads. +static void ProcessThreadsCallback(ThreadContextBase *tctx, void *arg) { + if (tctx->status != ThreadStatusRunning) + return; + + Frontier *frontier = reinterpret_cast(arg); + tid_t os_id = tctx->os_id; + + uptr stack_begin, stack_end, tls_begin, tls_end, cache_begin, cache_end; + DTLS *dtls; + bool thread_found = GetThreadRangesLocked(os_id, &stack_begin, &stack_end, + &tls_begin, &tls_end, + &cache_begin, &cache_end, &dtls); + if (!thread_found) { + LOG_THREADS("Thread %llu not found in registry.\n", os_id); + return; + } + + if (flags()->use_stacks) { + LOG_THREADS("Stack at %p-%p.\n", (void*)stack_begin, (void*)stack_end); + + // We can't get the SP for other threads to narrow down the range, but we + // we can for the current thread. + if (tctx->tid == GetCurrentThread()) { + uptr sp = (uptr) __builtin_frame_address(0); + if (sp < stack_begin || sp >= stack_end) { + // SP is outside the recorded stack range (e.g. the thread is running a + // signal handler on alternate stack, or swapcontext was used). + // Again, consider the entire stack range to be reachable. + LOG_THREADS("WARNING: stack pointer not in stack range.\n"); + } else { + // Shrink the stack range to ignore out-of-scope values. + stack_begin = sp; + } + } + + ScanRangeForPointers(stack_begin, stack_end, frontier, "STACK", kReachable); + } + + if (flags()->use_tls && tls_begin) { + LOG_THREADS("TLS at %p-%p.\n", (void*)tls_begin, (void*)tls_end); + // If the tls and cache ranges don't overlap, scan full tls range, + // otherwise, only scan the non-overlapping portions + if (cache_begin == cache_end || tls_end < cache_begin || + tls_begin > cache_end) { + ScanRangeForPointers(tls_begin, tls_end, frontier, "TLS", kReachable); + } else { + if (tls_begin < cache_begin) + ScanRangeForPointers(tls_begin, cache_begin, frontier, "TLS", + kReachable); + if (tls_end > cache_end) + ScanRangeForPointers(cache_end, tls_end, frontier, "TLS", kReachable); + } + } +} + +void ProcessThreads(SuspendedThreadsList const& suspended_threads, + Frontier* frontier, + tid_t caller_tid, + uptr caller_sp) { + GetThreadRegistryLocked()->RunCallbackForEachThreadLocked( + ProcessThreadsCallback, frontier); +} + +} // namespace __lsan + +#endif // CAN_SANITIZE_LEAKS && SANITIZER_EMSCRIPTEN diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp new file mode 100644 index 0000000000000..babc6793663a7 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp @@ -0,0 +1,132 @@ +//===-- sanitizer_emscripten.cc -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This provides implementations of some functions in sanitizer_linux_libcdep.c +// on emscripten. We are not using sanitizer_linux_libcdep.c because it contains +// a lot of threading and other code that does not work with emscripten yet, +// so instead, some minimal implementations are provided here so that UBSan can +// work. +//===----------------------------------------------------------------------===// + +#include "sanitizer_platform.h" +#include "sanitizer_common.h" +#include "sanitizer_stoptheworld.h" + +#include +#include + +#if SANITIZER_EMSCRIPTEN + +#include +#include +#include + +#include "emscripten_internal.h" + +namespace __sanitizer { + +void ListOfModules::init() { + modules_.Initialize(2); + + char name[256]; + emscripten_get_module_name(name, 256); + + LoadedModule main_module; + main_module.set(name, 0); + + // Emscripten represents program counters as offsets into WebAssembly + // modules. For JavaScript code, the "program counter" is the line number + // of the JavaScript code with the high bit set. + // Therefore, PC values 0x80000000 and beyond represents JavaScript code. + // As a result, 0x00000000 to 0x7FFFFFFF represents PC values for WASM code. + // We consider WASM code as main_module. + main_module.addAddressRange(0, 0x7FFFFFFF, /*executable*/ true, + /*writable*/ false); + modules_.push_back(main_module); + + // The remaining PC values, 0x80000000 to 0xFFFFFFFF, are JavaScript, + // and we consider it a separate module, js_module. + LoadedModule js_module; + js_module.set("JavaScript", 0x80000000); + js_module.addAddressRange(0x80000000, 0xFFFFFFFF, /*executable*/ true, + /*writable*/ false); + modules_.push_back(js_module); +} + +void ListOfModules::fallbackInit() { clear(); } + +int internal_sigaction(int signum, const void *act, void *oldact) { + return sigaction(signum, (const struct sigaction *)act, + (struct sigaction *)oldact); +} + +uptr internal_mmap(void *addr, uptr length, int prot, int flags, int fd, + u64 offset) { + CHECK(IsAligned(offset, 4096)); + return (uptr)emscripten_builtin_mmap(addr, length, prot, flags, fd, offset / 4096); +} + +uptr internal_munmap(void *addr, uptr length) { + return emscripten_builtin_munmap(addr, length); +} + +void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, + uptr *stack_bottom) { + *stack_top = emscripten_stack_get_base(); + *stack_bottom = emscripten_stack_get_end(); +} + +char *fake_argv[] = {0}; +char *fake_envp[] = {0}; + +char **GetArgv() { + return fake_argv; +} + +char **GetEnviron() { + return fake_envp; +} + +uptr GetTlsSize() { + return 0; +} + +void InitTlsSize() {} + +void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size) { + uptr stk_top; + GetThreadStackTopAndBottom(true, &stk_top, stk_addr); + *stk_size = stk_top - *stk_addr; +#ifdef __EMSCRIPTEN_PTHREADS__ + *tls_addr = (uptr) __builtin_wasm_tls_base(); + *tls_size = __builtin_wasm_tls_size(); +#else + *tls_addr = *tls_size = 0; +#endif +} + +class SuspendedThreadsListEmscripten final : public SuspendedThreadsList {}; + +void StopTheWorld(StopTheWorldCallback callback, void *argument) { + // TODO: have some workable alternative, since we can't just fork and suspend + // the parent process. This does not matter when single thread. + callback(SuspendedThreadsListEmscripten(), argument); +} + +void InitializePlatformCommonFlags(CommonFlags *cf) {} + +u64 MonotonicNanoTime() { + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (u64)ts.tv_sec * (1000ULL * 1000 * 1000) + ts.tv_nsec; +} + +} // namespace __sanitizer + +#endif diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_emscripten.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_emscripten.cpp new file mode 100644 index 0000000000000..9e25ef311aa60 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_emscripten.cpp @@ -0,0 +1,41 @@ +//===-- sanitizer_stacktrace_emscripten.cc --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +// +// Implementation of fast stack unwinding for Emscripten. +//===----------------------------------------------------------------------===// + +#ifdef __EMSCRIPTEN__ + +#include "sanitizer_common.h" +#include "sanitizer_stacktrace.h" + +#include "emscripten_internal.h" + +namespace __sanitizer { + +bool StackTrace::snapshot_stack = true; + +uptr StackTrace::GetCurrentPc() { + return snapshot_stack ? emscripten_stack_snapshot() : 0; +} + +void BufferedStackTrace::UnwindFast(uptr pc, uptr bp, uptr stack_top, + uptr stack_bottom, u32 max_depth) { + max_depth = Min(max_depth, kStackTraceMax); + size = emscripten_stack_unwind_buffer(pc, trace_buffer, max_depth); + trace_buffer[0] = pc; + size = Max(size, 1U); +} + +} // namespace __sanitizer + +#endif // __EMSCRIPTEN__ diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_emscripten.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_emscripten.cpp new file mode 100644 index 0000000000000..45d8d5c1403df --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_emscripten.cpp @@ -0,0 +1,80 @@ +//===-- sanitizer_symbolizer_emscripten.cc --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +// Emscripten-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// + +#include "sanitizer_platform.h" + +#if SANITIZER_EMSCRIPTEN + +#include "sanitizer_symbolizer_internal.h" +#include "emscripten_internal.h" + +namespace __sanitizer { + +class EmscriptenSymbolizerTool : public SymbolizerTool { + public: + bool SymbolizePC(uptr addr, SymbolizedStack *stack) override; + bool SymbolizeData(uptr addr, DataInfo *info) override { + return false; + } + const char *Demangle(const char *name) override { + return name; + } +}; + +bool EmscriptenSymbolizerTool::SymbolizePC(uptr addr, SymbolizedStack *frame) { + const char *func_name = emscripten_pc_get_function(addr); + if (func_name) { + frame->info.function = internal_strdup(func_name); + frame->info.function_offset = addr; + } + + const char *file_name = emscripten_pc_get_file(addr); + if (file_name) { + frame->info.file = internal_strdup(file_name); + frame->info.line = emscripten_pc_get_line(addr); + frame->info.column = emscripten_pc_get_column(addr); + } + + return !!func_name; +} + +static void ChooseSymbolizerTools(IntrusiveList *list, + LowLevelAllocator *allocator) { + if (!common_flags()->symbolize) { + VReport(2, "Symbolizer is disabled.\n"); + return; + } + + list->push_back(new(*allocator) EmscriptenSymbolizerTool()); +} + +const char *Symbolizer::PlatformDemangle(const char *name) { + return name; +} + +Symbolizer *Symbolizer::PlatformInit() { + IntrusiveList list; + list.clear(); + ChooseSymbolizerTools(&list, &symbolizer_allocator_); + + return new(symbolizer_allocator_) Symbolizer(list); +} + +void Symbolizer::LateInitialize() { + Symbolizer::GetOrInit(); +} + +} // namespace __sanitizer + +#endif // SANITIZER_EMSCRIPTEN diff --git a/compiler-rt/stack_limits.S b/compiler-rt/stack_limits.S new file mode 100644 index 0000000000000..1171afa8174fc --- /dev/null +++ b/compiler-rt/stack_limits.S @@ -0,0 +1,135 @@ +.globl emscripten_stack_init +.globl emscripten_stack_set_limits +.globl emscripten_stack_get_free +.globl emscripten_stack_get_base +.globl emscripten_stack_get_end + +#ifdef __wasm64__ +#define PTR i64 +#define ALIGN 3 +#define PTRSTORE .int64 +#else +#define PTR i32 +#define ALIGN 2 +#define PTRSTORE .int32 +#endif + +.globaltype __stack_pointer, PTR + +# TODO(sbc): It would be nice if these we initialized directly +# using PTR.const rather than using the `emscripten_stack_init` +.globaltype __stack_end, PTR +__stack_end: +.globaltype __stack_base, PTR +__stack_base: + +emscripten_stack_get_base: + .functype emscripten_stack_get_base () -> (PTR) + global.get __stack_base + end_function + +emscripten_stack_get_end: + .functype emscripten_stack_get_end () -> (PTR) + global.get __stack_end + end_function + +emscripten_stack_init: + # Initialize __stack_end and __stack_base. + # This must be called before emscripten_stack_get_end, + # emscripten_stack_get_base, or emscripten_stack_get_free are called + .functype emscripten_stack_init () -> () + + # What llvm calls __stack_high is the high address from where is grows + # downwards. We call this the stack base here in emscripten. +#ifdef __PIC__ + global.get __stack_high@GOT +#else + PTR.const __stack_high +#endif + global.set __stack_base + + # What llvm calls __stack_low is that end of the stack +#ifdef __PIC__ + global.get __stack_low@GOT +#else + PTR.const __stack_low +#endif + # Align up to 16 bytes + PTR.const 0xf + PTR.add + PTR.const -0x10 + PTR.and + global.set __stack_end + + end_function + +emscripten_stack_set_limits: + .functype emscripten_stack_set_limits (PTR, PTR) -> () + local.get 0 + global.set __stack_base + local.get 1 + global.set __stack_end + end_function + +emscripten_stack_get_free: + .functype emscripten_stack_get_free () -> (PTR) + global.get __stack_pointer + global.get __stack_end + PTR.sub + end_function + +#ifdef __EMSCRIPTEN_WASM_WORKERS__ +# TODO: Relocate the following to its own file wasm_worker.S, but need to figure out how to reference +# __stack_base and __stack_end globals from a separate file as externs in order for that to work. +.globl emscripten_wasm_worker_initialize +emscripten_wasm_worker_initialize: + .functype emscripten_wasm_worker_initialize (PTR /*stackLowestAddress*/, i32 /*stackSize*/) -> () + + // __stack_end = stackLowestAddress + (__builtin_wasm_tls_size() + 15) & -16; + local.get 0 + .globaltype __tls_size, PTR, immutable + global.get __tls_size + PTR.add + PTR.const 0xf + PTR.add + PTR.const -0x10 + PTR.and + global.set __stack_end + + // __stack_base = stackLowestAddress + stackSize; + local.get 0 + local.get 1 +#ifdef __wasm64__ + i64.extend_i32_u +#endif + PTR.add + global.set __stack_base + +// TODO: We'd like to do this here to avoid JS side calls to __set_stack_limits. +// (or even better, we'd like to avoid duplicate versions of the stack variables) +// See https://github.com/emscripten-core/emscripten/issues/16496 +// global.get __stack_base +// global.get __stack_end +// .functype __set_stack_limits (PTR, PTR) -> () +// call __set_stack_limits + + // __wasm_init_tls(stackLowestAddress); + local.get 0 + .functype __wasm_init_tls (PTR) -> () + call __wasm_init_tls + + // N.b. The function __wasm_init_tls above does not need + // __stack_pointer initialized, and it destroys the value it was set to. + // So we must initialize __stack_pointer only *after* completing __wasm_init_tls: + + // __stack_pointer = __stack_base; + global.get __stack_base + global.set __stack_pointer + + end_function +#endif + +# Add emscripten_stack_init to static ctors +.section .init_array.1,"",@ +.p2align ALIGN +PTRSTORE emscripten_stack_init diff --git a/compiler-rt/stack_ops.S b/compiler-rt/stack_ops.S new file mode 100644 index 0000000000000..b56b1dd524d69 --- /dev/null +++ b/compiler-rt/stack_ops.S @@ -0,0 +1,47 @@ +.globl stackSave +.globl stackRestore +.globl stackAlloc +.globl emscripten_stack_get_current + +#ifdef __wasm64__ +#define PTR i64 +#define MASK 0xfffffffffffffff0 +#else +#define PTR i32 +#define MASK 0xfffffff0 +#endif + +.globaltype __stack_pointer, PTR + +stackSave: + .functype stackSave() -> (PTR) + global.get __stack_pointer + end_function + +stackRestore: + .functype stackRestore(PTR) -> () + local.get 0 + global.set __stack_pointer + end_function + +stackAlloc: + .functype stackAlloc(PTR) -> (PTR) + .local PTR, PTR + global.get __stack_pointer + # Get arg 0 -> number of bytes to allocate + local.get 0 + # Stack grows down. Subtract arg0 from __stack_pointer + PTR.sub + # Align result by anding with ~15 + PTR.const MASK + PTR.and + local.tee 1 + global.set __stack_pointer + local.get 1 + end_function + +emscripten_stack_get_current: + .functype emscripten_stack_get_current () -> (PTR) + global.get __stack_pointer + end_function +