diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/tools | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/tools')
18 files changed, 1840 insertions, 43 deletions
diff --git a/Source/JavaScriptCore/tools/CodeProfile.cpp b/Source/JavaScriptCore/tools/CodeProfile.cpp index b99df2c66..c8d580aa1 100644 --- a/Source/JavaScriptCore/tools/CodeProfile.cpp +++ b/Source/JavaScriptCore/tools/CodeProfile.cpp @@ -30,7 +30,6 @@ #include "CodeProfiling.h" #include "LinkBuffer.h" #include "ProfileTreeNode.h" -#include <wtf/Vector.h> #include <wtf/text/WTFString.h> #if OS(DARWIN) @@ -143,7 +142,7 @@ void CodeProfile::sample(void* pc, void** framePointer) void CodeProfile::report() { - dataLogF("<CodeProfiling %s:%d>\n", m_file.data(), m_lineNo); + dataLogF("<CodeProfiling %s:%d>\n", m_file.data(), m_lineNumber); // How many frames of C-code to print - 0, if not verbose, 1 if verbose, up to 1024 if very verbose. unsigned recursionLimit = CodeProfiling::beVeryVerbose() ? 1024 : CodeProfiling::beVerbose(); @@ -186,7 +185,7 @@ void CodeProfile::report() for (size_t i = 0 ; i < m_children.size(); ++i) m_children[i]->report(); - dataLogF("</CodeProfiling %s:%d>\n", m_file.data(), m_lineNo); + dataLogF("</CodeProfiling %s:%d>\n", m_file.data(), m_lineNumber); } } diff --git a/Source/JavaScriptCore/tools/CodeProfile.h b/Source/JavaScriptCore/tools/CodeProfile.h index 6b0a30689..4fa357d50 100644 --- a/Source/JavaScriptCore/tools/CodeProfile.h +++ b/Source/JavaScriptCore/tools/CodeProfile.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CodeProfile_h -#define CodeProfile_h +#pragma once #include "SourceCode.h" #include "TieredMMapArray.h" @@ -37,11 +36,11 @@ class CodeProfile { public: CodeProfile(const SourceCode& source, CodeProfile* parent) : m_file(source.provider()->url().utf8()) - , m_lineNo(source.firstLine()) + , m_lineNumber(source.firstLine().oneBasedInt()) , m_parent(parent) { if (parent) - parent->addChild(this); + parent->addChild(std::unique_ptr<CodeProfile>(this)); } void sample(void* pc, void** framePointer); @@ -52,9 +51,9 @@ public: return m_parent; } - void addChild(CodeProfile* child) + void addChild(std::unique_ptr<CodeProfile> child) { - m_children.append(adoptPtr(child)); + m_children.append(WTFMove(child)); } private: @@ -80,15 +79,12 @@ private: }; CString m_file; - unsigned m_lineNo; + unsigned m_lineNumber; CodeProfile* m_parent; - Vector< OwnPtr<CodeProfile>> m_children; + Vector<std::unique_ptr<CodeProfile>> m_children; TieredMMapArray<CodeRecord> m_samples; static const char* s_codeTypeNames[NumberOfCodeTypes]; }; -} - -#endif // CodeProfile_h - +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/CodeProfiling.cpp b/Source/JavaScriptCore/tools/CodeProfiling.cpp index 740595e3e..f916c203b 100644 --- a/Source/JavaScriptCore/tools/CodeProfiling.cpp +++ b/Source/JavaScriptCore/tools/CodeProfiling.cpp @@ -33,7 +33,7 @@ #include <signal.h> #endif -#if OS(LINUX) +#if OS(LINUX) || OS(DARWIN) #include <sys/time.h> #endif @@ -48,7 +48,7 @@ WTF::MetaAllocatorTracker* CodeProfiling::s_tracker = 0; #pragma clang diagnostic ignored "-Wmissing-noreturn" #endif -#if (PLATFORM(MAC) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) +#if (OS(DARWIN) && !PLATFORM(GTK) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) // Helper function to start & stop the timer. // Presently we're using the wall-clock timer, since this seems to give the best results. static void setProfileTimer(unsigned usec) @@ -66,7 +66,7 @@ static void setProfileTimer(unsigned usec) #pragma clang diagnostic pop #endif -#if PLATFORM(MAC) && CPU(X86_64) +#if OS(DARWIN) && !PLATFORM(GTK) && CPU(X86_64) static void profilingTimer(int, siginfo_t*, void* uap) { mcontext_t context = static_cast<ucontext_t*>(uap)->uc_mcontext; @@ -92,7 +92,6 @@ void CodeProfiling::sample(void* pc, void** framePointer) void CodeProfiling::notifyAllocator(WTF::MetaAllocator* allocator) { -#if !OS(WINCE) // Check for JSC_CODE_PROFILING. const char* codeProfilingMode = getenv("JSC_CODE_PROFILING"); if (!codeProfilingMode) @@ -119,7 +118,6 @@ void CodeProfiling::notifyAllocator(WTF::MetaAllocator* allocator) ASSERT(!s_tracker); s_tracker = new WTF::MetaAllocatorTracker(); allocator->trackAllocations(s_tracker); -#endif } void* CodeProfiling::getOwnerUIDForPC(void* address) @@ -143,7 +141,7 @@ void CodeProfiling::begin(const SourceCode& source) if (alreadyProfiling) return; -#if (PLATFORM(MAC) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) +#if (OS(DARWIN) && !PLATFORM(GTK) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) // Regsiter a signal handler & itimer. struct sigaction action; action.sa_sigaction = reinterpret_cast<void (*)(int, siginfo_t *, void *)>(profilingTimer); @@ -167,7 +165,7 @@ void CodeProfiling::end() if (s_profileStack) return; -#if (PLATFORM(MAC) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) +#if (OS(DARWIN) && !PLATFORM(GTK) && CPU(X86_64)) || (OS(LINUX) && CPU(X86)) // Stop profiling setProfileTimer(0); #endif diff --git a/Source/JavaScriptCore/tools/CodeProfiling.h b/Source/JavaScriptCore/tools/CodeProfiling.h index 9b0f5daa3..311fcda68 100644 --- a/Source/JavaScriptCore/tools/CodeProfiling.h +++ b/Source/JavaScriptCore/tools/CodeProfiling.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CodeProfiling_h -#define CodeProfiling_h +#pragma once namespace WTF { @@ -79,7 +78,4 @@ private: static volatile CodeProfile* s_profileStack; }; -} - -#endif // CodeProfiling_h - +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/FunctionOverrides.cpp b/Source/JavaScriptCore/tools/FunctionOverrides.cpp new file mode 100644 index 000000000..ef8943592 --- /dev/null +++ b/Source/JavaScriptCore/tools/FunctionOverrides.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FunctionOverrides.h" + +#include <stdio.h> +#include <string.h> +#include <wtf/DataLog.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> +#include <wtf/text/StringHash.h> + +namespace JSC { + +/* + The overrides file defines function bodies that we will want to override with + a replacement for debugging purposes. The overrides file may contain + 'override' and 'with' clauses like these: + + // Example 1: function foo1(a) + override !@#$%{ print("In foo1"); }!@#$% + with abc{ + print("I am overridden"); + }abc + + // Example 2: function foo2(a) + override %%%{ + print("foo2's body has a string with }%% in it."); + // Because }%% appears in the function body here, we cannot use + // %% or % as the delimiter. %%% is ok though. + }%%% + with %%%{ + print("Overridden foo2"); + }%%% + + 1. Comments are lines starting with //. All comments will be ignored. + + 2. An 'override' clause is used to specify the original function body we + want to override. The with clause is used to specify the overriding + function body. + + An 'override' clause must be followed immediately by a 'with' clause. + + 3. An 'override' clause must be of the form: + override <delimiter>{...function body...}<delimiter> + + The override keyword must be at the start of the line. + + <delimiter> may be any string of any ASCII characters (except for '{', + '}', and whitespace characters) as long as the pattern of "}<delimiter>" + does not appear in the function body e.g. the override clause of Example 2 + above illustrates this. + + The start and end <delimiter> must be identical. + + The space between the override keyword and the start <delimiter> is + required. + + All characters between the pair of delimiters will be considered to + be part of the function body string. This allows us to also work + with script source that are multi-lined i.e. newlines are allowed. + + 4. A 'with' clause is identical in form to an 'override' clause except that + it uses the 'with' keyword instead of the 'override' keyword. + */ + +FunctionOverrides& FunctionOverrides::overrides() +{ + static LazyNeverDestroyed<FunctionOverrides> overrides; + static std::once_flag initializeListFlag; + std::call_once(initializeListFlag, [] { + const char* overridesFileName = Options::functionOverrides(); + overrides.construct(overridesFileName); + }); + return overrides; +} + +FunctionOverrides::FunctionOverrides(const char* overridesFileName) +{ + parseOverridesInFile(overridesFileName); +} + +void FunctionOverrides::reinstallOverrides() +{ + FunctionOverrides& overrides = FunctionOverrides::overrides(); + const char* overridesFileName = Options::functionOverrides(); + overrides.clear(); + overrides.parseOverridesInFile(overridesFileName); +} + +static void initializeOverrideInfo(const SourceCode& origCode, const String& newBody, FunctionOverrides::OverrideInfo& info) +{ + String origProviderStr = origCode.provider()->source().toString(); + unsigned origStart = origCode.startOffset(); + unsigned origFunctionStart = origProviderStr.reverseFind("function", origStart); + unsigned origBraceStart = origProviderStr.find("{", origStart); + unsigned headerLength = origBraceStart - origFunctionStart; + String origHeader = origProviderStr.substring(origFunctionStart, headerLength); + + String newProviderStr; + newProviderStr.append(origHeader); + newProviderStr.append(newBody); + + Ref<SourceProvider> newProvider = StringSourceProvider::create(newProviderStr, SourceOrigin { "<overridden>" }, "<overridden>"); + + info.firstLine = 1; + info.lineCount = 1; // Faking it. This doesn't really matter for now. + info.startColumn = 1; + info.endColumn = 1; // Faking it. This doesn't really matter for now. + info.parametersStartOffset = newProviderStr.find("("); + info.typeProfilingStartOffset = newProviderStr.find("{"); + info.typeProfilingEndOffset = newProviderStr.length() - 1; + + info.sourceCode = + SourceCode(WTFMove(newProvider), info.parametersStartOffset, info.typeProfilingEndOffset + 1, 1, 1); +} + +bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, FunctionOverrides::OverrideInfo& result) +{ + ASSERT(Options::functionOverrides()); + FunctionOverrides& overrides = FunctionOverrides::overrides(); + + String sourceString = origCode.view().toString(); + size_t sourceBodyStart = sourceString.find('{'); + if (sourceBodyStart == notFound) + return false; + String sourceBodyString = sourceString.substring(sourceBodyStart); + + auto it = overrides.m_entries.find(sourceBodyString); + if (it == overrides.m_entries.end()) + return false; + + initializeOverrideInfo(origCode, it->value, result); + return true; +} + +#define SYNTAX_ERROR "SYNTAX ERROR" +#define IO_ERROR "IO ERROR" +#define FAIL_WITH_ERROR(error, errorMessageInBrackets) \ + do { \ + dataLog("functionOverrides ", error, ": "); \ + dataLog errorMessageInBrackets; \ + exit(EXIT_FAILURE); \ + } while (false) + +static bool hasDisallowedCharacters(const char* str, size_t length) +{ + while (length--) { + char c = *str++; + // '{' is also disallowed, but we don't need to check for it because + // parseClause() searches for '{' as the end of the start delimiter. + // As a result, the parsed delimiter string will never include '{'. + if (c == '}' || isASCIISpace(c)) + return true; + } + return false; +} + +static String parseClause(const char* keyword, size_t keywordLength, FILE* file, const char* line, char* buffer, size_t bufferSize) +{ + const char* keywordPos = strstr(line, keyword); + if (!keywordPos) + FAIL_WITH_ERROR(SYNTAX_ERROR, ("Expecting '", keyword, "' clause:\n", line, "\n")); + if (keywordPos != line) + FAIL_WITH_ERROR(SYNTAX_ERROR, ("Cannot have any characters before '", keyword, "':\n", line, "\n")); + if (line[keywordLength] != ' ') + FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' must be followed by a ' ':\n", line, "\n")); + + const char* delimiterStart = &line[keywordLength + 1]; + const char* delimiterEnd = strstr(delimiterStart, "{"); + if (!delimiterEnd) + FAIL_WITH_ERROR(SYNTAX_ERROR, ("Missing { after '", keyword, "' clause start delimiter:\n", line, "\n")); + + size_t delimiterLength = delimiterEnd - delimiterStart; + String delimiter(delimiterStart, delimiterLength); + + if (hasDisallowedCharacters(delimiterStart, delimiterLength)) + FAIL_WITH_ERROR(SYNTAX_ERROR, ("Delimiter '", delimiter, "' cannot have '{', '}', or whitespace:\n", line, "\n")); + + String terminatorString; + terminatorString.append("}"); + terminatorString.append(delimiter); + + CString terminatorCString = terminatorString.ascii(); + const char* terminator = terminatorCString.data(); + line = delimiterEnd; // Start from the {. + + StringBuilder builder; + do { + const char* p = strstr(line, terminator); + if (p) { + if (p[strlen(terminator)] != '\n') + FAIL_WITH_ERROR(SYNTAX_ERROR, ("Unexpected characters after '", keyword, "' clause end delimiter '", delimiter, "':\n", line, "\n")); + + builder.append(line, p - line + 1); + return builder.toString(); + } + builder.append(line); + + } while ((line = fgets(buffer, bufferSize, file))); + + FAIL_WITH_ERROR(SYNTAX_ERROR, ("'", keyword, "' clause end delimiter '", delimiter, "' not found:\n", builder.toString(), "\n", "Are you missing a '}' before the delimiter?\n")); +} + +void FunctionOverrides::parseOverridesInFile(const char* fileName) +{ + if (!fileName) + return; + + FILE* file = fopen(fileName, "r"); + if (!file) + FAIL_WITH_ERROR(IO_ERROR, ("Failed to open file ", fileName, ". Did you add the file-read-data entitlement to WebProcess.sb?\n")); + + char* line; + char buffer[BUFSIZ]; + while ((line = fgets(buffer, sizeof(buffer), file))) { + if (strstr(line, "//") == line) + continue; + + if (line[0] == '\n' || line[0] == '\0') + continue; + + size_t keywordLength; + + keywordLength = sizeof("override") - 1; + String keyStr = parseClause("override", keywordLength, file, line, buffer, sizeof(buffer)); + + line = fgets(buffer, sizeof(buffer), file); + + keywordLength = sizeof("with") - 1; + String valueStr = parseClause("with", keywordLength, file, line, buffer, sizeof(buffer)); + + m_entries.add(keyStr, valueStr); + } + + int result = fclose(file); + if (result) + dataLogF("Failed to close file %s: %s\n", fileName, strerror(errno)); +} + +} // namespace JSC + diff --git a/Source/JavaScriptCore/tools/FunctionOverrides.h b/Source/JavaScriptCore/tools/FunctionOverrides.h new file mode 100644 index 000000000..3e5de199e --- /dev/null +++ b/Source/JavaScriptCore/tools/FunctionOverrides.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Options.h" +#include "SourceCode.h" +#include <wtf/HashMap.h> +#include <wtf/text/WTFString.h> + +namespace JSC { + +class ScriptExecutable; + +class FunctionOverrides { +public: + struct OverrideInfo { + SourceCode sourceCode; + unsigned firstLine; + unsigned lineCount; + unsigned startColumn; + unsigned endColumn; + unsigned parametersStartOffset; + unsigned typeProfilingStartOffset; + unsigned typeProfilingEndOffset; + }; + + static FunctionOverrides& overrides(); + FunctionOverrides(const char* functionOverridesFileName); + + static bool initializeOverrideFor(const SourceCode& origCode, OverrideInfo& result); + + JS_EXPORT_PRIVATE static void reinstallOverrides(); + +private: + void parseOverridesInFile(const char* fileName); + void clear() { m_entries.clear(); } + + HashMap<String, String> m_entries; +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/FunctionWhitelist.cpp b/Source/JavaScriptCore/tools/FunctionWhitelist.cpp new file mode 100644 index 000000000..c1ea36057 --- /dev/null +++ b/Source/JavaScriptCore/tools/FunctionWhitelist.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FunctionWhitelist.h" + +#if ENABLE(JIT) + +#include "CodeBlock.h" +#include "Options.h" +#include <stdio.h> +#include <string.h> + +namespace JSC { + +FunctionWhitelist::FunctionWhitelist(const char* filename) +{ + if (!filename) + return; + + FILE* f = fopen(filename, "r"); + if (!f) { + dataLogF("Failed to open file %s. Did you add the file-read-data entitlement to WebProcess.sb?\n", filename); + return; + } + + m_hasActiveWhitelist = true; + + char* line; + char buffer[BUFSIZ]; + while ((line = fgets(buffer, sizeof(buffer), f))) { + if (strstr(line, "//") == line) + continue; + + // Get rid of newlines at the ends of the strings. + size_t length = strlen(line); + if (line[length - 1] == '\n') { + line[length - 1] = '\0'; + length--; + } + + // Skip empty lines. + if (!length) + continue; + + m_entries.add(String(line, length)); + } + + int result = fclose(f); + if (result) + dataLogF("Failed to close file %s: %s\n", filename, strerror(errno)); +} + +bool FunctionWhitelist::contains(CodeBlock* codeBlock) const +{ + ASSERT(!isCompilationThread()); + if (!m_hasActiveWhitelist) + return true; + + if (m_entries.isEmpty()) + return false; + + String name = String::fromUTF8(codeBlock->inferredName()); + if (m_entries.contains(name)) + return true; + + String hash = String::fromUTF8(codeBlock->hashAsStringIfPossible()); + if (m_entries.contains(hash)) + return true; + + return m_entries.contains(name + '#' + hash); +} + +} // namespace JSC + +#endif // ENABLE(JIT) + diff --git a/Source/JavaScriptCore/tools/FunctionWhitelist.h b/Source/JavaScriptCore/tools/FunctionWhitelist.h new file mode 100644 index 000000000..b5e6e4f14 --- /dev/null +++ b/Source/JavaScriptCore/tools/FunctionWhitelist.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/HashSet.h> +#include <wtf/text/WTFString.h> + +namespace JSC { + +class CodeBlock; + +class FunctionWhitelist { +public: + explicit FunctionWhitelist(const char*); + + bool contains(CodeBlock*) const; + +private: + HashSet<String> m_entries; + bool m_hasActiveWhitelist { false }; +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp new file mode 100644 index 000000000..ae389731d --- /dev/null +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSDollarVM.h" + +#include "JSCInlines.h" + +namespace JSC { + +const ClassInfo JSDollarVM::s_info = { "DollarVM", &Base::s_info, 0, CREATE_METHOD_TABLE(JSDollarVM) }; + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/JSDollarVM.h b/Source/JavaScriptCore/tools/JSDollarVM.h new file mode 100644 index 000000000..22675772b --- /dev/null +++ b/Source/JavaScriptCore/tools/JSDollarVM.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "JSObject.h" + +namespace JSC { + +class JSDollarVM : public JSNonFinalObject { +public: + typedef JSNonFinalObject Base; + + DECLARE_EXPORT_INFO; + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + } + + static JSDollarVM* create(VM& vm, Structure* structure) + { + JSDollarVM* instance = new (NotNull, allocateCell<JSDollarVM>(vm.heap)) JSDollarVM(vm, structure); + instance->finishCreation(vm); + return instance; + } + +private: + JSDollarVM(VM& vm, Structure* structure) + : Base(vm, structure) + { + } +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/JSDollarVMPrototype.cpp b/Source/JavaScriptCore/tools/JSDollarVMPrototype.cpp new file mode 100644 index 000000000..95b422c4e --- /dev/null +++ b/Source/JavaScriptCore/tools/JSDollarVMPrototype.cpp @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSDollarVMPrototype.h" + +#include "CodeBlock.h" +#include "FunctionCodeBlock.h" +#include "Heap.h" +#include "HeapIterationScope.h" +#include "JSCInlines.h" +#include "JSFunction.h" +#include "MarkedSpaceInlines.h" +#include "StackVisitor.h" +#include <wtf/DataLog.h> +#include <wtf/StringPrintStream.h> + +namespace JSC { + +const ClassInfo JSDollarVMPrototype::s_info = { "DollarVMPrototype", &Base::s_info, 0, CREATE_METHOD_TABLE(JSDollarVMPrototype) }; + + +bool JSDollarVMPrototype::currentThreadOwnsJSLock(ExecState* exec) +{ + return exec->vm().apiLock().currentThreadIsHoldingLock(); +} + +static bool ensureCurrentThreadOwnsJSLock(ExecState* exec) +{ + if (JSDollarVMPrototype::currentThreadOwnsJSLock(exec)) + return true; + dataLog("ERROR: current thread does not own the JSLock\n"); + return false; +} + +void JSDollarVMPrototype::addFunction(VM& vm, JSGlobalObject* globalObject, const char* name, NativeFunction function, unsigned arguments) +{ + Identifier identifier = Identifier::fromString(&vm, name); + putDirect(vm, identifier, JSFunction::create(vm, globalObject, arguments, identifier.string(), function)); +} + +static EncodedJSValue JSC_HOST_CALL functionCrash(ExecState*) +{ + CRASH(); + return JSValue::encode(jsUndefined()); +} + +static EncodedJSValue JSC_HOST_CALL functionDFGTrue(ExecState*) +{ + return JSValue::encode(jsBoolean(false)); +} + +class CallerFrameJITTypeFunctor { +public: + CallerFrameJITTypeFunctor() + : m_currentFrame(0) + , m_jitType(JITCode::None) + { + } + + StackVisitor::Status operator()(StackVisitor& visitor) const + { + if (m_currentFrame++ > 1) { + m_jitType = visitor->codeBlock()->jitType(); + return StackVisitor::Done; + } + return StackVisitor::Continue; + } + + JITCode::JITType jitType() { return m_jitType; } + +private: + mutable unsigned m_currentFrame; + mutable JITCode::JITType m_jitType; +}; + +static EncodedJSValue JSC_HOST_CALL functionLLintTrue(ExecState* exec) +{ + if (!exec) + return JSValue::encode(jsUndefined()); + CallerFrameJITTypeFunctor functor; + exec->iterate(functor); + return JSValue::encode(jsBoolean(functor.jitType() == JITCode::InterpreterThunk)); +} + +static EncodedJSValue JSC_HOST_CALL functionJITTrue(ExecState* exec) +{ + if (!exec) + return JSValue::encode(jsUndefined()); + CallerFrameJITTypeFunctor functor; + exec->iterate(functor); + return JSValue::encode(jsBoolean(functor.jitType() == JITCode::BaselineJIT)); +} + +void JSDollarVMPrototype::gc(ExecState* exec) +{ + if (!ensureCurrentThreadOwnsJSLock(exec)) + return; + exec->heap()->collectAllGarbage(); +} + +static EncodedJSValue JSC_HOST_CALL functionGC(ExecState* exec) +{ + JSDollarVMPrototype::gc(exec); + return JSValue::encode(jsUndefined()); +} + +void JSDollarVMPrototype::edenGC(ExecState* exec) +{ + if (!ensureCurrentThreadOwnsJSLock(exec)) + return; + exec->heap()->collectSync(CollectionScope::Eden); +} + +static EncodedJSValue JSC_HOST_CALL functionEdenGC(ExecState* exec) +{ + JSDollarVMPrototype::edenGC(exec); + return JSValue::encode(jsUndefined()); +} + +bool JSDollarVMPrototype::isInHeap(Heap* heap, void* ptr) +{ + return isInObjectSpace(heap, ptr) || isInStorageSpace(heap, ptr); +} + +bool JSDollarVMPrototype::isInObjectSpace(Heap* heap, void* ptr) +{ + MarkedBlock* candidate = MarkedBlock::blockFor(ptr); + if (heap->objectSpace().blocks().set().contains(candidate)) + return true; + for (LargeAllocation* allocation : heap->objectSpace().largeAllocations()) { + if (allocation->contains(ptr)) + return true; + } + return false; +} + +bool JSDollarVMPrototype::isInStorageSpace(Heap*, void*) +{ + // FIXME: Do something with this. + // https://bugs.webkit.org/show_bug.cgi?id=161753 + return false; +} + +struct CellAddressCheckFunctor : MarkedBlock::CountFunctor { + CellAddressCheckFunctor(JSCell* candidate) + : candidate(candidate) + { + } + + IterationStatus operator()(HeapCell* cell, HeapCell::Kind) const + { + if (cell == candidate) { + found = true; + return IterationStatus::Done; + } + return IterationStatus::Continue; + } + + JSCell* candidate; + mutable bool found { false }; +}; + +bool JSDollarVMPrototype::isValidCell(Heap* heap, JSCell* candidate) +{ + HeapIterationScope iterationScope(*heap); + CellAddressCheckFunctor functor(candidate); + heap->objectSpace().forEachLiveCell(iterationScope, functor); + return functor.found; +} + +bool JSDollarVMPrototype::isValidCodeBlock(ExecState* exec, CodeBlock* candidate) +{ + if (!ensureCurrentThreadOwnsJSLock(exec)) + return false; + + struct CodeBlockValidationFunctor { + CodeBlockValidationFunctor(CodeBlock* candidate) + : candidate(candidate) + { + } + + bool operator()(CodeBlock* codeBlock) const + { + if (codeBlock == candidate) + found = true; + return found; + } + + CodeBlock* candidate; + mutable bool found { false }; + }; + + VM& vm = exec->vm(); + CodeBlockValidationFunctor functor(candidate); + vm.heap.forEachCodeBlock(functor); + return functor.found; +} + +CodeBlock* JSDollarVMPrototype::codeBlockForFrame(CallFrame* topCallFrame, unsigned frameNumber) +{ + if (!ensureCurrentThreadOwnsJSLock(topCallFrame)) + return nullptr; + + if (!topCallFrame) + return nullptr; + + struct FetchCodeBlockFunctor { + public: + FetchCodeBlockFunctor(unsigned targetFrameNumber) + : targetFrame(targetFrameNumber) + { + } + + StackVisitor::Status operator()(StackVisitor& visitor) const + { + currentFrame++; + if (currentFrame == targetFrame) { + codeBlock = visitor->codeBlock(); + return StackVisitor::Done; + } + return StackVisitor::Continue; + } + + unsigned targetFrame; + mutable unsigned currentFrame { 0 }; + mutable CodeBlock* codeBlock { nullptr }; + }; + + FetchCodeBlockFunctor functor(frameNumber); + topCallFrame->iterate(functor); + return functor.codeBlock; +} + +static EncodedJSValue JSC_HOST_CALL functionCodeBlockForFrame(ExecState* exec) +{ + if (exec->argumentCount() < 1) + return JSValue::encode(jsUndefined()); + + JSValue value = exec->uncheckedArgument(0); + if (!value.isUInt32()) + return JSValue::encode(jsUndefined()); + + // We need to inc the frame number because the caller would consider + // its own frame as frame 0. Hence, we need discount the frame for this + // function. + unsigned frameNumber = value.asUInt32() + 1; + CodeBlock* codeBlock = JSDollarVMPrototype::codeBlockForFrame(exec, frameNumber); + // Though CodeBlock is a JSCell, it is not safe to return it directly back to JS code + // as it is an internal type that the JS code cannot handle. Hence, we first encode the + // CodeBlock* as a double token (which is safe for JS code to handle) before returning it. + return JSValue::encode(JSValue(bitwise_cast<double>(static_cast<uint64_t>(reinterpret_cast<uintptr_t>(codeBlock))))); +} + +static CodeBlock* codeBlockFromArg(ExecState* exec) +{ + VM& vm = exec->vm(); + if (exec->argumentCount() < 1) + return nullptr; + + JSValue value = exec->uncheckedArgument(0); + CodeBlock* candidateCodeBlock = nullptr; + if (value.isCell()) { + JSFunction* func = jsDynamicCast<JSFunction*>(vm, value.asCell()); + if (func) { + if (func->isHostFunction()) + candidateCodeBlock = nullptr; + else + candidateCodeBlock = func->jsExecutable()->eitherCodeBlock(); + } + } else if (value.isDouble()) { + // If the value is a double, it may be an encoded CodeBlock* that came from + // $vm.codeBlockForFrame(). We'll treat it as a candidate codeBlock and check if it's + // valid below before using. + candidateCodeBlock = reinterpret_cast<CodeBlock*>(bitwise_cast<uint64_t>(value.asDouble())); + } + + if (candidateCodeBlock && JSDollarVMPrototype::isValidCodeBlock(exec, candidateCodeBlock)) + return candidateCodeBlock; + + if (candidateCodeBlock) + dataLog("Invalid codeBlock: ", RawPointer(candidateCodeBlock), " ", value, "\n"); + else + dataLog("Invalid codeBlock: ", value, "\n"); + return nullptr; +} + +static EncodedJSValue JSC_HOST_CALL functionCodeBlockFor(ExecState* exec) +{ + CodeBlock* codeBlock = codeBlockFromArg(exec); + WTF::StringPrintStream stream; + if (codeBlock) { + stream.print(*codeBlock); + return JSValue::encode(jsString(exec, stream.toString())); + } + return JSValue::encode(jsUndefined()); +} + +static EncodedJSValue JSC_HOST_CALL functionPrintSourceFor(ExecState* exec) +{ + CodeBlock* codeBlock = codeBlockFromArg(exec); + if (codeBlock) + codeBlock->dumpSource(); + return JSValue::encode(jsUndefined()); +} + +static EncodedJSValue JSC_HOST_CALL functionPrintByteCodeFor(ExecState* exec) +{ + CodeBlock* codeBlock = codeBlockFromArg(exec); + if (codeBlock) + codeBlock->dumpBytecode(); + return JSValue::encode(jsUndefined()); +} + +static EncodedJSValue JSC_HOST_CALL functionPrint(ExecState* exec) +{ + auto scope = DECLARE_THROW_SCOPE(exec->vm()); + for (unsigned i = 0; i < exec->argumentCount(); ++i) { + String argStr = exec->uncheckedArgument(i).toWTFString(exec); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + dataLog(argStr); + } + return JSValue::encode(jsUndefined()); +} + +class PrintFrameFunctor { +public: + enum Action { + PrintOne, + PrintAll + }; + + PrintFrameFunctor(Action action, unsigned framesToSkip) + : m_action(action) + , m_framesToSkip(framesToSkip) + { + } + + StackVisitor::Status operator()(StackVisitor& visitor) const + { + m_currentFrame++; + if (m_currentFrame > m_framesToSkip) { + visitor->dump(WTF::dataFile(), Indenter(2), [&] (PrintStream& out) { + out.print("[", (m_currentFrame - m_framesToSkip - 1), "] "); + }); + } + if (m_action == PrintOne && m_currentFrame > m_framesToSkip) + return StackVisitor::Done; + return StackVisitor::Continue; + } + +private: + Action m_action; + unsigned m_framesToSkip; + mutable unsigned m_currentFrame { 0 }; +}; + +static void printCallFrame(CallFrame* callFrame, unsigned framesToSkip) +{ + if (!ensureCurrentThreadOwnsJSLock(callFrame)) + return; + PrintFrameFunctor functor(PrintFrameFunctor::PrintOne, framesToSkip); + callFrame->iterate(functor); +} + +void JSDollarVMPrototype::printCallFrame(CallFrame* callFrame) +{ + JSC::printCallFrame(callFrame, 0); +} + +static void printStack(CallFrame* topCallFrame, unsigned framesToSkip) +{ + if (!ensureCurrentThreadOwnsJSLock(topCallFrame)) + return; + if (!topCallFrame) + return; + PrintFrameFunctor functor(PrintFrameFunctor::PrintAll, framesToSkip); + topCallFrame->iterate(functor); +} + +void JSDollarVMPrototype::printStack(CallFrame* topCallFrame) +{ + JSC::printStack(topCallFrame, 0); +} + +static EncodedJSValue JSC_HOST_CALL functionPrintCallFrame(ExecState* exec) +{ + // When the callers call this function, they are expecting to print their + // own frame. So skip 1 for this frame. + printCallFrame(exec, 1); + return JSValue::encode(jsUndefined()); +} + +static EncodedJSValue JSC_HOST_CALL functionPrintStack(ExecState* exec) +{ + // When the callers call this function, they are expecting to print the + // stack starting their own frame. So skip 1 for this frame. + printStack(exec, 1); + return JSValue::encode(jsUndefined()); +} + +void JSDollarVMPrototype::printValue(JSValue value) +{ + dataLog(value); +} + +static EncodedJSValue JSC_HOST_CALL functionValue(ExecState* exec) +{ + WTF::StringPrintStream stream; + for (unsigned i = 0; i < exec->argumentCount(); ++i) { + if (i) + stream.print(", "); + stream.print(exec->uncheckedArgument(i)); + } + + return JSValue::encode(jsString(exec, stream.toString())); +} + +#if !PLATFORM(WIN) +static EncodedJSValue JSC_HOST_CALL functionGetPID(ExecState*) +{ + return JSValue::encode(jsNumber(getpid())); +} +#endif + +void JSDollarVMPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + + addFunction(vm, globalObject, "crash", functionCrash, 0); + + putDirectNativeFunction(vm, globalObject, Identifier::fromString(&vm, "dfgTrue"), 0, functionDFGTrue, DFGTrueIntrinsic, DontEnum); + + addFunction(vm, globalObject, "llintTrue", functionLLintTrue, 0); + addFunction(vm, globalObject, "jitTrue", functionJITTrue, 0); + + addFunction(vm, globalObject, "gc", functionGC, 0); + addFunction(vm, globalObject, "edenGC", functionEdenGC, 0); + + addFunction(vm, globalObject, "codeBlockFor", functionCodeBlockFor, 1); + addFunction(vm, globalObject, "codeBlockForFrame", functionCodeBlockForFrame, 1); + addFunction(vm, globalObject, "printSourceFor", functionPrintSourceFor, 1); + addFunction(vm, globalObject, "printByteCodeFor", functionPrintByteCodeFor, 1); + + addFunction(vm, globalObject, "print", functionPrint, 1); + addFunction(vm, globalObject, "printCallFrame", functionPrintCallFrame, 0); + addFunction(vm, globalObject, "printStack", functionPrintStack, 0); + + addFunction(vm, globalObject, "value", functionValue, 1); +#if !PLATFORM(WIN) + addFunction(vm, globalObject, "getpid", functionGetPID, 0); +#endif +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/JSDollarVMPrototype.h b/Source/JavaScriptCore/tools/JSDollarVMPrototype.h new file mode 100644 index 000000000..788e26790 --- /dev/null +++ b/Source/JavaScriptCore/tools/JSDollarVMPrototype.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "JSObject.h" + +namespace JSC { + +class Heap; + +class JSDollarVMPrototype : public JSNonFinalObject { +public: + typedef JSNonFinalObject Base; + + DECLARE_INFO; + + static JSDollarVMPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) + { + JSDollarVMPrototype* prototype = new (NotNull, allocateCell<JSDollarVMPrototype>(vm.heap)) JSDollarVMPrototype(vm, structure); + prototype->finishCreation(vm, globalObject); + return prototype; + } + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + } + + // The following are exported because they are designed to be callable from + // lldb. The JS versions are implemented on top of these. + + JS_EXPORT_PRIVATE static bool currentThreadOwnsJSLock(ExecState*); + JS_EXPORT_PRIVATE static void gc(ExecState*); + JS_EXPORT_PRIVATE static void edenGC(ExecState*); + JS_EXPORT_PRIVATE static bool isInHeap(Heap*, void*); + JS_EXPORT_PRIVATE static bool isInObjectSpace(Heap*, void*); + JS_EXPORT_PRIVATE static bool isInStorageSpace(Heap*, void*); + JS_EXPORT_PRIVATE static bool isValidCell(Heap*, JSCell*); + JS_EXPORT_PRIVATE static bool isValidCodeBlock(ExecState*, CodeBlock*); + JS_EXPORT_PRIVATE static CodeBlock* codeBlockForFrame(CallFrame* topCallFrame, unsigned frameNumber); + JS_EXPORT_PRIVATE static void printCallFrame(CallFrame*); + JS_EXPORT_PRIVATE static void printStack(CallFrame* topCallFrame); + JS_EXPORT_PRIVATE static void printValue(JSValue); + +private: + JSDollarVMPrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(VM&, JSGlobalObject*); + void addFunction(VM&, JSGlobalObject*, const char* name, NativeFunction, unsigned arguments); +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/ProfileTreeNode.h b/Source/JavaScriptCore/tools/ProfileTreeNode.h index 88d57f3dd..66f415489 100644 --- a/Source/JavaScriptCore/tools/ProfileTreeNode.h +++ b/Source/JavaScriptCore/tools/ProfileTreeNode.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef ProfileTreeNode_h -#define ProfileTreeNode_h +#pragma once namespace JSC { @@ -89,9 +88,7 @@ private: qsort(entries.begin(), entries.size(), sizeof(MapEntry*), compareEntries); // Iterate over the children in sample-frequency order. - for (size_t e = 0; e < entries.size(); ++e) { - MapEntry* entry = entries[e]; - + for (auto* entry : entries) { // Print the number of samples, the name of this node, and the number of samples that are stack-top // in this node (samples directly within this node, excluding samples in children. for (unsigned i = 0; i < indent; ++i) @@ -117,7 +114,4 @@ private: Map* m_children; }; -} - -#endif // ProfileTreeNode_h - +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/SigillCrashAnalyzer.cpp b/Source/JavaScriptCore/tools/SigillCrashAnalyzer.cpp new file mode 100644 index 000000000..1ac1af480 --- /dev/null +++ b/Source/JavaScriptCore/tools/SigillCrashAnalyzer.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SigillCrashAnalyzer.h" + +#include "CallFrame.h" +#include "CodeBlock.h" +#include "VMInspector.h" +#include <mutex> +#include <wtf/StdLibExtras.h> + +#if USE(ARM64_DISASSEMBLER) +#include "A64DOpcode.h" +#endif + +#if HAVE(SIGNAL_H) +#include <signal.h> +#endif + +namespace JSC { + +struct SignalContext; + +class SigillCrashAnalyzer { +public: + static SigillCrashAnalyzer& instance(); + + enum class CrashSource { + Unknown, + JavaScriptCore, + Other, + }; + CrashSource analyze(SignalContext&); + +private: + SigillCrashAnalyzer() { } + void dumpCodeBlock(CodeBlock*, void* machinePC); + +#if USE(ARM64_DISASSEMBLER) + A64DOpcode m_arm64Opcode; +#endif +}; + +#if OS(DARWIN) + +#if USE(OS_LOG) + +#define log(format, ...) \ + os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__) + +#else // USE(OS_LOG) + +#define log(format, ...) \ + dataLogF(format, ##__VA_ARGS__) + +#endif // USE(OS_LOG) + +#if CPU(X86_64) +struct SignalContext { + SignalContext(mcontext_t& mcontext) + : mcontext(mcontext) + , machinePC(reinterpret_cast<void*>(mcontext->__ss.__rip)) + , stackPointer(reinterpret_cast<void*>(mcontext->__ss.__rsp)) + , framePointer(reinterpret_cast<CallFrame*>(mcontext->__ss.__rbp)) + { } + + void dump() + { +#define FOR_EACH_REGISTER(v) \ + v(rax) \ + v(rbx) \ + v(rcx) \ + v(rdx) \ + v(rdi) \ + v(rsi) \ + v(rbp) \ + v(rsp) \ + v(r8) \ + v(r9) \ + v(r10) \ + v(r11) \ + v(r12) \ + v(r13) \ + v(r14) \ + v(r15) \ + v(rip) \ + v(rflags) \ + v(cs) \ + v(fs) \ + v(gs) + +#define DUMP_REGISTER(__reg) \ + log("Register " #__reg ": %p", reinterpret_cast<void*>(mcontext->__ss.__##__reg)); + FOR_EACH_REGISTER(DUMP_REGISTER) +#undef FOR_EACH_REGISTER + } + + mcontext_t& mcontext; + void* machinePC; + void* stackPointer; + void* framePointer; +}; + +#elif CPU(ARM64) + +struct SignalContext { + SignalContext(mcontext_t& mcontext) + : mcontext(mcontext) + , machinePC(reinterpret_cast<void*>(mcontext->__ss.__pc)) + , stackPointer(reinterpret_cast<void*>(mcontext->__ss.__sp)) + , framePointer(reinterpret_cast<CallFrame*>(mcontext->__ss.__fp)) + { } + + void dump() + { + int i; + for (i = 0; i < 28; i += 4) { + log("x%d: %016llx x%d: %016llx x%d: %016llx x%d: %016llx", + i, mcontext->__ss.__x[i], + i+1, mcontext->__ss.__x[i+1], + i+2, mcontext->__ss.__x[i+2], + i+3, mcontext->__ss.__x[i+3]); + } + ASSERT(i < 29); + log("x%d: %016llx fp: %016llx lr: %016llx", + i, mcontext->__ss.__x[i], mcontext->__ss.__fp, mcontext->__ss.__lr); + log("sp: %016llx pc: %016llx cpsr: %08x", + mcontext->__ss.__sp, mcontext->__ss.__pc, mcontext->__ss.__cpsr); + } + + mcontext_t& mcontext; + void* machinePC; + void* stackPointer; + void* framePointer; +}; + +#else + +struct SignalContext { + SignalContext(mcontext_t&) { } + + void dump() { } + + void* machinePC; + void* stackPointer; + void* framePointer; +}; + +#endif + +struct sigaction originalSigIllAction; + +static void handleCrash(int signalNumber, siginfo_t* info, void* uap) +{ + SignalContext context(static_cast<ucontext_t*>(uap)->uc_mcontext); + SigillCrashAnalyzer& analyzer = SigillCrashAnalyzer::instance(); + auto crashSource = analyzer.analyze(context); + + auto originalAction = originalSigIllAction.sa_sigaction; + if (originalAction) { + // It is always safe to just invoke the original handler using the sa_sigaction form + // without checking for the SA_SIGINFO flag. If the original handler is of the + // sa_handler form, it will just ignore the 2nd and 3rd arguments since sa_handler is a + // subset of sa_sigaction. This is what the man pages says the OS does anyway. + originalAction(signalNumber, info, uap); + } + + if (crashSource == SigillCrashAnalyzer::CrashSource::JavaScriptCore) { + // Restore the default handler so that we can get a core dump. + struct sigaction defaultAction; + defaultAction.sa_handler = SIG_DFL; + sigfillset(&defaultAction.sa_mask); + defaultAction.sa_flags = 0; + sigaction(SIGILL, &defaultAction, nullptr); + } else if (!originalAction) { + // Pre-emptively restore the default handler but we may roll it back below. + struct sigaction currentAction; + struct sigaction defaultAction; + defaultAction.sa_handler = SIG_DFL; + sigfillset(&defaultAction.sa_mask); + defaultAction.sa_flags = 0; + sigaction(SIGILL, &defaultAction, ¤tAction); + + if (currentAction.sa_sigaction != handleCrash) { + // This means that there's a client handler installed after us. This also means + // that the client handler thinks it was able to recover from the SIGILL, and + // did not uninstall itself. We can't argue with this because the crash isn't + // known to be from a JavaScriptCore source. Hence, restore the client handler + // and keep going. + sigaction(SIGILL, ¤tAction, nullptr); + } + } +} + +static void installCrashHandler() +{ +#if CPU(X86_64) || CPU(ARM64) + struct sigaction action; + action.sa_sigaction = reinterpret_cast<void (*)(int, siginfo_t *, void *)>(handleCrash); + sigfillset(&action.sa_mask); + action.sa_flags = SA_SIGINFO; + sigaction(SIGILL, &action, &originalSigIllAction); +#else + UNUSED_PARAM(handleCrash); +#endif +} + +#else // OS(DARWIN) + +#define log(format, ...) do { } while (false) + +struct SignalContext { + SignalContext() { } + + void dump() { } + + void* machinePC; + void* stackPointer; + void* framePointer; +}; + +static void installCrashHandler() +{ + // Do nothing. Not supported for this platform. +} + +#endif // OS(DARWIN) + +SigillCrashAnalyzer& SigillCrashAnalyzer::instance() +{ + static SigillCrashAnalyzer* analyzer; + static std::once_flag once; + std::call_once(once, [] { + installCrashHandler(); + analyzer = new SigillCrashAnalyzer; + }); + return *analyzer; +} + +void enableSigillCrashAnalyzer() +{ + // Just instantiating the SigillCrashAnalyzer will enable it. + SigillCrashAnalyzer::instance(); +} + +auto SigillCrashAnalyzer::analyze(SignalContext& context) -> CrashSource +{ + CrashSource crashSource = CrashSource::Unknown; + log("BEGIN SIGILL analysis"); + + do { + // First, dump the signal context info so that we'll at least have the same info + // that the default crash handler would given us in case this crash analyzer + // itself crashes. + context.dump(); + + VMInspector& inspector = VMInspector::instance(); + + // Use a timeout period of 2 seconds. The client is about to crash, and we don't + // want to turn the crash into a hang by re-trying the lock for too long. + auto expectedLocker = inspector.lock(Seconds(2)); + if (!expectedLocker) { + ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut); + log("ERROR: Unable to analyze SIGILL. Timed out while waiting to iterate VMs."); + break; + } + auto& locker = expectedLocker.value(); + + void* pc = context.machinePC; + auto isInJITMemory = inspector.isValidExecutableMemory(locker, pc); + if (!isInJITMemory) { + log("ERROR: Timed out: not able to determine if pc %p is in valid JIT executable memory", pc); + break; + } + if (!isInJITMemory.value()) { + log("pc %p is NOT in valid JIT executable memory", pc); + crashSource = CrashSource::Other; + break; + } + log("pc %p is in valid JIT executable memory", pc); + crashSource = CrashSource::JavaScriptCore; + +#if CPU(ARM64) + size_t pcAsSize = reinterpret_cast<size_t>(pc); + if (pcAsSize != roundUpToMultipleOf<sizeof(uint32_t)>(pcAsSize)) { + log("pc %p is NOT properly aligned", pc); + break; + } + + // We know it's safe to read the word at the PC because we're handling a SIGILL. + // Otherwise, we would have crashed with a SIGBUS instead. + uint32_t wordAtPC = *reinterpret_cast<uint32_t*>(pc); + log("instruction bits at pc %p is: 0x%08x", pc, wordAtPC); +#endif + + auto expectedCodeBlock = inspector.codeBlockForMachinePC(locker, pc); + if (!expectedCodeBlock) { + if (expectedCodeBlock.error() == VMInspector::Error::TimedOut) + log("ERROR: Timed out: not able to determine if pc %p is in a valid CodeBlock", pc); + else + log("The current thread does not own any VM JSLock"); + break; + } + CodeBlock* codeBlock = expectedCodeBlock.value(); + if (!codeBlock) { + log("machine PC %p does not belong to any CodeBlock in the currently entered VM", pc); + break; + } + + log("pc %p belongs to CodeBlock %p of type %s", pc, codeBlock, JITCode::typeName(codeBlock->jitType())); + + dumpCodeBlock(codeBlock, pc); + } while (false); + + log("END SIGILL analysis"); + return crashSource; +} + +void SigillCrashAnalyzer::dumpCodeBlock(CodeBlock* codeBlock, void* machinePC) +{ +#if CPU(ARM64) + JITCode* jitCode = codeBlock->jitCode().get(); + + // Dump the raw bits of the code. + uint32_t* start = reinterpret_cast<uint32_t*>(jitCode->start()); + uint32_t* end = reinterpret_cast<uint32_t*>(jitCode->end()); + log("JITCode %p [%p-%p]:", jitCode, start, end); + if (start < end) { + uint32_t* p = start; + while (p + 8 <= end) { + log("[%p-%p]: %08x %08x %08x %08x %08x %08x %08x %08x", p, p+7, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + p += 8; + } + if (p + 7 <= end) + log("[%p-%p]: %08x %08x %08x %08x %08x %08x %08x", p, p+6, p[0], p[1], p[2], p[3], p[4], p[5], p[6]); + else if (p + 6 <= end) + log("[%p-%p]: %08x %08x %08x %08x %08x %08x", p, p+5, p[0], p[1], p[2], p[3], p[4], p[5]); + else if (p + 5 <= end) + log("[%p-%p]: %08x %08x %08x %08x %08x", p, p+4, p[0], p[1], p[2], p[3], p[4]); + else if (p + 4 <= end) + log("[%p-%p]: %08x %08x %08x %08x", p, p+3, p[0], p[1], p[2], p[3]); + if (p + 3 <= end) + log("[%p-%p]: %08x %08x %08x", p, p+2, p[0], p[1], p[2]); + else if (p + 2 <= end) + log("[%p-%p]: %08x %08x", p, p+1, p[0], p[1]); + else if (p + 1 <= end) + log("[%p-%p]: %08x", p, p, p[0]); + } + + // Dump the disassembly of the code. + log("Disassembly:"); + uint32_t* currentPC = reinterpret_cast<uint32_t*>(jitCode->executableAddress()); + size_t byteCount = jitCode->size(); + while (byteCount) { + char pcString[24]; + if (currentPC == machinePC) { + snprintf(pcString, sizeof(pcString), "* 0x%lx", reinterpret_cast<unsigned long>(currentPC)); + log("%20s: %s <=========================", pcString, m_arm64Opcode.disassemble(currentPC)); + } else { + snprintf(pcString, sizeof(pcString), "0x%lx", reinterpret_cast<unsigned long>(currentPC)); + log("%20s: %s", pcString, m_arm64Opcode.disassemble(currentPC)); + } + currentPC++; + byteCount -= sizeof(uint32_t); + } +#else + UNUSED_PARAM(codeBlock); + UNUSED_PARAM(machinePC); + // Not implemented yet. +#endif +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/SigillCrashAnalyzer.h b/Source/JavaScriptCore/tools/SigillCrashAnalyzer.h new file mode 100644 index 000000000..a36b82f7d --- /dev/null +++ b/Source/JavaScriptCore/tools/SigillCrashAnalyzer.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +namespace JSC { + +// Enables the SIGILL crash analyzer. This is a one way trip. There's no going back. +void enableSigillCrashAnalyzer(); + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/TieredMMapArray.h b/Source/JavaScriptCore/tools/TieredMMapArray.h index e0dfa3628..1418292ce 100644 --- a/Source/JavaScriptCore/tools/TieredMMapArray.h +++ b/Source/JavaScriptCore/tools/TieredMMapArray.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TieredMMapArray_h -#define TieredMMapArray_h +#pragma once #include <wtf/OSAllocator.h> @@ -110,7 +109,4 @@ private: size_t m_size; }; -} - -#endif // TieredMMapArray_h - +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/VMInspector.cpp b/Source/JavaScriptCore/tools/VMInspector.cpp new file mode 100644 index 000000000..ef44ecca7 --- /dev/null +++ b/Source/JavaScriptCore/tools/VMInspector.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "VMInspector.h" + +#include "CodeBlock.h" +#include "CodeBlockSet.h" +#include "HeapInlines.h" +#include <mutex> +#include <wtf/Expected.h> + +#if !OS(WINDOWS) +#include <unistd.h> +#endif + +namespace JSC { + +VMInspector& VMInspector::instance() +{ + static VMInspector* manager; + static std::once_flag once; + std::call_once(once, [] { + manager = new VMInspector(); + }); + return *manager; +} + +void VMInspector::add(VM* vm) +{ + auto locker = holdLock(m_lock); + m_list.append(vm); +} + +void VMInspector::remove(VM* vm) +{ + auto locker = holdLock(m_lock); + m_list.remove(vm); +} + +auto VMInspector::lock(Seconds timeout) -> Expected<Locker, Error> +{ + // This function may be called from a signal handler (e.g. via visit()). Hence, + // it should only use APIs that are safe to call from signal handlers. This is + // why we use unistd.h's sleep() instead of its alternatives. + + // We'll be doing sleep(1) between tries below. Hence, sleepPerRetry is 1. + unsigned maxRetries = (timeout < Seconds::infinity()) ? timeout.value() : UINT_MAX; + + Expected<Locker, Error> locker = Locker::tryLock(m_lock); + unsigned tryCount = 0; + while (!locker && tryCount < maxRetries) { + // We want the version of sleep from unistd.h. Cast to disambiguate. +#if !OS(WINDOWS) + (static_cast<unsigned (*)(unsigned)>(sleep))(1); +#endif + locker = Locker::tryLock(m_lock); + } + + if (!locker) + return makeUnexpected(Error::TimedOut); + return locker; +} + +#if ENABLE(JIT) +static bool ensureIsSafeToLock(Lock& lock) +{ + unsigned maxRetries = 2; + unsigned tryCount = 0; + while (tryCount <= maxRetries) { + bool success = lock.tryLock(); + if (success) { + lock.unlock(); + return true; + } + tryCount++; + } + return false; +}; +#endif // ENABLE(JIT) + +auto VMInspector::isValidExecutableMemory(const VMInspector::Locker&, void* machinePC) -> Expected<bool, Error> +{ +#if ENABLE(JIT) + bool found = false; + bool hasTimeout = false; + iterate([&] (VM& vm) -> FunctorStatus { + auto allocator = vm.executableAllocator; + auto& lock = allocator.getLock(); + + bool isSafeToLock = ensureIsSafeToLock(lock); + if (!isSafeToLock) { + hasTimeout = true; + return FunctorStatus::Continue; // Skip this VM. + } + + LockHolder executableAllocatorLocker(lock); + if (allocator.isValidExecutableMemory(executableAllocatorLocker, machinePC)) { + found = true; + return FunctorStatus::Done; + } + return FunctorStatus::Continue; + }); + + if (!found && hasTimeout) + return makeUnexpected(Error::TimedOut); + return found; +#else + UNUSED_PARAM(machinePC); + return false; +#endif +} + +auto VMInspector::codeBlockForMachinePC(const VMInspector::Locker&, void* machinePC) -> Expected<CodeBlock*, Error> +{ +#if ENABLE(JIT) + CodeBlock* codeBlock = nullptr; + bool hasTimeout = false; + iterate([&] (VM& vm) { + if (!vm.apiLock().currentThreadIsHoldingLock()) + return FunctorStatus::Continue; + + // It is safe to call Heap::forEachCodeBlockIgnoringJITPlans here because: + // 1. CodeBlocks are added to the CodeBlockSet from the main thread before + // they are handed to the JIT plans. Those codeBlocks will have a null jitCode, + // but we check for that in our lambda functor. + // 2. CodeBlockSet::iterate() will acquire the CodeBlockSet lock before iterating. + // This ensures that a CodeBlock won't be GCed while we're iterating. + // 3. We do a tryLock on the CodeBlockSet's lock first to ensure that it is + // safe for the current thread to lock it before calling + // Heap::forEachCodeBlockIgnoringJITPlans(). Hence, there's no risk of + // re-entering the lock and deadlocking on it. + + auto& lock = vm.heap.codeBlockSet().getLock(); + bool isSafeToLock = ensureIsSafeToLock(lock); + if (!isSafeToLock) { + hasTimeout = true; + return FunctorStatus::Continue; // Skip this VM. + } + + vm.heap.forEachCodeBlockIgnoringJITPlans([&] (CodeBlock* cb) { + JITCode* jitCode = cb->jitCode().get(); + if (!jitCode) { + // If the codeBlock is a replacement codeBlock which is in the process of being + // compiled, its jitCode will be null, and we can disregard it as a match for + // the machinePC we're searching for. + return false; + } + + if (!JITCode::isJIT(jitCode->jitType())) + return false; + + if (jitCode->contains(machinePC)) { + codeBlock = cb; + return true; + } + return false; + }); + if (codeBlock) + return FunctorStatus::Done; + return FunctorStatus::Continue; + }); + + if (!codeBlock && hasTimeout) + return makeUnexpected(Error::TimedOut); + return codeBlock; +#else + UNUSED_PARAM(machinePC); + return nullptr; +#endif +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/tools/VMInspector.h b/Source/JavaScriptCore/tools/VMInspector.h new file mode 100644 index 000000000..b633b4bd7 --- /dev/null +++ b/Source/JavaScriptCore/tools/VMInspector.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "VM.h" +#include <wtf/DoublyLinkedList.h> +#include <wtf/Expected.h> +#include <wtf/Lock.h> + +namespace JSC { + +class VMInspector { +public: + enum class Error { + None, + TimedOut + }; + + typedef WTF::Locker<Lock> Locker; + + static VMInspector& instance(); + + void add(VM*); + void remove(VM*); + + Expected<Locker, Error> lock(Seconds timeout = Seconds::infinity()); + + Expected<bool, Error> isValidExecutableMemory(const Locker&, void*); + Expected<CodeBlock*, Error> codeBlockForMachinePC(const Locker&, void*); + +private: + enum class FunctorStatus { + Continue, + Done + }; + template <typename Functor> void iterate(const Functor& functor) + { + for (VM* vm = m_list.head(); vm; vm = vm->next()) { + FunctorStatus status = functor(*vm); + if (status == FunctorStatus::Done) + return; + } + } + + Lock m_lock; + DoublyLinkedList<VM> m_list; +}; + +} // namespace JSC |