diff options
Diffstat (limited to 'deps/v8/tools/debug_helper')
-rw-r--r-- | deps/v8/tools/debug_helper/BUILD.gn | 104 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/DEPS | 3 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/README.md | 6 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/debug-helper-internal.cc | 58 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/debug-helper-internal.h | 130 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/debug-helper.h | 177 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/gen-heap-constants.py | 63 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/get-object-properties.cc | 535 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/heap-constants.cc | 51 | ||||
-rw-r--r-- | deps/v8/tools/debug_helper/heap-constants.h | 28 |
10 files changed, 1155 insertions, 0 deletions
diff --git a/deps/v8/tools/debug_helper/BUILD.gn b/deps/v8/tools/debug_helper/BUILD.gn new file mode 100644 index 0000000000..c81fddc9e5 --- /dev/null +++ b/deps/v8/tools/debug_helper/BUILD.gn @@ -0,0 +1,104 @@ +# Copyright 2019 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../gni/snapshot_toolchain.gni") +import("../../gni/v8.gni") + +config("internal_config") { + visibility = [ ":*" ] # Only targets in this file can depend on this. + + if (is_component_build) { + defines = [ "BUILDING_V8_DEBUG_HELPER" ] + } + + include_dirs = [ + ".", + "../..", + "$target_gen_dir", + "$target_gen_dir/../..", + ] +} + +# This config should be applied to code using v8_debug_helper. +config("external_config") { + if (is_component_build) { + defines = [ "USING_V8_DEBUG_HELPER" ] + } + include_dirs = [ "." ] +} + +action("run_mkgrokdump") { + testonly = true + visibility = [ ":*" ] + + deps = [ + "../../test/mkgrokdump:mkgrokdump($v8_generator_toolchain)", + ] + + script = "../run.py" + + outputs = [ + "$target_gen_dir/v8heapconst.py", + ] + + args = [ + "./" + rebase_path( + get_label_info( + "../../test/mkgrokdump:mkgrokdump($v8_generator_toolchain)", + "root_out_dir") + "/mkgrokdump", + root_build_dir), + "--outfile", + rebase_path("$target_gen_dir/v8heapconst.py", root_build_dir), + ] +} + +action("gen_heap_constants") { + testonly = true + visibility = [ ":*" ] + deps = [ + ":run_mkgrokdump", + ] + script = "gen-heap-constants.py" + outputs = [ + "$target_gen_dir/heap-constants-gen.cc", + ] + args = [ + rebase_path(target_gen_dir, root_build_dir), + rebase_path("$target_gen_dir/heap-constants-gen.cc", root_build_dir), + ] +} + +v8_component("v8_debug_helper") { + testonly = true + + public = [ + "debug-helper.h", + ] + + sources = [ + "$target_gen_dir/../../torque-generated/class-debug-readers-tq.cc", + "$target_gen_dir/../../torque-generated/class-debug-readers-tq.h", + "$target_gen_dir/heap-constants-gen.cc", + "debug-helper-internal.cc", + "debug-helper-internal.h", + "debug-helper.h", + "get-object-properties.cc", + "heap-constants.cc", + "heap-constants.h", + ] + + deps = [ + ":gen_heap_constants", + "../..:run_torque", + "../..:v8_headers", + "../..:v8_libbase", + ] + + configs = [ ":internal_config" ] + if (v8_enable_i18n_support) { + configs += [ "//third_party/icu:icu_config" ] + } + + public_configs = [ ":external_config" ] +} diff --git a/deps/v8/tools/debug_helper/DEPS b/deps/v8/tools/debug_helper/DEPS new file mode 100644 index 0000000000..2c6adb4df5 --- /dev/null +++ b/deps/v8/tools/debug_helper/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+torque-generated" +] diff --git a/deps/v8/tools/debug_helper/README.md b/deps/v8/tools/debug_helper/README.md new file mode 100644 index 0000000000..bc99569c43 --- /dev/null +++ b/deps/v8/tools/debug_helper/README.md @@ -0,0 +1,6 @@ +# V8 debug helper + +This library is for debugging V8 itself, not debugging JavaScript running within +V8. It is designed to be called from a debugger extension running within a +native debugger such as WinDbg or LLDB. It can be used on live processes or +crash dumps, and cannot assume that all memory is available in a dump. diff --git a/deps/v8/tools/debug_helper/debug-helper-internal.cc b/deps/v8/tools/debug_helper/debug-helper-internal.cc new file mode 100644 index 0000000000..ee5629b438 --- /dev/null +++ b/deps/v8/tools/debug_helper/debug-helper-internal.cc @@ -0,0 +1,58 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "debug-helper-internal.h" +#include "src/common/ptr-compr-inl.h" +#include "torque-generated/class-debug-readers-tq.h" + +namespace i = v8::internal; + +namespace v8_debug_helper_internal { + +bool IsPointerCompressed(uintptr_t address) { +#if COMPRESS_POINTERS_BOOL + STATIC_ASSERT(i::kPtrComprHeapReservationSize == uintptr_t{1} << 32); + intptr_t signed_address = static_cast<intptr_t>(address); + return signed_address >= INT32_MIN && signed_address <= INT32_MAX; +#else + return false; +#endif +} + +uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_ptr) { + if (!COMPRESS_POINTERS_BOOL || !IsPointerCompressed(address)) return address; + return i::DecompressTaggedAny(any_uncompressed_ptr, + static_cast<i::Tagged_t>(address)); +} + +d::PropertyKind GetArrayKind(d::MemoryAccessResult mem_result) { + d::PropertyKind indexed_field_kind{}; + switch (mem_result) { + case d::MemoryAccessResult::kOk: + indexed_field_kind = d::PropertyKind::kArrayOfKnownSize; + break; + case d::MemoryAccessResult::kAddressNotValid: + indexed_field_kind = + d::PropertyKind::kArrayOfUnknownSizeDueToInvalidMemory; + break; + default: + indexed_field_kind = + d::PropertyKind::kArrayOfUnknownSizeDueToValidButInaccessibleMemory; + break; + } + return indexed_field_kind; +} + +std::vector<std::unique_ptr<ObjectProperty>> TqObject::GetProperties( + d::MemoryAccessor accessor) const { + return std::vector<std::unique_ptr<ObjectProperty>>(); +} + +const char* TqObject::GetName() const { return "v8::internal::Object"; } + +void TqObject::Visit(TqObjectVisitor* visitor) const { + visitor->VisitObject(this); +} + +} // namespace v8_debug_helper_internal diff --git a/deps/v8/tools/debug_helper/debug-helper-internal.h b/deps/v8/tools/debug_helper/debug-helper-internal.h new file mode 100644 index 0000000000..82506c0941 --- /dev/null +++ b/deps/v8/tools/debug_helper/debug-helper-internal.h @@ -0,0 +1,130 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines internal versions of the public API structs. These should +// all be tidy and simple classes which maintain proper ownership (unique_ptr) +// of each other. Each contains an instance of its corresponding public type, +// which can be filled out with GetPublicView. + +#ifndef V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_INTERNAL_H_ +#define V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_INTERNAL_H_ + +#include <string> +#include <vector> + +#include "debug-helper.h" +#include "src/objects/instance-type.h" + +namespace d = v8::debug_helper; + +namespace v8_debug_helper_internal { + +// A value that was read from the debuggee's memory. +template <typename TValue> +struct Value { + d::MemoryAccessResult validity; + TValue value; +}; + +class ObjectProperty { + public: + inline ObjectProperty(std::string name, std::string type, + std::string decompressed_type, uintptr_t address, + size_t num_values = 1, + d::PropertyKind kind = d::PropertyKind::kSingle) + : name_(name), + type_(type), + decompressed_type_(decompressed_type), + address_(address), + num_values_(num_values), + kind_(kind) {} + + inline d::ObjectProperty* GetPublicView() { + public_view_.name = name_.c_str(); + public_view_.type = type_.c_str(); + public_view_.decompressed_type = decompressed_type_.c_str(); + public_view_.address = address_; + public_view_.num_values = num_values_; + public_view_.kind = kind_; + return &public_view_; + } + + private: + std::string name_; + std::string type_; + std::string decompressed_type_; + uintptr_t address_; + size_t num_values_; + d::PropertyKind kind_; + + d::ObjectProperty public_view_; +}; + +class ObjectPropertiesResult; +using ObjectPropertiesResultInternal = ObjectPropertiesResult; + +struct ObjectPropertiesResultExtended : public d::ObjectPropertiesResult { + ObjectPropertiesResultInternal* base; // Back reference for cleanup +}; + +class ObjectPropertiesResult { + public: + inline ObjectPropertiesResult( + d::TypeCheckResult type_check_result, std::string brief, std::string type, + std::vector<std::unique_ptr<ObjectProperty>> properties) + : type_check_result_(type_check_result), + brief_(brief), + type_(type), + properties_(std::move(properties)) {} + + inline void Prepend(const char* prefix) { brief_ = prefix + brief_; } + + inline d::ObjectPropertiesResult* GetPublicView() { + public_view_.type_check_result = type_check_result_; + public_view_.brief = brief_.c_str(); + public_view_.type = type_.c_str(); + public_view_.num_properties = properties_.size(); + properties_raw_.resize(0); + for (const auto& property : properties_) { + properties_raw_.push_back(property->GetPublicView()); + } + public_view_.properties = properties_raw_.data(); + public_view_.base = this; + return &public_view_; + } + + private: + d::TypeCheckResult type_check_result_; + std::string brief_; + std::string type_; + std::vector<std::unique_ptr<ObjectProperty>> properties_; + + ObjectPropertiesResultExtended public_view_; + std::vector<d::ObjectProperty*> properties_raw_; +}; + +class TqObjectVisitor; + +// Base class representing a V8 object in the debuggee's address space. +// Subclasses for specific object types are generated by the Torque compiler. +class TqObject { + public: + inline TqObject(uintptr_t address) : address_(address) {} + virtual ~TqObject() = default; + virtual std::vector<std::unique_ptr<ObjectProperty>> GetProperties( + d::MemoryAccessor accessor) const; + virtual const char* GetName() const; + virtual void Visit(TqObjectVisitor* visitor) const; + + protected: + uintptr_t address_; +}; + +bool IsPointerCompressed(uintptr_t address); +uintptr_t Decompress(uintptr_t address, uintptr_t any_uncompressed_address); +d::PropertyKind GetArrayKind(d::MemoryAccessResult mem_result); + +} // namespace v8_debug_helper_internal + +#endif diff --git a/deps/v8/tools/debug_helper/debug-helper.h b/deps/v8/tools/debug_helper/debug-helper.h new file mode 100644 index 0000000000..9bbec76c7c --- /dev/null +++ b/deps/v8/tools/debug_helper/debug-helper.h @@ -0,0 +1,177 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines the public interface to v8_debug_helper. + +#ifndef V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_H_ +#define V8_TOOLS_DEBUG_HELPER_DEBUG_HELPER_H_ + +#include <cstdint> +#include <memory> + +#if defined(_WIN32) + +#ifdef BUILDING_V8_DEBUG_HELPER +#define V8_DEBUG_HELPER_EXPORT __declspec(dllexport) +#elif USING_V8_DEBUG_HELPER +#define V8_DEBUG_HELPER_EXPORT __declspec(dllimport) +#else +#define V8_DEBUG_HELPER_EXPORT +#endif + +#else // defined(_WIN32) + +#ifdef BUILDING_V8_DEBUG_HELPER +#define V8_DEBUG_HELPER_EXPORT __attribute__((visibility("default"))) +#else +#define V8_DEBUG_HELPER_EXPORT +#endif + +#endif // defined(_WIN32) + +namespace v8 { +namespace debug_helper { + +// Possible results when attempting to fetch memory from the debuggee. +enum class MemoryAccessResult { + kOk, + kAddressNotValid, + kAddressValidButInaccessible, // Possible in incomplete dump. +}; + +// Information about how this tool discovered the type of the object. +enum class TypeCheckResult { + // Success cases: + kSmi, + kWeakRef, + kUsedMap, + kUsedTypeHint, + + // Failure cases: + kUnableToDecompress, // Caller must provide the heap range somehow. + kObjectPointerInvalid, + kObjectPointerValidButInaccessible, // Possible in incomplete dump. + kMapPointerInvalid, + kMapPointerValidButInaccessible, // Possible in incomplete dump. + kUnknownInstanceType, + kUnknownTypeHint, +}; + +enum class PropertyKind { + kSingle, + kArrayOfKnownSize, + kArrayOfUnknownSizeDueToInvalidMemory, + kArrayOfUnknownSizeDueToValidButInaccessibleMemory, +}; + +struct ObjectProperty { + const char* name; + + // Statically-determined type, such as from .tq definition. + const char* type; + + // In some cases, |type| may be a simple type representing a compressed + // pointer such as v8::internal::TaggedValue. In those cases, + // |decompressed_type| will contain the type of the object when decompressed. + // Otherwise, |decompressed_type| will match |type|. In any case, it is safe + // to pass the |decompressed_type| value as the type_hint on a subsequent call + // to GetObjectProperties. + const char* decompressed_type; + + // The address where the property value can be found in the debuggee's address + // space, or the address of the first value for an array. + uintptr_t address; + + // If kind indicates an array of unknown size, num_values will be 0 and debug + // tools should display this property as a raw pointer. Note that there is a + // semantic difference between num_values=1 and kind=kSingle (normal property) + // versus num_values=1 and kind=kArrayOfKnownSize (one-element array). + size_t num_values; + + PropertyKind kind; +}; + +struct ObjectPropertiesResult { + TypeCheckResult type_check_result; + const char* brief; + const char* type; // Runtime type of the object. + size_t num_properties; + ObjectProperty** properties; +}; + +// Copies byte_count bytes of memory from the given address in the debuggee to +// the destination buffer. +typedef MemoryAccessResult (*MemoryAccessor)(uintptr_t address, + uint8_t* destination, + size_t byte_count); + +// Additional data that can help GetObjectProperties to be more accurate. Any +// fields you don't know can be set to zero and this library will do the best it +// can with the information available. +struct Roots { + // Beginning of allocated space for various kinds of data. These can help us + // to detect certain common objects that are placed in memory during startup. + // These values might be provided via name-value pairs in CrashPad dumps. + // Otherwise, they can be obtained as follows: + // 1. Get the Isolate pointer for the current thread. It might be somewhere on + // the stack, or it might be accessible from thread-local storage with the + // key stored in v8::internal::Isolate::isolate_key_. + // 2. Get isolate->heap_.map_space_->memory_chunk_list_.front_ and similar for + // old_space_ and read_only_space_. + uintptr_t map_space; + uintptr_t old_space; + uintptr_t read_only_space; + + // Any valid heap pointer address. On platforms where pointer compression is + // enabled, this can allow us to get data from compressed pointers even if the + // other data above is not provided. The Isolate pointer is valid for this + // purpose if you have it. + uintptr_t any_heap_pointer; +}; + +} // namespace debug_helper +} // namespace v8 + +extern "C" { +// Raw library interface. If possible, use functions in v8::debug_helper +// namespace instead because they use smart pointers to prevent leaks. +V8_DEBUG_HELPER_EXPORT v8::debug_helper::ObjectPropertiesResult* +_v8_debug_helper_GetObjectProperties( + uintptr_t object, v8::debug_helper::MemoryAccessor memory_accessor, + const v8::debug_helper::Roots& heap_roots, const char* type_hint); +V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult( + v8::debug_helper::ObjectPropertiesResult* result); +} + +namespace v8 { +namespace debug_helper { + +struct DebugHelperObjectPropertiesResultDeleter { + void operator()(v8::debug_helper::ObjectPropertiesResult* ptr) { + _v8_debug_helper_Free_ObjectPropertiesResult(ptr); + } +}; +using ObjectPropertiesResultPtr = + std::unique_ptr<ObjectPropertiesResult, + DebugHelperObjectPropertiesResultDeleter>; + +// Get information about the given object pointer, which could be: +// - A tagged pointer, strong or weak +// - A cleared weak pointer +// - A compressed tagged pointer, sign-extended to 64 bits +// - A tagged small integer +// The type hint is only used if the object's Map is missing or corrupt. It +// should be the fully-qualified name of a class that inherits from +// v8::internal::Object. +inline ObjectPropertiesResultPtr GetObjectProperties( + uintptr_t object, v8::debug_helper::MemoryAccessor memory_accessor, + const Roots& heap_roots, const char* type_hint = nullptr) { + return ObjectPropertiesResultPtr(_v8_debug_helper_GetObjectProperties( + object, memory_accessor, heap_roots, type_hint)); +} + +} // namespace debug_helper +} // namespace v8 + +#endif diff --git a/deps/v8/tools/debug_helper/gen-heap-constants.py b/deps/v8/tools/debug_helper/gen-heap-constants.py new file mode 100644 index 0000000000..0fd575a994 --- /dev/null +++ b/deps/v8/tools/debug_helper/gen-heap-constants.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# Copyright 2019 the V8 project authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""This program writes a C++ file that can be used to look up whether a given +address matches known object locations. The first argument is the directory +containing the file v8heapconst.py; the second argument is the output .cc file. +""" + +import sys +sys.path.insert(0, sys.argv[1]) +import v8heapconst + +out = """ +#include <cstdint> +#include <string> + +namespace v8_debug_helper_internal { +""" + +def iterate_objects(target_space, camel_space_name): + global out + result = [] + for (space, offset), (instance_type, name) in v8heapconst.KNOWN_MAPS.items(): + if space == target_space: + result.append((offset, name)) + for (space, offset), name in v8heapconst.KNOWN_OBJECTS.items(): + if space == target_space: + result.append((offset, name)) + out = out + '\nstd::string FindKnownObjectIn' + camel_space_name \ + + '(uintptr_t offset) {\n switch (offset) {\n' + for offset, name in result: + out = out + ' case ' + str(offset) + ': return "' + name + '";\n' + out = out + ' default: return "";\n }\n}\n' + +iterate_objects('map_space', 'MapSpace') +iterate_objects('read_only_space', 'ReadOnlySpace') +iterate_objects('old_space', 'OldSpace') + +def iterate_maps(target_space, camel_space_name): + global out + out = out + '\nint FindKnownMapInstanceTypeIn' + camel_space_name \ + + '(uintptr_t offset) {\n switch (offset) {\n' + for (space, offset), (instance_type, name) in v8heapconst.KNOWN_MAPS.items(): + if space == target_space: + out = out + ' case ' + str(offset) + ': return ' + str(instance_type) \ + + ';\n' + out = out + ' default: return -1;\n }\n}\n' + +iterate_maps('map_space', 'MapSpace') +iterate_maps('read_only_space', 'ReadOnlySpace') + +out = out + '\n}\n' + +try: + with open(sys.argv[2], "r") as out_file: + if out == out_file.read(): + sys.exit(0) # No modification needed. +except: + pass # File probably doesn't exist; write it. +with open(sys.argv[2], "w") as out_file: + out_file.write(out) diff --git a/deps/v8/tools/debug_helper/get-object-properties.cc b/deps/v8/tools/debug_helper/get-object-properties.cc new file mode 100644 index 0000000000..fbe992c40e --- /dev/null +++ b/deps/v8/tools/debug_helper/get-object-properties.cc @@ -0,0 +1,535 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <sstream> + +#include "debug-helper-internal.h" +#include "heap-constants.h" +#include "include/v8-internal.h" +#include "src/common/ptr-compr-inl.h" +#include "src/objects/string-inl.h" +#include "src/strings/unicode-inl.h" +#include "torque-generated/class-debug-readers-tq.h" + +namespace i = v8::internal; + +namespace v8_debug_helper_internal { + +// INSTANCE_TYPE_CHECKERS_SINGLE_BASE, trimmed down to only classes that have +// layouts defined in .tq files (this subset relationship is asserted below). +// For now, this is a hand-maintained list. +// TODO(v8:7793): Torque should know enough about instance types to generate +// this list. +#define TQ_INSTANCE_TYPES_SINGLE_BASE(V) \ + V(ByteArray, BYTE_ARRAY_TYPE) \ + V(BytecodeArray, BYTECODE_ARRAY_TYPE) \ + V(CallHandlerInfo, CALL_HANDLER_INFO_TYPE) \ + V(Cell, CELL_TYPE) \ + V(DescriptorArray, DESCRIPTOR_ARRAY_TYPE) \ + V(EmbedderDataArray, EMBEDDER_DATA_ARRAY_TYPE) \ + V(FeedbackCell, FEEDBACK_CELL_TYPE) \ + V(FeedbackVector, FEEDBACK_VECTOR_TYPE) \ + V(FixedDoubleArray, FIXED_DOUBLE_ARRAY_TYPE) \ + V(Foreign, FOREIGN_TYPE) \ + V(FreeSpace, FREE_SPACE_TYPE) \ + V(HeapNumber, HEAP_NUMBER_TYPE) \ + V(JSArgumentsObject, JS_ARGUMENTS_TYPE) \ + V(JSArray, JS_ARRAY_TYPE) \ + V(JSArrayBuffer, JS_ARRAY_BUFFER_TYPE) \ + V(JSArrayIterator, JS_ARRAY_ITERATOR_TYPE) \ + V(JSAsyncFromSyncIterator, JS_ASYNC_FROM_SYNC_ITERATOR_TYPE) \ + V(JSAsyncFunctionObject, JS_ASYNC_FUNCTION_OBJECT_TYPE) \ + V(JSAsyncGeneratorObject, JS_ASYNC_GENERATOR_OBJECT_TYPE) \ + V(JSBoundFunction, JS_BOUND_FUNCTION_TYPE) \ + V(JSDataView, JS_DATA_VIEW_TYPE) \ + V(JSDate, JS_DATE_TYPE) \ + V(JSFunction, JS_FUNCTION_TYPE) \ + V(JSGlobalObject, JS_GLOBAL_OBJECT_TYPE) \ + V(JSGlobalProxy, JS_GLOBAL_PROXY_TYPE) \ + V(JSMap, JS_MAP_TYPE) \ + V(JSMessageObject, JS_MESSAGE_OBJECT_TYPE) \ + V(JSModuleNamespace, JS_MODULE_NAMESPACE_TYPE) \ + V(JSPromise, JS_PROMISE_TYPE) \ + V(JSProxy, JS_PROXY_TYPE) \ + V(JSRegExp, JS_REGEXP_TYPE) \ + V(JSRegExpStringIterator, JS_REGEXP_STRING_ITERATOR_TYPE) \ + V(JSSet, JS_SET_TYPE) \ + V(JSStringIterator, JS_STRING_ITERATOR_TYPE) \ + V(JSTypedArray, JS_TYPED_ARRAY_TYPE) \ + V(JSPrimitiveWrapper, JS_PRIMITIVE_WRAPPER_TYPE) \ + V(JSFinalizationGroup, JS_FINALIZATION_GROUP_TYPE) \ + V(JSFinalizationGroupCleanupIterator, \ + JS_FINALIZATION_GROUP_CLEANUP_ITERATOR_TYPE) \ + V(JSWeakMap, JS_WEAK_MAP_TYPE) \ + V(JSWeakRef, JS_WEAK_REF_TYPE) \ + V(JSWeakSet, JS_WEAK_SET_TYPE) \ + V(Map, MAP_TYPE) \ + V(Oddball, ODDBALL_TYPE) \ + V(PreparseData, PREPARSE_DATA_TYPE) \ + V(PropertyArray, PROPERTY_ARRAY_TYPE) \ + V(PropertyCell, PROPERTY_CELL_TYPE) \ + V(SharedFunctionInfo, SHARED_FUNCTION_INFO_TYPE) \ + V(Symbol, SYMBOL_TYPE) \ + V(WasmExceptionObject, WASM_EXCEPTION_TYPE) \ + V(WasmGlobalObject, WASM_GLOBAL_TYPE) \ + V(WasmMemoryObject, WASM_MEMORY_TYPE) \ + V(WasmModuleObject, WASM_MODULE_TYPE) \ + V(WasmTableObject, WASM_TABLE_TYPE) \ + V(WeakArrayList, WEAK_ARRAY_LIST_TYPE) \ + V(WeakCell, WEAK_CELL_TYPE) +#ifdef V8_INTL_SUPPORT + +#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \ + TQ_INSTANCE_TYPES_SINGLE_BASE(V) \ + V(JSV8BreakIterator, JS_INTL_V8_BREAK_ITERATOR_TYPE) \ + V(JSCollator, JS_INTL_COLLATOR_TYPE) \ + V(JSDateTimeFormat, JS_INTL_DATE_TIME_FORMAT_TYPE) \ + V(JSListFormat, JS_INTL_LIST_FORMAT_TYPE) \ + V(JSLocale, JS_INTL_LOCALE_TYPE) \ + V(JSNumberFormat, JS_INTL_NUMBER_FORMAT_TYPE) \ + V(JSPluralRules, JS_INTL_PLURAL_RULES_TYPE) \ + V(JSRelativeTimeFormat, JS_INTL_RELATIVE_TIME_FORMAT_TYPE) \ + V(JSSegmentIterator, JS_INTL_SEGMENT_ITERATOR_TYPE) \ + V(JSSegmenter, JS_INTL_SEGMENTER_TYPE) + +#else + +#define TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) TQ_INSTANCE_TYPES_SINGLE_BASE(V) + +#endif // V8_INTL_SUPPORT + +enum class InstanceTypeCheckersSingle { +#define ENUM_VALUE(ClassName, INSTANCE_TYPE) k##ClassName = i::INSTANCE_TYPE, + INSTANCE_TYPE_CHECKERS_SINGLE(ENUM_VALUE) +#undef ENUM_VALUE +}; + +#define CHECK_VALUE(ClassName, INSTANCE_TYPE) \ + static_assert( \ + static_cast<i::InstanceType>( \ + InstanceTypeCheckersSingle::k##ClassName) == i::INSTANCE_TYPE, \ + "TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS must be subset of " \ + "INSTANCE_TYPE_CHECKERS_SINGLE. Invalid class: " #ClassName); +TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(CHECK_VALUE) +#undef CHECK_VALUE + +// Adapts one STRUCT_LIST_GENERATOR entry to (Name, NAME) format. +#define STRUCT_INSTANCE_TYPE_ADAPTER(V, NAME, Name, name) V(Name, NAME) + +#define TQ_INSTANCE_TYPES_SINGLE(V) \ + TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS(V) \ + STRUCT_LIST_GENERATOR(STRUCT_INSTANCE_TYPE_ADAPTER, V) + +// Likewise, these are the subset of INSTANCE_TYPE_CHECKERS_RANGE that have +// definitions in .tq files, rearranged with more specific things first. Also +// includes JSObject and JSReceiver, which in the runtime are optimized to use +// a one-sided check. +#define TQ_INSTANCE_TYPES_RANGE(V) \ + V(Context, FIRST_CONTEXT_TYPE, LAST_CONTEXT_TYPE) \ + V(FixedArray, FIRST_FIXED_ARRAY_TYPE, LAST_FIXED_ARRAY_TYPE) \ + V(Microtask, FIRST_MICROTASK_TYPE, LAST_MICROTASK_TYPE) \ + V(String, FIRST_STRING_TYPE, LAST_STRING_TYPE) \ + V(Name, FIRST_NAME_TYPE, LAST_NAME_TYPE) \ + V(WeakFixedArray, FIRST_WEAK_FIXED_ARRAY_TYPE, LAST_WEAK_FIXED_ARRAY_TYPE) \ + V(JSObject, FIRST_JS_OBJECT_TYPE, LAST_JS_OBJECT_TYPE) \ + V(JSReceiver, FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE) + +std::string AppendAddressAndType(const std::string& brief, uintptr_t address, + const char* type) { + std::stringstream brief_stream; + brief_stream << "0x" << std::hex << address << " <" << type << ">"; + return brief.empty() ? brief_stream.str() + : brief + " (" + brief_stream.str() + ")"; +} + +struct TypedObject { + TypedObject(d::TypeCheckResult type_check_result, + std::unique_ptr<TqObject> object) + : type_check_result(type_check_result), object(std::move(object)) {} + d::TypeCheckResult type_check_result; + std::unique_ptr<TqObject> object; +}; + +TypedObject GetTypedObjectByHint(uintptr_t address, + std::string type_hint_string) { +#define TYPE_NAME_CASE(ClassName, ...) \ + if (type_hint_string == "v8::internal::" #ClassName) { \ + return {d::TypeCheckResult::kUsedTypeHint, \ + v8::base::make_unique<Tq##ClassName>(address)}; \ + } + + TQ_INSTANCE_TYPES_SINGLE(TYPE_NAME_CASE) + TQ_INSTANCE_TYPES_RANGE(TYPE_NAME_CASE) + +#undef TYPE_NAME_CASE + + return {d::TypeCheckResult::kUnknownTypeHint, + v8::base::make_unique<TqHeapObject>(address)}; +} + +TypedObject GetTypedObjectForString(uintptr_t address, i::InstanceType type) { + class StringGetDispatcher : public i::AllStatic { + public: +#define DEFINE_METHOD(ClassName) \ + static inline TypedObject Handle##ClassName(uintptr_t address) { \ + return {d::TypeCheckResult::kUsedMap, \ + v8::base::make_unique<Tq##ClassName>(address)}; \ + } + STRING_CLASS_TYPES(DEFINE_METHOD) +#undef DEFINE_METHOD + static inline TypedObject HandleInvalidString(uintptr_t address) { + return {d::TypeCheckResult::kUnknownInstanceType, + v8::base::make_unique<TqString>(address)}; + } + }; + + return i::StringShape(type) + .DispatchToSpecificTypeWithoutCast<StringGetDispatcher, TypedObject>( + address); +} + +TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor, + const char* type_hint) { + auto heap_object = v8::base::make_unique<TqHeapObject>(address); + Value<uintptr_t> map_ptr = heap_object->GetMapValue(accessor); + + if (map_ptr.validity != d::MemoryAccessResult::kOk) { + return {map_ptr.validity == d::MemoryAccessResult::kAddressNotValid + ? d::TypeCheckResult::kObjectPointerInvalid + : d::TypeCheckResult::kObjectPointerValidButInaccessible, + std::move(heap_object)}; + } + Value<i::InstanceType> type = + TqMap(map_ptr.value).GetInstanceTypeValue(accessor); + + if (type.validity == d::MemoryAccessResult::kOk) { + // Dispatch to the appropriate method for each instance type. After calling + // the generated method to fetch properties, we can add custom properties. + switch (type.value) { +#define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \ + case i::INSTANCE_TYPE: \ + return {d::TypeCheckResult::kUsedMap, \ + v8::base::make_unique<Tq##ClassName>(address)}; + TQ_INSTANCE_TYPES_SINGLE(INSTANCE_TYPE_CASE) +#undef INSTANCE_TYPE_CASE + + default: + + // Special case: concrete subtypes of String are not included in the + // main instance type list because they use the low bits of the instance + // type enum as flags. + if (type.value <= i::LAST_STRING_TYPE) { + return GetTypedObjectForString(address, type.value); + } + +#define INSTANCE_RANGE_CASE(ClassName, FIRST_TYPE, LAST_TYPE) \ + if (type.value >= i::FIRST_TYPE && type.value <= i::LAST_TYPE) { \ + return {d::TypeCheckResult::kUsedMap, \ + v8::base::make_unique<Tq##ClassName>(address)}; \ + } + TQ_INSTANCE_TYPES_RANGE(INSTANCE_RANGE_CASE) +#undef INSTANCE_RANGE_CASE + + return {d::TypeCheckResult::kUnknownInstanceType, + std::move(heap_object)}; + break; + } + } else if (type_hint != nullptr) { + // Try to use the provided type hint, since the real instance type is + // unavailable. + return GetTypedObjectByHint(address, type_hint); + } else { + // TODO(v8:9376): Use known maps here. If known map is just a guess (because + // root pointers weren't provided), then create a synthetic property with + // the more specific type. Then the caller could presumably ask us again + // with the type hint we provided. Otherwise, just go ahead and use it to + // generate properties. + return {type.validity == d::MemoryAccessResult::kAddressNotValid + ? d::TypeCheckResult::kMapPointerInvalid + : d::TypeCheckResult::kMapPointerValidButInaccessible, + std::move(heap_object)}; + } +} + +#undef STRUCT_INSTANCE_TYPE_ADAPTER +#undef TQ_INSTANCE_TYPES_SINGLE_BASE +#undef TQ_INSTANCE_TYPES_SINGLE +#undef TQ_INSTANCE_TYPES_SINGLE_NOSTRUCTS +#undef TQ_INSTANCE_TYPES_RANGE + +// An object visitor that accumulates the first few characters of a string. +class ReadStringVisitor : public TqObjectVisitor { + public: + ReadStringVisitor(d::MemoryAccessor accessor) + : accessor_(accessor), index_(0), limit_(INT32_MAX), done_(false) {} + + // Returns the result as UTF-8 once visiting is complete. + std::string GetString() { + std::vector<char> result( + string_.size() * unibrow::Utf16::kMaxExtraUtf8BytesForOneUtf16CodeUnit); + unsigned write_index = 0; + int prev_char = unibrow::Utf16::kNoPreviousCharacter; + for (size_t read_index = 0; read_index < string_.size(); ++read_index) { + uint16_t character = string_[read_index]; + write_index += + unibrow::Utf8::Encode(result.data() + write_index, character, + prev_char, /*replace_invalid=*/true); + prev_char = character; + } + return {result.data(), write_index}; + } + + template <typename T> + void ReadSeqString(const T* object) { + int32_t length = GetOrFinish(object->GetLengthValue(accessor_)); + for (; index_ < length && index_ < limit_ && !done_; ++index_) { + char16_t c = static_cast<char16_t>( + GetOrFinish(object->GetCharsValue(accessor_, index_))); + if (!done_) AddCharacter(c); + } + } + + void VisitSeqOneByteString(const TqSeqOneByteString* object) override { + ReadSeqString(object); + } + + void VisitSeqTwoByteString(const TqSeqTwoByteString* object) override { + ReadSeqString(object); + } + + void VisitConsString(const TqConsString* object) override { + uintptr_t first_address = GetOrFinish(object->GetFirstValue(accessor_)); + if (done_) return; + auto first = GetTypedHeapObject(first_address, accessor_, nullptr).object; + first->Visit(this); + if (done_) return; + int32_t first_length = GetOrFinish( + static_cast<TqString*>(first.get())->GetLengthValue(accessor_)); + uintptr_t second = GetOrFinish(object->GetSecondValue(accessor_)); + if (done_) return; + IndexModifier modifier(this, -first_length, -first_length); + GetTypedHeapObject(second, accessor_, nullptr).object->Visit(this); + } + + void VisitSlicedString(const TqSlicedString* object) override { + uintptr_t parent = GetOrFinish(object->GetParentValue(accessor_)); + int32_t length = GetOrFinish(object->GetLengthValue(accessor_)); + int32_t offset = i::PlatformSmiTagging::SmiToInt( + GetOrFinish(object->GetOffsetValue(accessor_))); + if (done_) return; + int32_t limit_adjust = offset + length - limit_; + IndexModifier modifier(this, offset, limit_adjust < 0 ? limit_adjust : 0); + GetTypedHeapObject(parent, accessor_, nullptr).object->Visit(this); + } + + void VisitThinString(const TqThinString* object) override { + uintptr_t actual = GetOrFinish(object->GetActualValue(accessor_)); + if (done_) return; + GetTypedHeapObject(actual, accessor_, nullptr).object->Visit(this); + } + + void VisitExternalString(const TqExternalString* object) override { + // TODO(v8:9376): External strings are very common and important when + // attempting to print the source of a function in the browser. For now + // we're just ignoring them, but eventually we'll want some kind of + // mechanism where the user of this library can provide a callback function + // that fetches data from external strings. + AddEllipsisAndFinish(); + } + + void VisitObject(const TqObject* object) override { + // If we fail to find a specific type for a sub-object within a cons string, + // sliced string, or thin string, we will end up here. + AddEllipsisAndFinish(); + } + + private: + // Unpacks a value that was fetched from the debuggee. If the value indicates + // that it couldn't successfully fetch memory, then prevents further work. + template <typename T> + T GetOrFinish(Value<T> value) { + if (value.validity != d::MemoryAccessResult::kOk) { + AddEllipsisAndFinish(); + } + return value.value; + } + + void AddEllipsisAndFinish() { + if (!done_) { + string_ += u"..."; + done_ = true; + } + } + + void AddCharacter(char16_t c) { + if (string_.size() >= kMaxCharacters) { + AddEllipsisAndFinish(); + } else { + string_.push_back(c); + } + } + + // Temporarily adds offsets to both index_ and limit_, to handle ConsString + // and SlicedString. + class IndexModifier { + public: + IndexModifier(ReadStringVisitor* that, int32_t index_adjust, + int32_t limit_adjust) + : that_(that), + index_adjust_(index_adjust), + limit_adjust_(limit_adjust) { + that_->index_ += index_adjust_; + that_->limit_ += limit_adjust_; + } + ~IndexModifier() { + that_->index_ -= index_adjust_; + that_->limit_ -= limit_adjust_; + } + + private: + ReadStringVisitor* that_; + int32_t index_adjust_; + int32_t limit_adjust_; + DISALLOW_COPY_AND_ASSIGN(IndexModifier); + }; + + static constexpr int kMaxCharacters = 80; // How many characters to print. + + std::u16string string_; // Result string. + d::MemoryAccessor accessor_; + int32_t index_; // Index of next char to read. + int32_t limit_; // Don't read past this index (set by SlicedString). + bool done_; // Whether to stop further work. +}; + +// An object visitor that adds extra debugging information for some types. +class AddInfoVisitor : public TqObjectVisitor { + public: + AddInfoVisitor(const std::string& brief, d::MemoryAccessor accessor) + : accessor_(accessor), brief_(brief) {} + + // Returns the brief object description, once visiting is complete. + const std::string& GetBrief() { return brief_; } + + void VisitString(const TqString* object) override { + ReadStringVisitor visitor(accessor_); + object->Visit(&visitor); + if (!brief_.empty()) brief_ += " "; + brief_ += "\"" + visitor.GetString() + "\""; + } + + private: + d::MemoryAccessor accessor_; + std::string brief_; +}; + +std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties( + uintptr_t address, d::MemoryAccessor accessor, const char* type_hint, + std::string brief) { + TypedObject typed = GetTypedHeapObject(address, accessor, type_hint); + + // TODO(v8:9376): Many object types need additional data that is not included + // in their Torque layout definitions. For example, JSObject has an array of + // in-object properties after its Torque-defined fields, which at a minimum + // should be represented as an array in this response. If the relevant memory + // is available, we should instead represent those properties (and any out-of- + // object properties) using their JavaScript property names. + AddInfoVisitor visitor(brief, accessor); + typed.object->Visit(&visitor); + brief = visitor.GetBrief(); + + brief = AppendAddressAndType(brief, address, typed.object->GetName()); + + return v8::base::make_unique<ObjectPropertiesResult>( + typed.type_check_result, brief, typed.object->GetName(), + typed.object->GetProperties(accessor)); +} + +std::unique_ptr<ObjectPropertiesResult> GetHeapObjectProperties( + uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots, + const char* type_hint) { + // Try to figure out the heap range, for pointer compression (this is unused + // if pointer compression is disabled). + uintptr_t any_uncompressed_ptr = 0; + if (!IsPointerCompressed(address)) any_uncompressed_ptr = address; + if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.any_heap_pointer; + if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.map_space; + if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.old_space; + if (any_uncompressed_ptr == 0) any_uncompressed_ptr = roots.read_only_space; + if (any_uncompressed_ptr == 0) { + // We can't figure out the heap range. Just check for known objects. + std::string brief = FindKnownObject(address, roots); + brief = AppendAddressAndType(brief, address, "v8::internal::TaggedValue"); + return v8::base::make_unique<ObjectPropertiesResult>( + d::TypeCheckResult::kUnableToDecompress, brief, + "v8::internal::TaggedValue", + std::vector<std::unique_ptr<ObjectProperty>>()); + } + + // TODO(v8:9376): It seems that the space roots are at predictable offsets + // within the heap reservation block when pointer compression is enabled, so + // we should be able to set those here. + + address = Decompress(address, any_uncompressed_ptr); + // From here on all addresses should be decompressed. + + // Regardless of whether we can read the object itself, maybe we can find its + // pointer in the list of known objects. + std::string brief = FindKnownObject(address, roots); + return GetHeapObjectProperties(address, memory_accessor, type_hint, brief); +} + +std::unique_ptr<ObjectPropertiesResult> GetObjectPropertiesImpl( + uintptr_t address, d::MemoryAccessor memory_accessor, const d::Roots& roots, + const char* type_hint) { + std::vector<std::unique_ptr<ObjectProperty>> props; + if (static_cast<uint32_t>(address) == i::kClearedWeakHeapObjectLower32) { + return v8::base::make_unique<ObjectPropertiesResult>( + d::TypeCheckResult::kWeakRef, "cleared weak ref", + "v8::internal::HeapObject", std::move(props)); + } + bool is_weak = (address & i::kHeapObjectTagMask) == i::kWeakHeapObjectTag; + if (is_weak) { + address &= ~i::kWeakHeapObjectMask; + } + if (i::Internals::HasHeapObjectTag(address)) { + std::unique_ptr<ObjectPropertiesResult> result = + GetHeapObjectProperties(address, memory_accessor, roots, type_hint); + if (is_weak) { + result->Prepend("weak ref to "); + } + return result; + } + + // For smi values, construct a response with a description representing the + // untagged value. + int32_t value = i::PlatformSmiTagging::SmiToInt(address); + std::stringstream stream; + stream << value << " (0x" << std::hex << value << ")"; + return v8::base::make_unique<ObjectPropertiesResult>( + d::TypeCheckResult::kSmi, stream.str(), "v8::internal::Smi", + std::move(props)); +} + +} // namespace v8_debug_helper_internal + +namespace di = v8_debug_helper_internal; + +extern "C" { +V8_DEBUG_HELPER_EXPORT d::ObjectPropertiesResult* +_v8_debug_helper_GetObjectProperties(uintptr_t object, + d::MemoryAccessor memory_accessor, + const d::Roots& heap_roots, + const char* type_hint) { + return di::GetObjectPropertiesImpl(object, memory_accessor, heap_roots, + type_hint) + .release() + ->GetPublicView(); +} +V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_ObjectPropertiesResult( + d::ObjectPropertiesResult* result) { + std::unique_ptr<di::ObjectPropertiesResult> ptr( + static_cast<di::ObjectPropertiesResultExtended*>(result)->base); +} +} diff --git a/deps/v8/tools/debug_helper/heap-constants.cc b/deps/v8/tools/debug_helper/heap-constants.cc new file mode 100644 index 0000000000..2bd0420690 --- /dev/null +++ b/deps/v8/tools/debug_helper/heap-constants.cc @@ -0,0 +1,51 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "heap-constants.h" +#include "src/common/globals.h" + +namespace d = v8::debug_helper; + +namespace v8_debug_helper_internal { + +std::string FindKnownObject(uintptr_t address, const d::Roots& roots) { + uintptr_t containing_page = address & ~i::kPageAlignmentMask; + uintptr_t offset_in_page = address & i::kPageAlignmentMask; + + // If there's a match with a known root, then search only that page. + if (containing_page == roots.map_space) { + return FindKnownObjectInMapSpace(offset_in_page); + } + if (containing_page == roots.old_space) { + return FindKnownObjectInOldSpace(offset_in_page); + } + if (containing_page == roots.read_only_space) { + return FindKnownObjectInReadOnlySpace(offset_in_page); + } + + // For any unknown roots, compile a list of things this object might be. + std::string result; + if (roots.map_space == 0) { + std::string sub_result = FindKnownObjectInMapSpace(offset_in_page); + if (!sub_result.empty()) { + result += "maybe " + sub_result; + } + } + if (roots.old_space == 0) { + std::string sub_result = FindKnownObjectInOldSpace(offset_in_page); + if (!sub_result.empty()) { + result = (result.empty() ? "" : result + ", ") + "maybe " + sub_result; + } + } + if (roots.read_only_space == 0) { + std::string sub_result = FindKnownObjectInReadOnlySpace(offset_in_page); + if (!sub_result.empty()) { + result = (result.empty() ? "" : result + ", ") + "maybe " + sub_result; + } + } + + return result; +} + +} // namespace v8_debug_helper_internal diff --git a/deps/v8/tools/debug_helper/heap-constants.h b/deps/v8/tools/debug_helper/heap-constants.h new file mode 100644 index 0000000000..f3149bbb47 --- /dev/null +++ b/deps/v8/tools/debug_helper/heap-constants.h @@ -0,0 +1,28 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_TOOLS_DEBUG_HELPER_HEAP_CONSTANTS_H_ +#define V8_TOOLS_DEBUG_HELPER_HEAP_CONSTANTS_H_ + +#include <cstdint> +#include <string> + +#include "debug-helper.h" + +namespace d = v8::debug_helper; + +namespace v8_debug_helper_internal { + +// Functions generated by mkgrokdump: +std::string FindKnownObjectInOldSpace(uintptr_t offset); +std::string FindKnownObjectInReadOnlySpace(uintptr_t offset); +std::string FindKnownObjectInMapSpace(uintptr_t offset); +std::string FindKnownMapInstanceTypeInMapSpace(uintptr_t offset); +std::string FindKnownMapInstanceTypeInReadOnlySpace(uintptr_t offset); + +std::string FindKnownObject(uintptr_t address, const d::Roots& roots); + +} // namespace v8_debug_helper_internal + +#endif |