diff --git a/compiler-rt/lib/asan/asan_emscripten.cpp b/compiler-rt/lib/asan/asan_emscripten.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/asan/asan_emscripten.cpp @@ -0,0 +1,119 @@ +#include "asan_interceptors.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_poisoning.h" +#include "asan_stack.h" +#include "asan_thread.h" + +#if SANITIZER_EMSCRIPTEN +#include + +#include + +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); +} + +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() {} + +// We can use a plain thread_local variable for TSD. +static thread_local void *per_thread; + +void *AsanTSDGet() { return per_thread; } + +void AsanTSDSet(void *tsd) { per_thread = tsd; } + +// There's no initialization needed, and the passed-in destructor +// will never be called. Instead, our own thread destruction hook +// (below) will call AsanThread::TSDDtor directly. +void AsanTSDInit(void (*destructor)(void *tsd)) { + DCHECK(destructor == &PlatformTSDDtor); +} + +void PlatformTSDDtor(void *tsd) { UNREACHABLE(__func__); } + +extern "C" { +void *emscripten_builtin_malloc(size_t size); +void emscripten_builtin_free(void *memory); +int emscripten_builtin_pthread_create(void *thread, void *attr, + void *(*callback)(void *), void *arg); +int pthread_attr_getdetachstate(void *attr, int *detachstate); +} + +static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { + atomic_uintptr_t *param = reinterpret_cast(arg); + AsanThread *t = nullptr; + while ((t = reinterpret_cast( + atomic_load(param, memory_order_acquire))) == nullptr) + internal_sched_yield(); + emscripten_builtin_free(param); + SetCurrentThread(t); + return t->ThreadStart(GetTid(), nullptr); +} + +INTERCEPTOR(int, pthread_create, void *thread, void *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) + pthread_attr_getdetachstate(attr, &detached); + atomic_uintptr_t *param = + (atomic_uintptr_t *)emscripten_builtin_malloc(sizeof(atomic_uintptr_t)); + atomic_store(param, 0, memory_order_relaxed); + 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, param); + } + if (result == 0) { + u32 current_tid = GetCurrentTidOrInvalid(); + AsanThread *t = + AsanThread::Create(start_routine, arg, current_tid, &stack, detached); + atomic_store(param, reinterpret_cast(t), memory_order_release); + } + return result; +} + +} // namespace __asan + +namespace __lsan { + +#ifndef USE_THREADS +// 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_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp --- a/compiler-rt/lib/asan/asan_errors.cpp +++ b/compiler-rt/lib/asan/asan_errors.cpp @@ -479,6 +479,17 @@ scariness.Scare(bug_type_score + read_after_free_bonus, bug_descr); if (far_from_bounds) scariness.Scare(10, "far-from-bounds"); } +#if SANITIZER_EMSCRIPTEN + // If address is in the first page (64 KB), then it is likely that the + // access is a result of a null pointer dereference. + else if (addr < 65536) { + bug_descr = "null-pointer-dereference"; + scariness.Scare(25, bug_descr); + } else if (AddrIsInShadow(addr)) { + bug_descr = "shadow-access"; + scariness.Scare(25, bug_descr); + } +#endif } } diff --git a/compiler-rt/lib/asan/asan_flags.cpp b/compiler-rt/lib/asan/asan_flags.cpp --- a/compiler-rt/lib/asan/asan_flags.cpp +++ b/compiler-rt/lib/asan/asan_flags.cpp @@ -22,6 +22,11 @@ #include "ubsan/ubsan_flags.h" #include "ubsan/ubsan_platform.h" +#if SANITIZER_EMSCRIPTEN +extern "C" void emscripten_builtin_free(void *); +#include +#endif + namespace __asan { Flags asan_flags_dont_use_directly; // use via flags(). @@ -58,7 +63,11 @@ CommonFlags cf; cf.CopyFrom(*common_flags()); cf.detect_leaks = cf.detect_leaks && CAN_SANITIZE_LEAKS; +#if !SANITIZER_EMSCRIPTEN + // getenv on emscripten uses malloc, which we can't when using LSan. + // You can't run external symbolizer executables anyway. cf.external_symbolizer_path = GetEnv("ASAN_SYMBOLIZER_PATH"); +#endif cf.malloc_context_size = kDefaultMallocContextSize; cf.intercept_tls_get_addr = true; cf.exitcode = 1; @@ -119,6 +128,25 @@ lsan_parser.ParseString(lsan_default_options); #endif +#if SANITIZER_EMSCRIPTEN + char *options; + // Override from Emscripten Module. +#define MAKE_OPTION_LOAD(parser, name) \ + options = (char *)EM_ASM_INT({ \ + return _emscripten_with_builtin_malloc( \ + function() { return allocateUTF8(Module[name] || 0); }); \ + }); \ + parser.ParseString(options); \ + emscripten_builtin_free(options); + + MAKE_OPTION_LOAD(asan_parser, 'ASAN_OPTIONS'); +#if CAN_SANITIZE_LEAKS + MAKE_OPTION_LOAD(lsan_parser, 'LSAN_OPTIONS'); +#endif +#if CAN_SANITIZE_UB + MAKE_OPTION_LOAD(ubsan_parser, 'UBSAN_OPTIONS'); +#endif +#else // Override from command line. asan_parser.ParseStringFromEnv("ASAN_OPTIONS"); #if CAN_SANITIZE_LEAKS @@ -127,6 +155,7 @@ #if CAN_SANITIZE_UB ubsan_parser.ParseStringFromEnv("UBSAN_OPTIONS"); #endif +#endif // SANITIZER_EMSCRIPTEN InitializeCommonFlags(); diff --git a/compiler-rt/lib/asan/asan_interceptors.cpp b/compiler-rt/lib/asan/asan_interceptors.cpp --- a/compiler-rt/lib/asan/asan_interceptors.cpp +++ b/compiler-rt/lib/asan/asan_interceptors.cpp @@ -23,10 +23,10 @@ #include "lsan/lsan_common.h" #include "sanitizer_common/sanitizer_libc.h" -// There is no general interception at all on Fuchsia and RTEMS. +// There is no general interception at all on Fuchsia, RTEMS and Emscripten. // Only the functions in asan_interceptors_memintrinsics.cpp are // really defined to replace libc functions. -#if !SANITIZER_FUCHSIA && !SANITIZER_RTEMS +#if !SANITIZER_FUCHSIA && !SANITIZER_RTEMS && !SANITIZER_EMSCRIPTEN #if SANITIZER_POSIX #include "sanitizer_common/sanitizer_posix.h" diff --git a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp --- a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp +++ b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp @@ -30,7 +30,7 @@ ASAN_MEMMOVE_IMPL(nullptr, to, from, size); } -#if SANITIZER_FUCHSIA || SANITIZER_RTEMS +#if SANITIZER_FUCHSIA || SANITIZER_RTEMS || SANITIZER_EMSCRIPTEN // Fuchsia and RTEMS don't use sanitizer_common_interceptors.inc, but // the only things there it wants are these three. Just define them diff --git a/compiler-rt/lib/asan/asan_malloc_linux.cpp b/compiler-rt/lib/asan/asan_malloc_linux.cpp --- a/compiler-rt/lib/asan/asan_malloc_linux.cpp +++ b/compiler-rt/lib/asan/asan_malloc_linux.cpp @@ -15,7 +15,8 @@ #include "sanitizer_common/sanitizer_platform.h" #if SANITIZER_FREEBSD || SANITIZER_FUCHSIA || SANITIZER_LINUX || \ - SANITIZER_NETBSD || SANITIZER_RTEMS || SANITIZER_SOLARIS + SANITIZER_NETBSD || SANITIZER_RTEMS || SANITIZER_SOLARIS || \ + SANITIZER_EMSCRIPTEN #include "sanitizer_common/sanitizer_allocator_checks.h" #include "sanitizer_common/sanitizer_errno.h" diff --git a/compiler-rt/lib/asan/asan_mapping.h b/compiler-rt/lib/asan/asan_mapping.h --- a/compiler-rt/lib/asan/asan_mapping.h +++ b/compiler-rt/lib/asan/asan_mapping.h @@ -268,6 +268,8 @@ #if SANITIZER_MYRIAD2 #include "asan_mapping_myriad.h" +#elif SANITIZER_EMSCRIPTEN +#include "asan_mapping_emscripten.h" #elif defined(__sparc__) && SANITIZER_WORDSIZE == 64 #include "asan_mapping_sparc64.h" #else diff --git a/compiler-rt/lib/asan/asan_mapping_emscripten.h b/compiler-rt/lib/asan/asan_mapping_emscripten.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/asan/asan_mapping_emscripten.h @@ -0,0 +1,87 @@ +//===-- 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 << 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) >> 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/asan/asan_poisoning.h b/compiler-rt/lib/asan/asan_poisoning.h --- a/compiler-rt/lib/asan/asan_poisoning.h +++ b/compiler-rt/lib/asan/asan_poisoning.h @@ -54,6 +54,10 @@ // RTEMS doesn't have have pages, let alone a fast way to zero // them, so default to memset. SANITIZER_RTEMS == 1 || + // Emscripten doesn't have a nice way to zero whole pages. + // The bulk memory proposal will allow memset to be optimized, but + // even then, we still must use memset. + SANITIZER_EMSCRIPTEN == 1 || shadow_end - shadow_beg < common_flags()->clear_shadow_mmap_threshold) { REAL(memset)((void*)shadow_beg, value, shadow_end - shadow_beg); } else { diff --git a/compiler-rt/lib/asan/asan_poisoning.cpp b/compiler-rt/lib/asan/asan_poisoning.cpp --- a/compiler-rt/lib/asan/asan_poisoning.cpp +++ b/compiler-rt/lib/asan/asan_poisoning.cpp @@ -65,7 +65,9 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) { // Since asan's mapping is compacting, the shadow chunk may be // not page-aligned, so we only flush the page-aligned portion. +#if !SANITIZER_EMSCRIPTEN ReleaseMemoryPagesToOS(MemToShadow(p), MemToShadow(p + size)); +#endif } void AsanPoisonOrUnpoisonIntraObjectRedzone(uptr ptr, uptr size, bool poison) { @@ -187,6 +189,16 @@ if (!AddrIsInMem(beg) && !AddrIsInShadow(beg)) return 0; if (!AddrIsInMem(end) && !AddrIsInShadow(end)) return 0; } else { +#if SANITIZER_EMSCRIPTEN + // XXX Emscripten hack XXX + // Null pointer handling, since Emscripten does not crash on null pointer, + // ASan must catch null pointer dereference by itself. + // Unfortunately, this function returns 0 to mean the region is not + // poisoned, so we must return 1 instead if we receive a region + // starting at 0. + if (!beg) + return 1; +#endif if (!AddrIsInMem(beg)) return beg; if (!AddrIsInMem(end)) return end; } diff --git a/compiler-rt/lib/asan/asan_posix.cpp b/compiler-rt/lib/asan/asan_posix.cpp --- a/compiler-rt/lib/asan/asan_posix.cpp +++ b/compiler-rt/lib/asan/asan_posix.cpp @@ -83,7 +83,7 @@ atomic_signal_fence(memory_order_seq_cst); AsanThread::TSDDtor(tsd); } -#else +#elif !SANITIZER_EMSCRIPTEN static pthread_key_t tsd_key; static bool tsd_key_inited = false; void AsanTSDInit(void (*destructor)(void *tsd)) { diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp --- a/compiler-rt/lib/asan/asan_rtl.cpp +++ b/compiler-rt/lib/asan/asan_rtl.cpp @@ -50,6 +50,7 @@ Report("Sleeping for %d second(s)\n", flags()->sleep_before_dying); SleepForSeconds(flags()->sleep_before_dying); } +#if !SANITIZER_EMSCRIPTEN if (flags()->unmap_shadow_on_exit) { if (kMidMemBeg) { UnmapOrDie((void*)kLowShadowBeg, kMidMemBeg - kLowShadowBeg); @@ -59,6 +60,7 @@ UnmapOrDie((void*)kLowShadowBeg, kHighShadowEnd - kLowShadowBeg); } } +#endif } static void AsanCheckFailed(const char *file, int line, const char *cond, @@ -314,7 +316,7 @@ } static void InitializeHighMemEnd() { -#if !SANITIZER_MYRIAD2 +#if !SANITIZER_MYRIAD2 && !SANITIZER_EMSCRIPTEN #if !ASAN_FIXED_MAPPING kHighMemEnd = GetMaxUserVirtualAddress(); // Increase kHighMemEnd to make sure it's properly @@ -463,7 +465,9 @@ InitializeShadowMemory(); AsanTSDInit(PlatformTSDDtor); +#if !SANITIZER_EMSCRIPTEN InstallDeadlySignalHandlers(AsanOnDeadlySignal); +#endif AllocatorOptions allocator_options; allocator_options.SetFrom(flags(), common_flags()); diff --git a/compiler-rt/lib/asan/asan_shadow_setup.cpp b/compiler-rt/lib/asan/asan_shadow_setup.cpp --- a/compiler-rt/lib/asan/asan_shadow_setup.cpp +++ b/compiler-rt/lib/asan/asan_shadow_setup.cpp @@ -13,9 +13,9 @@ #include "sanitizer_common/sanitizer_platform.h" -// asan_fuchsia.cpp and asan_rtems.cpp have their own +// asan_fuchsia.cpp, asan_rtems.cpp and asan_emscripten.cpp have their own // InitializeShadowMemory implementation. -#if !SANITIZER_FUCHSIA && !SANITIZER_RTEMS +#if !SANITIZER_FUCHSIA && !SANITIZER_RTEMS && !SANITIZER_EMSCRIPTEN #include "asan_internal.h" #include "asan_mapping.h" diff --git a/compiler-rt/lib/asan/asan_thread.h b/compiler-rt/lib/asan/asan_thread.h --- a/compiler-rt/lib/asan/asan_thread.h +++ b/compiler-rt/lib/asan/asan_thread.h @@ -29,7 +29,13 @@ namespace __asan { const u32 kInvalidTid = 0xffffff; // Must fit into 24 bits. +#if SANITIZER_EMSCRIPTEN && !defined(USE_THREADS) +const u32 kMaxNumberOfThreads = 1; +#elif SANITIZER_EMSCRIPTEN +const u32 kMaxNumberOfThreads = 128; +#else const u32 kMaxNumberOfThreads = (1 << 22); // 4M +#endif class AsanThread; diff --git a/compiler-rt/lib/asan/asan_thread.cpp b/compiler-rt/lib/asan/asan_thread.cpp --- a/compiler-rt/lib/asan/asan_thread.cpp +++ b/compiler-rt/lib/asan/asan_thread.cpp @@ -101,7 +101,9 @@ VReport(1, "T%d exited\n", tid); malloc_storage().CommitBack(); +#if !SANITIZER_EMSCRIPTEN if (common_flags()->use_sigaltstack) UnsetAlternateSignalStack(); +#endif asanThreadRegistry().FinishThread(tid); FlushToDeadThreadStats(&stats_); // We also clear the shadow on thread destruction because @@ -249,7 +251,9 @@ if (signal_thread_is_registered) atomic_store(signal_thread_is_registered, 1, memory_order_release); +#if !SANITIZER_EMSCRIPTEN if (common_flags()->use_sigaltstack) SetAlternateSignalStack(); +#endif if (!start_routine_) { // start_routine_ == 0 if we're on the main thread or on one of the diff --git a/compiler-rt/lib/interception/interception.h b/compiler-rt/lib/interception/interception.h --- a/compiler-rt/lib/interception/interception.h +++ b/compiler-rt/lib/interception/interception.h @@ -16,9 +16,10 @@ #include "sanitizer_common/sanitizer_internal_defs.h" -#if !SANITIZER_LINUX && !SANITIZER_FREEBSD && !SANITIZER_MAC && \ +#if !SANITIZER_LINUX && !SANITIZER_FREEBSD && !SANITIZER_MAC && \ !SANITIZER_NETBSD && !SANITIZER_OPENBSD && !SANITIZER_WINDOWS && \ - !SANITIZER_FUCHSIA && !SANITIZER_RTEMS && !SANITIZER_SOLARIS + !SANITIZER_FUCHSIA && !SANITIZER_RTEMS && !SANITIZER_SOLARIS && \ + !SANITIZER_EMSCRIPTEN # error "Interception doesn't work on this operating system." #endif @@ -130,7 +131,7 @@ extern "C" ret_type func(__VA_ARGS__); # define DECLARE_WRAPPER_WINAPI(ret_type, func, ...) \ extern "C" __declspec(dllimport) ret_type __stdcall func(__VA_ARGS__); -#elif SANITIZER_RTEMS +#elif SANITIZER_RTEMS || SANITIZER_EMSCRIPTEN # define WRAP(x) x # define WRAPPER_NAME(x) #x # define INTERCEPTOR_ATTRIBUTE @@ -162,6 +163,13 @@ # define INTERCEPTOR_ATTRIBUTE __attribute__((visibility("default"))) # define REAL(x) __unsanitized_##x # define DECLARE_REAL(ret_type, func, ...) +#elif SANITIZER_EMSCRIPTEN +// Sanitizer runtimes on Emscripten just define functions directly to override +// the libc functions. If the real version is really needed, they can be defined +// with the emscripten_builtin_ prefix. +#define REAL(x) emscripten_builtin_##x +#define DECLARE_REAL(ret_type, func, ...) \ + extern "C" ret_type REAL(func)(__VA_ARGS__); #elif SANITIZER_RTEMS # define REAL(x) __real_ ## x # define DECLARE_REAL(ret_type, func, ...) \ @@ -202,7 +210,8 @@ // macros does its job. In exceptional cases you may need to call REAL(foo) // without defining INTERCEPTOR(..., foo, ...). For example, if you override // foo with an interceptor for other function. -#if !SANITIZER_MAC && !SANITIZER_FUCHSIA && !SANITIZER_RTEMS +#if !SANITIZER_MAC && !SANITIZER_FUCHSIA && !SANITIZER_RTEMS && \ + !SANITIZER_EMSCRIPTEN # define DEFINE_REAL(ret_type, func, ...) \ typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ namespace __interception { \ @@ -281,7 +290,7 @@ #define INCLUDED_FROM_INTERCEPTION_LIB #if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD || \ - SANITIZER_OPENBSD || SANITIZER_SOLARIS + SANITIZER_OPENBSD || SANITIZER_SOLARIS || SANITIZER_EMSCRIPTEN # include "interception_linux.h" # define INTERCEPT_FUNCTION(func) INTERCEPT_FUNCTION_LINUX_OR_FREEBSD(func) diff --git a/compiler-rt/lib/interception/interception_linux.h b/compiler-rt/lib/interception/interception_linux.h --- a/compiler-rt/lib/interception/interception_linux.h +++ b/compiler-rt/lib/interception/interception_linux.h @@ -12,7 +12,7 @@ //===----------------------------------------------------------------------===// #if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD || \ - SANITIZER_OPENBSD || SANITIZER_SOLARIS + SANITIZER_OPENBSD || SANITIZER_SOLARIS || SANITIZER_EMSCRIPTEN #if !defined(INCLUDED_FROM_INTERCEPTION_LIB) # error "interception_linux.h should be included from interception library only" @@ -35,8 +35,9 @@ (::__interception::uptr) & (func), \ (::__interception::uptr) & WRAP(func)) -// Android, Solaris and OpenBSD do not have dlvsym -#if !SANITIZER_ANDROID && !SANITIZER_SOLARIS && !SANITIZER_OPENBSD +// Android, Solaris, OpenBSD and emscripten do not have dlvsym +#if !SANITIZER_ANDROID && !SANITIZER_SOLARIS && !SANITIZER_OPENBSD && \ + !SANITIZER_EMSCRIPTEN #define INTERCEPT_FUNCTION_VER_LINUX_OR_FREEBSD(func, symver) \ ::__interception::InterceptFunction( \ #func, symver, \ diff --git a/compiler-rt/lib/interception/interception_linux.cpp b/compiler-rt/lib/interception/interception_linux.cpp --- a/compiler-rt/lib/interception/interception_linux.cpp +++ b/compiler-rt/lib/interception/interception_linux.cpp @@ -14,7 +14,7 @@ #include "interception.h" #if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD || \ - SANITIZER_OPENBSD || SANITIZER_SOLARIS + SANITIZER_OPENBSD || SANITIZER_SOLARIS || SANITIZER_EMSCRIPTEN #include // for dlsym() and dlvsym() @@ -64,7 +64,8 @@ } // Android and Solaris do not have dlvsym -#if !SANITIZER_ANDROID && !SANITIZER_SOLARIS && !SANITIZER_OPENBSD +#if !SANITIZER_ANDROID && !SANITIZER_SOLARIS && !SANITIZER_OPENBSD && \ + !SANITIZER_EMSCRIPTEN static void *GetFuncAddr(const char *name, const char *ver) { return dlvsym(RTLD_NEXT, name, ver); } diff --git a/compiler-rt/lib/lsan/lsan.cpp b/compiler-rt/lib/lsan/lsan.cpp --- a/compiler-rt/lib/lsan/lsan.cpp +++ b/compiler-rt/lib/lsan/lsan.cpp @@ -58,7 +58,11 @@ { CommonFlags cf; cf.CopyFrom(*common_flags()); +#if !SANITIZER_EMSCRIPTEN + // getenv on emscripten uses malloc, which we can't when using LSan. + // You can't run external symbolizers anyway. cf.external_symbolizer_path = GetEnv("LSAN_SYMBOLIZER_PATH"); +#endif cf.malloc_context_size = 30; cf.intercept_tls_get_addr = true; cf.detect_leaks = true; @@ -113,7 +117,10 @@ InitTlsSize(); InitializeInterceptors(); InitializeThreadRegistry(); +#if !SANITIZER_EMSCRIPTEN + // Emscripten does not have signals InstallDeadlySignalHandlers(LsanOnDeadlySignal); +#endif u32 tid = ThreadCreate(0, 0, true); CHECK_EQ(tid, 0); ThreadStart(tid, GetTid()); diff --git a/compiler-rt/lib/lsan/lsan_allocator.h b/compiler-rt/lib/lsan/lsan_allocator.h --- a/compiler-rt/lib/lsan/lsan_allocator.h +++ b/compiler-rt/lib/lsan/lsan_allocator.h @@ -50,7 +50,7 @@ }; #if defined(__mips64) || defined(__aarch64__) || defined(__i386__) || \ - defined(__arm__) + defined(__arm__) || defined(__wasm32__) template struct AP32 { static const uptr kSpaceBeg = 0; diff --git a/compiler-rt/lib/lsan/lsan_allocator.cpp b/compiler-rt/lib/lsan/lsan_allocator.cpp --- a/compiler-rt/lib/lsan/lsan_allocator.cpp +++ b/compiler-rt/lib/lsan/lsan_allocator.cpp @@ -26,7 +26,7 @@ extern "C" void *memset(void *ptr, int value, uptr num); namespace __lsan { -#if defined(__i386__) || defined(__arm__) +#if defined(__i386__) || defined(__arm__) || defined(__wasm32__) static const uptr kMaxAllowedMallocSize = 1UL << 30; #elif defined(__mips64) || defined(__aarch64__) static const uptr kMaxAllowedMallocSize = 4UL << 30; diff --git a/compiler-rt/lib/lsan/lsan_common.h b/compiler-rt/lib/lsan/lsan_common.h --- a/compiler-rt/lib/lsan/lsan_common.h +++ b/compiler-rt/lib/lsan/lsan_common.h @@ -42,6 +42,8 @@ #define CAN_SANITIZE_LEAKS 1 #elif SANITIZER_NETBSD #define CAN_SANITIZE_LEAKS 1 +#elif SANITIZER_EMSCRIPTEN +#define CAN_SANITIZE_LEAKS 1 #else #define CAN_SANITIZE_LEAKS 0 #endif @@ -212,6 +214,10 @@ uptr *cache_end, DTLS **dtls); void ForEachExtraStackRange(tid_t os_id, RangeIteratorCallback callback, void *arg); +// Scans thread data (stacks and TLS) for heap pointers. +void ProcessThreads(SuspendedThreadsList const &suspended_threads, + Frontier *frontier); + // If called from the main thread, updates the main thread's TID in the thread // registry. We need this to handle processes that fork() without a subsequent // exec(), which invalidates the recorded TID. To update it, we must call diff --git a/compiler-rt/lib/lsan/lsan_common.cpp b/compiler-rt/lib/lsan/lsan_common.cpp --- a/compiler-rt/lib/lsan/lsan_common.cpp +++ b/compiler-rt/lib/lsan/lsan_common.cpp @@ -25,6 +25,10 @@ #include "sanitizer_common/sanitizer_thread_registry.h" #include "sanitizer_common/sanitizer_tls_get_addr.h" +#if SANITIZER_EMSCRIPTEN +#include "lsan/lsan_allocator.h" +#endif + #if CAN_SANITIZE_LEAKS namespace __lsan { @@ -162,6 +166,15 @@ uptr pp = begin; if (pp % alignment) pp = pp + alignment - pp % alignment; + + // Emscripten in non-threaded mode stores thread_local variables in the + // same place as normal globals. This means allocator_cache must be skipped + // when scanning globals instead of when scanning thread-locals. +#if SANITIZER_EMSCRIPTEN && !defined(USE_THREADS) + uptr cache_begin, cache_end; + GetAllocatorCacheRange(&cache_begin, &cache_end); +#endif + for (; pp + sizeof(void *) <= end; pp += alignment) { // NOLINT void *p = *reinterpret_cast(pp); if (!CanBeAHeapPointer(reinterpret_cast(p))) continue; @@ -181,6 +194,14 @@ continue; } +#if SANITIZER_EMSCRIPTEN && !defined(USE_THREADS) + if (cache_begin <= pp && pp < cache_end) { + LOG_POINTERS("%p: skipping because it overlaps the cache %p-%p.\n", pp, + cache_begin, cache_end); + continue; + } +#endif + m.set_tag(tag); LOG_POINTERS("%p: found %p pointing into chunk %p-%p of size %zu.\n", pp, p, chunk, chunk + m.requested_size(), m.requested_size()); @@ -211,9 +232,10 @@ ScanRangeForPointers(begin, end, frontier, "FAKE STACK", kReachable); } +#if !SANITIZER_EMSCRIPTEN // Scans thread data (stacks and TLS) for heap pointers. -static void ProcessThreads(SuspendedThreadsList const &suspended_threads, - Frontier *frontier) { +void ProcessThreads(SuspendedThreadsList const &suspended_threads, + Frontier *frontier) { InternalMmapVector registers(suspended_threads.RegisterCount()); uptr registers_begin = reinterpret_cast(registers.data()); uptr registers_end = @@ -307,6 +329,7 @@ } } } +#endif // !SANITIZER_EMSCRIPTEN void ScanRootRegion(Frontier *frontier, const RootRegion &root_region, uptr region_begin, uptr region_end, bool is_readable) { @@ -322,14 +345,22 @@ kReachable); } +#if SANITIZER_EMSCRIPTEN +extern "C" uptr emscripten_get_heap_size(); +#endif + static void ProcessRootRegion(Frontier *frontier, const RootRegion &root_region) { +#if SANITIZER_EMSCRIPTEN + ScanRootRegion(frontier, root_region, 0, emscripten_get_heap_size(), true); +#else MemoryMappingLayout proc_maps(/*cache_enabled*/ true); MemoryMappedSegment segment; while (proc_maps.Next(&segment)) { ScanRootRegion(frontier, root_region, segment.start, segment.end, segment.IsReadable()); } +#endif // SANITIZER_EMSCRIPTEN } // Scans root regions for heap pointers. @@ -433,6 +464,7 @@ // On all other platforms, this simply checks to ensure that the caller pc is // valid before reporting chunks as leaked. void ProcessPC(Frontier *frontier) { +#if !SANITIZER_EMSCRIPTEN StackDepotReverseMap stack_depot_reverse_map; InvalidPCParam arg; arg.frontier = frontier; @@ -440,6 +472,7 @@ arg.skip_linker_allocations = flags()->use_tls && flags()->use_ld_allocations && GetLinker() != nullptr; ForEachChunk(MarkInvalidPCCb, &arg); +#endif } // Sets the appropriate tag on each chunk. @@ -555,7 +588,9 @@ CheckForLeaksParam *param = reinterpret_cast(arg); CHECK(param); CHECK(!param->success); +#if !SANITIZER_EMSCRIPTEN ReportUnsuspendedThreads(suspended_threads); +#endif ClassifyAllChunks(suspended_threads); ForEachChunk(CollectLeaksCb, ¶m->leak_report); // Clean up for subsequent leak checks. This assumes we did not overwrite any @@ -587,6 +622,7 @@ } param.leak_report.ApplySuppressions(); uptr unsuppressed_count = param.leak_report.UnsuppressedLeakCount(); + if (unsuppressed_count > 0) { Decorator d; Printf("\n" @@ -651,8 +687,15 @@ static Suppression *GetSuppressionForStack(u32 stack_trace_id) { StackTrace stack = StackDepotGet(stack_trace_id); for (uptr i = 0; i < stack.size; i++) { +#if SANITIZER_EMSCRIPTEN + // On Emscripten, the stack trace is the actual call site, not + // the code that would be executed after the return. + // Therefore, StackTrace::GetPreviousInstructionPc is not needed. + Suppression *s = GetSuppressionForAddr(stack.trace[i]); +#else Suppression *s = GetSuppressionForAddr( StackTrace::GetPreviousInstructionPc(stack.trace[i])); +#endif if (s) return s; } return nullptr; diff --git a/compiler-rt/lib/lsan/lsan_common_emscripten.cpp b/compiler-rt/lib/lsan/lsan_common_emscripten.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/lsan/lsan_common_emscripten.cpp @@ -0,0 +1,185 @@ +//=-- +//lsan_common_emscripten.cpp--------------------------------------------===// +// +// 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 "lsan_common.h" +#include "sanitizer_common/sanitizer_platform.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 { + +extern "C" uptr emscripten_get_heap_size(); + +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 DoStopTheWorld(StopTheWorldCallback callback, void *argument) { + // Currently, on Emscripten this does nothing and just calls the callback. + // This works fine on a single-threaded environment. + StopTheWorld(callback, argument); +} + +u32 GetCurrentThread(); + +// This is based on ProcessThreads in lsan_common.cpp. +// 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 %d not found in registry.\n", os_id); + return; + } + + if (flags()->use_stacks) { + LOG_THREADS("Stack at %p-%p.\n", stack_begin, 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); + CHECK(stack_begin <= sp && sp < stack_end); + stack_begin = sp; + } + + ScanRangeForPointers(stack_begin, stack_end, frontier, "STACK", kReachable); + } + + if (flags()->use_tls && tls_begin) { + LOG_THREADS("TLS at %p-%p.\n", tls_begin, 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) { + GetThreadRegistryLocked()->RunCallbackForEachThreadLocked( + ProcessThreadsCallback, frontier); +} + +} // namespace __lsan + +extern "C" void __lsan_disable_in_this_thread() { + __lsan::DisableInThisThread(); +} + +extern "C" void __lsan_enable_in_this_thread() { __lsan::EnableInThisThread(); } +#endif // CAN_SANITIZE_LEAKS && SANITIZER_EMSCRIPTEN diff --git a/compiler-rt/lib/lsan/lsan_interceptors.cpp b/compiler-rt/lib/lsan/lsan_interceptors.cpp --- a/compiler-rt/lib/lsan/lsan_interceptors.cpp +++ b/compiler-rt/lib/lsan/lsan_interceptors.cpp @@ -351,6 +351,16 @@ atomic_uintptr_t tid; }; +#if SANITIZER_EMSCRIPTEN +extern "C" { +int emscripten_builtin_pthread_create(void *thread, void *attr, + void *(*callback)(void *), void *arg); +int emscripten_builtin_pthread_join(void *th, void **ret); +void *emscripten_builtin_malloc(size_t size); +void emscripten_builtin_free(void *); +} +#endif + extern "C" void *__lsan_thread_start_func(void *arg) { ThreadParam *p = (ThreadParam*)arg; void* (*callback)(void *arg) = p->callback; @@ -369,7 +379,11 @@ internal_sched_yield(); SetCurrentThread(tid); ThreadStart(tid, GetTid()); +#if SANITIZER_EMSCRIPTEN + emscripten_builtin_free(p); +#else atomic_store(&p->tid, 0, memory_order_release); +#endif return callback(param); } @@ -385,10 +399,18 @@ AdjustStackSize(attr); int detached = 0; pthread_attr_getdetachstate(attr, &detached); +#if SANITIZER_EMSCRIPTEN + ThreadParam *p = + (ThreadParam *)emscripten_builtin_malloc(sizeof(ThreadParam)); + p->callback = callback; + p->param = param; + atomic_store(&p->tid, 0, memory_order_relaxed); +#else ThreadParam p; p.callback = callback; p.param = param; atomic_store(&p.tid, 0, memory_order_relaxed); +#endif int res; { // Ignore all allocations made by pthread_create: thread stack/TLS may be @@ -396,15 +418,23 @@ // the linked list it's stored in doesn't even hold valid pointers to the // objects, the latter are calculated by obscure pointer arithmetic. ScopedInterceptorDisabler disabler; +#if SANITIZER_EMSCRIPTEN + res = REAL(pthread_create)(th, attr, __lsan_thread_start_func, p); +#else res = REAL(pthread_create)(th, attr, __lsan_thread_start_func, &p); +#endif } if (res == 0) { int tid = ThreadCreate(GetCurrentThread(), *(uptr *)th, IsStateDetached(detached)); CHECK_NE(tid, 0); +#if SANITIZER_EMSCRIPTEN + atomic_store(&p->tid, tid, memory_order_release); +#else atomic_store(&p.tid, tid, memory_order_release); while (atomic_load(&p.tid, memory_order_acquire) != 0) internal_sched_yield(); +#endif } if (attr == &myattr) pthread_attr_destroy(&myattr); @@ -420,11 +450,21 @@ return res; } +#if !SANITIZER_EMSCRIPTEN INTERCEPTOR(void, _exit, int status) { if (status == 0 && HasReportedLeaks()) status = common_flags()->exitcode; REAL(_exit)(status); } +#endif +#if SANITIZER_EMSCRIPTEN +namespace __lsan { + +void InitializeInterceptors() {} + +} // namespace __lsan + +#else #define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name) #include "sanitizer_common/sanitizer_signal_interceptors.inc" @@ -463,3 +503,4 @@ } } // namespace __lsan +#endif // SANITIZER_EMSCRIPTEN diff --git a/compiler-rt/lib/lsan/lsan_linux.cpp b/compiler-rt/lib/lsan/lsan_linux.cpp --- a/compiler-rt/lib/lsan/lsan_linux.cpp +++ b/compiler-rt/lib/lsan/lsan_linux.cpp @@ -12,7 +12,7 @@ #include "sanitizer_common/sanitizer_platform.h" -#if SANITIZER_LINUX || SANITIZER_NETBSD +#if SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_EMSCRIPTEN #include "lsan_allocator.h" @@ -29,4 +29,4 @@ } // namespace __lsan -#endif // SANITIZER_LINUX || SANITIZER_NETBSD +#endif // SANITIZER_LINUX || SANITIZER_NETBSD || 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 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_emscripten.cpp @@ -0,0 +1,128 @@ +//===-- sanitizer_emscripten.cpp ------------------------------------------===// +// +// 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 + +#include "sanitizer_common.h" +#include "sanitizer_platform.h" +#include "sanitizer_stoptheworld.h" + +#if SANITIZER_EMSCRIPTEN + +#include + +namespace __sanitizer { + +extern "C" { +int emscripten_get_module_name(char *buf, uptr length); +} + +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(); } + +SANITIZER_WEAK_ATTRIBUTE int real_sigaction(int signum, const void *act, + void *oldact); + +int internal_sigaction(int signum, const void *act, void *oldact) { +#if !SANITIZER_GO + if (&real_sigaction) + return real_sigaction(signum, act, oldact); +#endif + return sigaction(signum, (const struct sigaction *)act, + (struct sigaction *)oldact); +} + +extern "C" { +uptr emscripten_builtin_mmap2(void *addr, uptr length, int prot, int flags, + int fd, unsigned offset); +uptr emscripten_builtin_munmap(void *addr, uptr length); +} + +uptr internal_mmap(void *addr, uptr length, int prot, int flags, int fd, + OFF_T offset) { + CHECK(IsAligned(offset, 4096)); + return emscripten_builtin_mmap2(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 = EM_ASM_INT({ return STACK_BASE; }); + *stack_bottom = EM_ASM_INT({ return STACK_MAX; }); +} + +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 USE_THREADS + *tls_addr = (uptr)__builtin_wasm_tls_base(); + *tls_size = __builtin_wasm_tls_size(); +#else + *tls_addr = *tls_size = 0; +#endif +} + +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(SuspendedThreadsList(), argument); +} + +} // namespace __sanitizer + +#endif diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp @@ -17,6 +17,11 @@ #include "sanitizer_flags.h" #include "sanitizer_flag_parser.h" +#if SANITIZER_EMSCRIPTEN +extern "C" void emscripten_builtin_free(void *); +#include +#endif + namespace __sanitizer { LowLevelAllocator FlagParser::Alloc; @@ -120,9 +125,20 @@ } void FlagParser::ParseStringFromEnv(const char *env_name) { +#if SANITIZER_EMSCRIPTEN + const char *env = (char *)EM_ASM_INT({ + return _emscripten_with_builtin_malloc(function() { + return allocateUTF8(Module['UBSAN_OPTIONS'] || 0); + }); + }); +#else const char *env = GetEnv(env_name); +#endif // SANITIZER_EMSCRIPTEN VPrintf(1, "%s: %s\n", env_name, env ? env : ""); ParseString(env, env_name); +#if SANITIZER_EMSCRIPTEN + emscripten_builtin_free(env) +#endif } void FlagParser::ParseString(const char *s, const char *env_option_name) { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h @@ -182,7 +182,8 @@ #if (SANITIZER_WORDSIZE == 64) || SANITIZER_MAC typedef uptr operator_new_size_type; #else -# if SANITIZER_OPENBSD || defined(__s390__) && !defined(__s390x__) +#if SANITIZER_OPENBSD || defined(__s390__) && !defined(__s390x__) || \ + SANITIZER_EMSCRIPTEN // Special case: 31-bit s390 has unsigned long as size_t. typedef unsigned long operator_new_size_type; # else diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp @@ -14,7 +14,7 @@ #include "sanitizer_platform.h" #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || \ - SANITIZER_OPENBSD || SANITIZER_SOLARIS + SANITIZER_OPENBSD || SANITIZER_SOLARIS || SANITIZER_EMSCRIPTEN #include "sanitizer_common.h" #include "sanitizer_flags.h" @@ -24,6 +24,7 @@ #include "sanitizer_linux.h" #include "sanitizer_mutex.h" #include "sanitizer_placement_new.h" +#include "sanitizer_posix.h" #include "sanitizer_procmaps.h" #if SANITIZER_LINUX @@ -101,9 +102,14 @@ #define environ _environ #endif +#if SANITIZER_EMSCRIPTEN +#include +#include +#endif + extern char **environ; -#if SANITIZER_LINUX +#if SANITIZER_LINUX || SANITIZER_EMSCRIPTEN // struct kernel_timeval { long tv_sec; @@ -158,13 +164,15 @@ #include "sanitizer_syscall_linux_aarch64.inc" #elif SANITIZER_LINUX && defined(__arm__) #include "sanitizer_syscall_linux_arm.inc" +#elif SANITIZER_EMSCRIPTEN +#include "sanitizer_syscall_emscripten.inc" #else #include "sanitizer_syscall_generic.inc" #endif // --------------- sanitizer_libc.h #if !SANITIZER_SOLARIS && !SANITIZER_NETBSD -#if !SANITIZER_S390 && !SANITIZER_OPENBSD +#if !SANITIZER_S390 && !SANITIZER_OPENBSD && !SANITIZER_EMSCRIPTEN uptr internal_mmap(void *addr, uptr length, int prot, int flags, int fd, OFF_T offset) { #if SANITIZER_FREEBSD || SANITIZER_LINUX_USES_64BIT_SYSCALLS @@ -177,13 +185,15 @@ offset / 4096); #endif } -#endif // !SANITIZER_S390 && !SANITIZER_OPENBSD +#endif // !SANITIZER_S390 && !SANITIZER_OPENBSD && !SANITIZER_NETBSD -#if !SANITIZER_OPENBSD +#if !SANITIZER_OPENBSD && !SANITIZER_EMSCRIPTEN uptr internal_munmap(void *addr, uptr length) { return internal_syscall(SYSCALL(munmap), (uptr)addr, length); } +#endif +#if !SANITIZER_OPENBSD int internal_mprotect(void *addr, uptr length, int prot) { return internal_syscall(SYSCALL(mprotect), (uptr)addr, length, prot); } @@ -231,7 +241,8 @@ return res; } -#if !SANITIZER_LINUX_USES_64BIT_SYSCALLS && SANITIZER_LINUX +#if !SANITIZER_LINUX_USES_64BIT_SYSCALLS && SANITIZER_LINUX || \ + SANITIZER_EMSCRIPTEN static void stat64_to_stat(struct stat64 *in, struct stat *out) { internal_memset(out, 0, sizeof(*out)); out->st_dev = in->st_dev; @@ -416,7 +427,11 @@ } uptr internal_sched_yield() { +#if SANITIZER_EMSCRIPTEN + return 0; +#else return internal_syscall(SYSCALL(sched_yield)); +#endif } void internal__exit(int exitcode) { @@ -469,13 +484,15 @@ return internal_syscall(SYSCALL(getthrid)); #elif SANITIZER_SOLARIS return thr_self(); +#elif SANITIZER_EMSCRIPTEN + return (tid_t)pthread_self(); #else return internal_syscall(SYSCALL(gettid)); #endif } int TgKill(pid_t pid, tid_t tid, int sig) { -#if SANITIZER_LINUX +#if SANITIZER_LINUX || SANITIZER_EMSCRIPTEN return internal_syscall(SYSCALL(tgkill), pid, tid, sig); #elif SANITIZER_FREEBSD return internal_syscall(SYSCALL(thr_kill2), pid, tid, sig); @@ -501,11 +518,23 @@ return (u64)tv.tv_sec * 1000*1000*1000 + tv.tv_usec * 1000; } +#if SANITIZER_EMSCRIPTEN +int __clock_gettime(__sanitizer_clockid_t clk_id, void *tp); + +uptr internal_clock_gettime(__sanitizer_clockid_t clk_id, void *tp) { + return __clock_gettime(clk_id, tp); +} +#else uptr internal_clock_gettime(__sanitizer_clockid_t clk_id, void *tp) { return internal_syscall(SYSCALL(clock_gettime), clk_id, tp); } +#endif // SANITIZER_EMSCRIPTEN #endif // !SANITIZER_SOLARIS && !SANITIZER_NETBSD +#if SANITIZER_EMSCRIPTEN +extern "C" const char *emscripten_get_env(const char *name); +#endif + // Like getenv, but reads env directly from /proc (on Linux) or parses the // 'environ' array (on some others) and does not use libc. This function // should be called first inside __asan_init. @@ -544,6 +573,8 @@ p = endp + 1; } return nullptr; // Not found. +#elif SANITIZER_EMSCRIPTEN + return emscripten_get_env(name); #else #error "Unsupported platform" #endif @@ -581,7 +612,7 @@ } #endif -#if !SANITIZER_OPENBSD +#if !SANITIZER_OPENBSD && !SANITIZER_EMSCRIPTEN static void GetArgsAndEnv(char ***argv, char ***envp) { #if SANITIZER_FREEBSD // On FreeBSD, retrieving the argument and environment arrays is done via the @@ -628,7 +659,7 @@ return envp; } -#endif // !SANITIZER_OPENBSD +#endif // !SANITIZER_OPENBSD && !SANITIZER_EMSCRIPTEN #if !SANITIZER_SOLARIS enum MutexState { @@ -651,6 +682,8 @@ _umtx_op(m, UMTX_OP_WAIT_UINT, MtxSleeping, 0, 0); #elif SANITIZER_NETBSD sched_yield(); /* No userspace futex-like synchronization */ +#elif SANITIZER_EMSCRIPTEN + emscripten_futex_wait(m, MtxSleeping, INFINITY); #else internal_syscall(SYSCALL(futex), (uptr)m, FUTEX_WAIT_PRIVATE, MtxSleeping, 0, 0, 0); @@ -667,6 +700,8 @@ _umtx_op(m, UMTX_OP_WAKE, 1, 0, 0); #elif SANITIZER_NETBSD /* No userspace futex-like synchronization */ +#elif SANITIZER_EMSCRIPTEN + emscripten_futex_wake(m, 1); #else internal_syscall(SYSCALL(futex), (uptr)m, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); #endif @@ -757,7 +792,10 @@ } int internal_fork() { -#if SANITIZER_USES_CANONICAL_LINUX_SYSCALLS +#if SANITIZER_EMSCRIPTEN + Report("fork not supported on emscripten\n"); + return -1; +#elif SANITIZER_USES_CANONICAL_LINUX_SYSCALLS return internal_syscall(SYSCALL(clone), SIGCHLD, 0); #else return internal_syscall(SYSCALL(fork)); @@ -891,7 +929,7 @@ #endif #endif // !SANITIZER_SOLARIS -#if !SANITIZER_NETBSD +#if !SANITIZER_NETBSD && !SANITIZER_EMSCRIPTEN // ThreadLister implementation. ThreadLister::ThreadLister(pid_t pid) : pid_(pid), buffer_(4096) { char task_directory_path[80]; @@ -1078,12 +1116,18 @@ } #endif // !SANITIZER_ANDROID +#if SANITIZER_EMSCRIPTEN +extern "C" uptr emscripten_get_module_name(char *buf, uptr buf_len); +#endif + #if !SANITIZER_OPENBSD uptr ReadBinaryName(/*out*/char *buf, uptr buf_len) { #if SANITIZER_SOLARIS const char *default_module_name = getexecname(); CHECK_NE(default_module_name, NULL); return internal_snprintf(buf, buf_len, "%s", default_module_name); +#elif SANITIZER_EMSCRIPTEN + return emscripten_get_module_name(buf, buf_len); #else #if SANITIZER_FREEBSD || SANITIZER_NETBSD #if SANITIZER_FREEBSD @@ -1966,6 +2010,9 @@ # endif *bp = ucontext->uc_mcontext.gregs[11]; *sp = ucontext->uc_mcontext.gregs[15]; +#elif SANITIZER_EMSCRIPTEN + Report("GetPcSpBp not implemented on emscripten"); + Abort(); #else # error "Unsupported arch" #endif diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform.h @@ -13,9 +13,9 @@ #define SANITIZER_PLATFORM_H #if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && \ - !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(_WIN32) && \ - !defined(__Fuchsia__) && !defined(__rtems__) && \ - !(defined(__sun__) && defined(__svr4__)) + !defined(__OpenBSD__) && !defined(__APPLE__) && !defined(_WIN32) && \ + !defined(__Fuchsia__) && !defined(__rtems__) && \ + !(defined(__sun__) && defined(__svr4__)) && !defined(__EMSCRIPTEN__) # error "This operating system is not supported" #endif @@ -110,9 +110,16 @@ # define SANITIZER_RTEMS 0 #endif -#define SANITIZER_POSIX \ - (SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_MAC || \ - SANITIZER_NETBSD || SANITIZER_OPENBSD || SANITIZER_SOLARIS) +#if defined(__EMSCRIPTEN__) +#define SANITIZER_EMSCRIPTEN 1 +#else +#define SANITIZER_EMSCRIPTEN 0 +#endif + +#define SANITIZER_POSIX \ + (SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_MAC || \ + SANITIZER_NETBSD || SANITIZER_OPENBSD || SANITIZER_SOLARIS || \ + SANITIZER_EMSCRIPTEN) #if __LP64__ || defined(_WIN64) # define SANITIZER_WORDSIZE 64 diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -125,6 +125,12 @@ # define SI_LINUX_NOT_FREEBSD 0 #endif +#if SANITIZER_EMSCRIPTEN +#define SI_EMSCRIPTEN 1 +#else +#define SI_EMSCRIPTEN 0 +#endif + #define SANITIZER_INTERCEPT_STRLEN SI_NOT_FUCHSIA #define SANITIZER_INTERCEPT_STRNLEN (SI_NOT_MAC && SI_NOT_FUCHSIA) #define SANITIZER_INTERCEPT_STRCMP SI_NOT_FUCHSIA @@ -252,7 +258,7 @@ #define SANITIZER_INTERCEPT_RECVMMSG SI_LINUX #define SANITIZER_INTERCEPT_SENDMMSG SI_LINUX #define SANITIZER_INTERCEPT_GETPEERNAME SI_POSIX -#define SANITIZER_INTERCEPT_IOCTL SI_POSIX +#define SANITIZER_INTERCEPT_IOCTL SI_POSIX && !SI_EMSCRIPTEN #define SANITIZER_INTERCEPT_INET_ATON SI_POSIX #define SANITIZER_INTERCEPT_SYSINFO SI_LINUX #define SANITIZER_INTERCEPT_READDIR SI_POSIX diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h @@ -14,7 +14,7 @@ #ifndef SANITIZER_PLATFORM_LIMITS_POSIX_H #define SANITIZER_PLATFORM_LIMITS_POSIX_H -#if SANITIZER_LINUX || SANITIZER_MAC +#if SANITIZER_LINUX || SANITIZER_MAC || SANITIZER_EMSCRIPTEN #include "sanitizer_internal_defs.h" #include "sanitizer_platform.h" @@ -511,7 +511,7 @@ typedef long __sanitizer_clock_t; #endif -#if SANITIZER_LINUX +#if SANITIZER_LINUX || SANITIZER_EMSCRIPTEN typedef int __sanitizer_clockid_t; #endif @@ -565,6 +565,8 @@ // The size is determined by looking at sizeof of real sigset_t on linux. uptr val[128 / sizeof(uptr)]; }; +#elif SANITIZER_EMSCRIPTEN + typedef unsigned long __sanitizer_sigset_t; #endif struct __sanitizer_siginfo { @@ -1018,6 +1020,7 @@ // when it can not be determined without including any system headers. extern const unsigned IOCTL_NOT_PRESENT; +#if !SANITIZER_EMSCRIPTEN extern unsigned IOCTL_FIOASYNC; extern unsigned IOCTL_FIOCLEX; extern unsigned IOCTL_FIOGETOWN; @@ -1066,6 +1069,8 @@ extern unsigned IOCTL_SIOCGETSGCNT; extern unsigned IOCTL_SIOCGETVIFCNT; #endif +#endif + #if SANITIZER_LINUX extern unsigned IOCTL_EVIOCGABS; extern unsigned IOCTL_EVIOCGBIT; diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp @@ -13,7 +13,7 @@ #include "sanitizer_platform.h" -#if SANITIZER_LINUX || SANITIZER_MAC +#if SANITIZER_LINUX || SANITIZER_MAC || SANITIZER_EMSCRIPTEN // Tests in this file assume that off_t-dependent data structures match the // libc ABI. For example, struct dirent here is what readdir() function (as // exported from libc) returns, and not the user-facing "dirent", which @@ -53,7 +53,7 @@ #include #endif -#if !SANITIZER_ANDROID +#if !SANITIZER_ANDROID && !SANITIZER_EMSCRIPTEN #include #include #include @@ -195,7 +195,7 @@ unsigned struct_statfs64_sz = sizeof(struct statfs64); #endif // SANITIZER_MAC && !SANITIZER_IOS -#if !SANITIZER_ANDROID +#if !SANITIZER_ANDROID && !SANITIZER_EMSCRIPTEN unsigned struct_fstab_sz = sizeof(struct fstab); unsigned struct_statfs_sz = sizeof(struct statfs); unsigned struct_sockaddr_sz = sizeof(struct sockaddr); @@ -383,7 +383,10 @@ // ioctl arguments unsigned struct_ifreq_sz = sizeof(struct ifreq); unsigned struct_termios_sz = sizeof(struct termios); + +#if !SANITIZER_EMSCRIPTEN unsigned struct_winsize_sz = sizeof(struct winsize); +#endif #if SANITIZER_LINUX unsigned struct_arpreq_sz = sizeof(struct arpreq); @@ -465,15 +468,18 @@ unsigned struct_ppp_stats_sz = sizeof(struct ppp_stats); #endif // (SANITIZER_LINUX || SANITIZER_FREEBSD) && !SANITIZER_ANDROID -#if !SANITIZER_ANDROID && !SANITIZER_MAC +#if !SANITIZER_ANDROID && !SANITIZER_MAC && !SANITIZER_EMSCRIPTEN unsigned struct_sioc_sg_req_sz = sizeof(struct sioc_sg_req); unsigned struct_sioc_vif_req_sz = sizeof(struct sioc_vif_req); #endif +#if !SANITIZER_EMSCRIPTEN const unsigned long __sanitizer_bufsiz = BUFSIZ; +#endif const unsigned IOCTL_NOT_PRESENT = 0; +#if !SANITIZER_EMSCRIPTEN unsigned IOCTL_FIOASYNC = FIOASYNC; unsigned IOCTL_FIOCLEX = FIOCLEX; unsigned IOCTL_FIOGETOWN = FIOGETOWN; @@ -522,6 +528,7 @@ unsigned IOCTL_SIOCGETSGCNT = SIOCGETSGCNT; unsigned IOCTL_SIOCGETVIFCNT = SIOCGETVIFCNT; #endif +#endif #if SANITIZER_LINUX unsigned IOCTL_EVIOCGABS = EVIOCGABS(0); @@ -1016,6 +1023,7 @@ #endif COMPILER_CHECK(sizeof(__sanitizer_dirent) <= sizeof(dirent)); +#if !SANITIZER_EMSCRIPTEN CHECK_SIZE_AND_OFFSET(dirent, d_ino); #if SANITIZER_MAC CHECK_SIZE_AND_OFFSET(dirent, d_seekoff); @@ -1025,6 +1033,7 @@ CHECK_SIZE_AND_OFFSET(dirent, d_off); #endif CHECK_SIZE_AND_OFFSET(dirent, d_reclen); +#endif // !SANITIZER_EMSCRIPTEN #if SANITIZER_LINUX && !SANITIZER_ANDROID COMPILER_CHECK(sizeof(__sanitizer_dirent64) <= sizeof(dirent64)); @@ -1044,6 +1053,7 @@ CHECK_TYPE_SIZE(nfds_t); +#if !SANITIZER_EMSCRIPTEN CHECK_TYPE_SIZE(sigset_t); COMPILER_CHECK(sizeof(__sanitizer_sigaction) == sizeof(struct sigaction)); @@ -1058,6 +1068,7 @@ #if SANITIZER_LINUX && (!SANITIZER_ANDROID || !SANITIZER_MIPS32) CHECK_STRUCT_SIZE_AND_OFFSET(sigaction, sa_restorer); #endif +#endif // !SANITIZER_EMSCRIPTEN #if SANITIZER_LINUX CHECK_TYPE_SIZE(__sysctl_args); @@ -1111,7 +1122,9 @@ CHECK_SIZE_AND_OFFSET(mntent, mnt_passno); #endif +#if !SANITIZER_EMSCRIPTEN CHECK_TYPE_SIZE(ether_addr); +#endif #if (SANITIZER_LINUX || SANITIZER_FREEBSD) && !SANITIZER_ANDROID CHECK_TYPE_SIZE(ipc_perm); @@ -1148,7 +1161,7 @@ CHECK_TYPE_SIZE(clockid_t); #endif -#if !SANITIZER_ANDROID +#if !SANITIZER_ANDROID && !SANITIZER_EMSCRIPTEN CHECK_TYPE_SIZE(ifaddrs); CHECK_SIZE_AND_OFFSET(ifaddrs, ifa_next); CHECK_SIZE_AND_OFFSET(ifaddrs, ifa_name); @@ -1178,7 +1191,7 @@ COMPILER_CHECK(sizeof(__sanitizer_struct_mallinfo) == sizeof(struct mallinfo)); #endif -#if !SANITIZER_ANDROID +#if !SANITIZER_ANDROID && !SANITIZER_EMSCRIPTEN CHECK_TYPE_SIZE(timeb); CHECK_SIZE_AND_OFFSET(timeb, time); CHECK_SIZE_AND_OFFSET(timeb, millitm); diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp @@ -220,6 +220,14 @@ return (end1 < start2) || (end2 < start1); } +#if SANITIZER_EMSCRIPTEN +bool MemoryRangeIsAvailable(uptr /*range_start*/, uptr /*range_end*/) { + // TODO: actually implement this. + return true; +} + +void DumpProcessMap() { Report("Cannot dump memory map on emscripten"); } +#else // FIXME: this is thread-unsafe, but should not cause problems most of the time. // When the shadow is mapped only a single thread usually exists (plus maybe // several worker threads on Mac, which aren't expected to map big chunks of @@ -252,6 +260,7 @@ Report("End of process memory map.\n"); UnmapOrDie(filename, kBufSize); } +#endif const char *GetPwd() { return GetEnv("PWD"); @@ -272,6 +281,10 @@ } bool GetCodeRangeForFile(const char *module, uptr *start, uptr *end) { +#if SANITIZER_EMSCRIPTEN + // Code is not mapped in memory in Emscripten, so this operation is + // meaningless and thus always fails. +#else MemoryMappingLayout proc_maps(/*cache_enabled*/false); InternalScopedString buff(kMaxPathLength); MemoryMappedSegment segment(buff.data(), kMaxPathLength); @@ -283,6 +296,7 @@ return true; } } +#endif return false; } diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp @@ -215,7 +215,9 @@ // Set the alternate signal stack for the main thread. // This will cause SetAlternateSignalStack to be called twice, but the stack // will be actually set only once. +#if !SANITIZER_EMSCRIPTEN if (common_flags()->use_sigaltstack) SetAlternateSignalStack(); +#endif MaybeInstallSigaction(SIGSEGV, handler); MaybeInstallSigaction(SIGBUS, handler); MaybeInstallSigaction(SIGABRT, handler); @@ -301,7 +303,9 @@ // to read the file mappings from /proc/self/maps. Luckily, neither the // process will be able to load additional libraries, so it's fine to use the // cached mappings. +#if !SANITIZER_EMSCRIPTEN MemoryMappingLayout::CacheMemoryMappings(); +#endif } bool MmapFixedNoReserve(uptr fixed_addr, uptr size, const char *name) { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h @@ -33,7 +33,8 @@ // Fast unwind is the only option on Mac for now; we will need to // revisit this macro when slow unwind works on Mac, see // https://github.com/google/sanitizers/issues/137 -#if SANITIZER_MAC || SANITIZER_OPENBSD || SANITIZER_RTEMS +#if SANITIZER_MAC || SANITIZER_OPENBSD || SANITIZER_RTEMS || \ + SANITIZER_EMSCRIPTEN # define SANITIZER_CAN_SLOW_UNWIND 0 #else # define SANITIZER_CAN_SLOW_UNWIND 1 diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.cpp @@ -26,9 +26,11 @@ #endif } +#if !defined(__EMSCRIPTEN__) uptr StackTrace::GetCurrentPc() { return GET_CALLER_PC(); } +#endif void BufferedStackTrace::Init(const uptr *pcs, uptr cnt, uptr extra_top_pc) { size = cnt + !!extra_top_pc; @@ -39,8 +41,8 @@ top_frame_bp = 0; } -// Sparc implemention is in its own file. -#if !defined(__sparc__) +// Sparc and Emscripten implementions are in their own files. +#if !defined(__sparc__) && !defined(__EMSCRIPTEN__) // In GCC on ARM bp points to saved lr, not fp, so we should check the next // cell in stack to be a saved frame pointer. GetCanonicFrame returns the 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 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_emscripten.cpp @@ -0,0 +1,43 @@ +//===-- sanitizer_stacktrace_emscripten.cpp +//--------------------------------===// +// +// 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" + +namespace __sanitizer { + +extern "C" { +uptr emscripten_stack_snapshot(); +uptr emscripten_return_address(int level); +u32 emscripten_stack_unwind_buffer(uptr pc, uptr *buffer, u32 depth); +} + +uptr StackTrace::GetCurrentPc() { return emscripten_stack_snapshot(); } + +void BufferedStackTrace::FastUnwindStack(uptr pc, uptr bp, uptr stack_top, + uptr stack_bottom, u32 max_depth) { + bool saw_pc = false; + 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_stacktrace_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp @@ -28,9 +28,15 @@ int dedup_frames = common_flags()->dedup_token_length; uptr frame_num = 0; for (uptr i = 0; i < size && trace[i]; i++) { +#if !SANITIZER_EMSCRIPTEN // PCs in stack traces are actually the return addresses, that is, // addresses of the next instructions after the call. uptr pc = GetPreviousInstructionPc(trace[i]); +#else + // On Emscripten, the stack traces are obtained from JavaScript, and the + // addresses are not return addresses. + uptr pc = trace[i]; +#endif SymbolizedStack *frames = Symbolizer::GetOrInit()->SymbolizePC(pc); CHECK(frames); for (SymbolizedStack *cur = frames; cur; cur = cur->next) { 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 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_emscripten.cpp @@ -0,0 +1,79 @@ +//===-- sanitizer_symbolizer_emscripten.cpp +//--------------------------------===// +// +// 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" + +namespace __sanitizer { + +extern "C" { +const char *emscripten_pc_get_function(uptr pc); +const char *emscripten_pc_get_file(uptr pc); +int emscripten_pc_get_line(uptr pc); +int emscripten_pc_get_column(uptr pc); +} + +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/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp @@ -12,7 +12,7 @@ //===----------------------------------------------------------------------===// #include "sanitizer_platform.h" -#if SANITIZER_POSIX +#if SANITIZER_POSIX && !SANITIZER_EMSCRIPTEN #include "sanitizer_allocator_internal.h" #include "sanitizer_common.h" #include "sanitizer_file.h" diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cpp @@ -39,7 +39,21 @@ } #endif -#if !SANITIZER_FUCHSIA +#if SANITIZER_EMSCRIPTEN +#include + +static INLINE bool ReportSupportsColors() { + return !!EM_ASM_INT({ + var setting = Module['printWithColors']; + if (setting != null) { + return setting; + } else { + return ENVIRONMENT_IS_NODE && process.stderr.isTTY; + } + }); +} + +#elif !SANITIZER_FUCHSIA bool ReportFile::SupportsColors() { SpinMutexLock l(mu); @@ -56,7 +70,7 @@ // Fuchsia's logs always go through post-processing that handles colorization. static INLINE bool ReportSupportsColors() { return true; } -#endif // !SANITIZER_FUCHSIA +#endif // SANITIZER_EMSCRIPTEN, !SANITIZER_FUCHSIA bool ColorizeReports() { // FIXME: Add proper Windows support to AnsiColorDecorator and re-enable color diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_syscall_emscripten.inc b/compiler-rt/lib/sanitizer_common/sanitizer_syscall_emscripten.inc new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_syscall_emscripten.inc @@ -0,0 +1,64 @@ +#define __scc(X) ((long) (X)) + +#define __syscall_emscripten(n, ...) __syscall##n(n, ##__VA_ARGS__) +#define __syscall_emscripten0(n) __syscall_emscripten(n) +#define __syscall_emscripten1(n,a) __syscall_emscripten(n,__scc(a)) +#define __syscall_emscripten2(n,a,b) __syscall_emscripten(n,__scc(a),__scc(b)) +#define __syscall_emscripten3(n,a,b,c) __syscall_emscripten(n,__scc(a),__scc(b),__scc(c)) +#define __syscall_emscripten4(n,a,b,c,d) __syscall_emscripten(n,__scc(a),__scc(b),__scc(c),__scc(d)) +#define __syscall_emscripten5(n,a,b,c,d,e) __syscall_emscripten(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e)) +#define __syscall_emscripten6(n,a,b,c,d,e,f) __syscall_emscripten(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f)) +#define __syscall_emscripten7(n,a,b,c,d,e,f,g) __syscall_emscripten(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f),__scc(g)) + +#define __SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n +#define __SYSCALL_NARGS(...) __SYSCALL_NARGS_X(__VA_ARGS__,7,6,5,4,3,2,1,0,) +#define __SYSCALL_CONCAT_X(a,b) a##b +#define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X(a,b) +#define __SYSCALL_DISP(b,...) __SYSCALL_CONCAT(b,__SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__) +#define internal_syscall(...) __SYSCALL_DISP(__syscall_emscripten,__VA_ARGS__) + +#define DECLARE_SYSCALL2(x) extern "C" long __syscall##x(int which, ...) +#define DECLARE_SYSCALL1(x) DECLARE_SYSCALL2(x) +#define DECLARE_SYSCALL(x) DECLARE_SYSCALL1(SYS_##x) + +DECLARE_SYSCALL(dup); +DECLARE_SYSCALL(mmap2); +DECLARE_SYSCALL(munmap); +DECLARE_SYSCALL(mprotect); +DECLARE_SYSCALL(close); +DECLARE_SYSCALL(open); +DECLARE_SYSCALL(read); +DECLARE_SYSCALL(write); +DECLARE_SYSCALL(ftruncate); +DECLARE_SYSCALL(stat64); +DECLARE_SYSCALL(lstat64); +DECLARE_SYSCALL(fstat64); +DECLARE_SYSCALL(dup2); +DECLARE_SYSCALL(readlink); +DECLARE_SYSCALL(unlink); +DECLARE_SYSCALL(rename); +DECLARE_SYSCALL(exit_group); +DECLARE_SYSCALL(nanosleep); +DECLARE_SYSCALL(execve); +DECLARE_SYSCALL(gettid); +DECLARE_SYSCALL(getpid); +DECLARE_SYSCALL(getppid); +DECLARE_SYSCALL(getdents); +DECLARE_SYSCALL(wait4); +DECLARE_SYSCALL(lseek); +DECLARE_SYSCALL(sigaltstack); +DECLARE_SYSCALL(ptrace); +DECLARE_SYSCALL(tgkill); +DECLARE_SYSCALL(gettimeofday); +DECLARE_SYSCALL(rt_sigprocmask); + +#define SYSCALL(x) SYS_##x + +bool internal_iserror(uptr retval, int *rverrno) { + if (retval >= (uptr)-4095) { + if (rverrno) + *rverrno = -retval; + return true; + } + return false; +} diff --git a/compiler-rt/lib/ubsan/ubsan_flags.cpp b/compiler-rt/lib/ubsan/ubsan_flags.cpp --- a/compiler-rt/lib/ubsan/ubsan_flags.cpp +++ b/compiler-rt/lib/ubsan/ubsan_flags.cpp @@ -55,7 +55,11 @@ CommonFlags cf; cf.CopyFrom(*common_flags()); cf.print_summary = false; +#if !SANITIZER_EMSCRIPTEN + // getenv on emscripten uses malloc, which we can't when using some + // sanitizers. You can't run external symbolizers anyway. cf.external_symbolizer_path = GetFlag("UBSAN_SYMBOLIZER_PATH"); +#endif OverrideCommonFlags(cf); } diff --git a/compiler-rt/lib/ubsan/ubsan_platform.h b/compiler-rt/lib/ubsan/ubsan_platform.h --- a/compiler-rt/lib/ubsan/ubsan_platform.h +++ b/compiler-rt/lib/ubsan/ubsan_platform.h @@ -14,9 +14,9 @@ // Other platforms should be easy to add, and probably work as-is. #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) || \ - defined(__NetBSD__) || defined(__OpenBSD__) || \ - (defined(__sun__) && defined(__svr4__)) || \ - defined(_WIN32) || defined(__Fuchsia__) || defined(__rtems__) + defined(__NetBSD__) || defined(__OpenBSD__) || \ + (defined(__sun__) && defined(__svr4__)) || defined(_WIN32) || \ + defined(__Fuchsia__) || defined(__rtems__) || defined(__EMSCRIPTEN__) # define CAN_SANITIZE_UB 1 #else # define CAN_SANITIZE_UB 0 diff --git a/compiler-rt/lib/ubsan/ubsan_signals_standalone.cpp b/compiler-rt/lib/ubsan/ubsan_signals_standalone.cpp --- a/compiler-rt/lib/ubsan/ubsan_signals_standalone.cpp +++ b/compiler-rt/lib/ubsan/ubsan_signals_standalone.cpp @@ -12,11 +12,6 @@ #include "ubsan_platform.h" #include "sanitizer_common/sanitizer_platform.h" -#if CAN_SANITIZE_UB -#include "interception/interception.h" -#include "sanitizer_common/sanitizer_stacktrace.h" -#include "ubsan_diag.h" -#include "ubsan_init.h" // Interception of signals breaks too many things on Android. // * It requires that ubsan is the first dependency of the main executable for @@ -26,13 +21,18 @@ // debuggerd handler, but before the ART handler. // * Interceptors don't work at all when ubsan runtime is loaded late, ex. when // it is part of an APK that does not use wrap.sh method. -#if SANITIZER_FUCHSIA || SANITIZER_ANDROID +#if SANITIZER_FUCHSIA || SANITIZER_ANDROID || SANITIZER_EMSCRIPTEN namespace __ubsan { void InitializeDeadlySignals() {} } -#else +#elif CAN_SANITIZE_UB + +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "ubsan_diag.h" +#include "ubsan_init.h" #define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name) #include "sanitizer_common/sanitizer_signal_interceptors.inc" @@ -65,6 +65,4 @@ } // namespace __ubsan -#endif - #endif // CAN_SANITIZE_UB