| // 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/external-pointer.h" |
| #include "src/execution/frame-constants.h" |
| #include "src/execution/frames.h" |
| #include "src/execution/isolate-utils.h" |
| #include "src/objects/string-inl.h" |
| #include "src/strings/unicode-inl.h" |
| #include "torque-generated/class-debug-readers.h" |
| |
| namespace i = v8::internal; |
| |
| namespace v8 { |
| namespace internal { |
| namespace debug_helper_internal { |
| |
| constexpr char kObject[] = "v8::internal::Object"; |
| constexpr char kTaggedValue[] = "v8::internal::TaggedValue"; |
| constexpr char kSmi[] = "v8::internal::Smi"; |
| constexpr char kHeapObject[] = "v8::internal::HeapObject"; |
| #ifdef V8_COMPRESS_POINTERS |
| constexpr char kObjectAsStoredInHeap[] = "v8::internal::TaggedValue"; |
| #else |
| constexpr char kObjectAsStoredInHeap[] = "v8::internal::Object"; |
| #endif |
| |
| 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() + ")"; |
| } |
| |
| std::string JoinWithSpace(const std::string& a, const std::string& b) { |
| return a.empty() || b.empty() ? a + b : a + " " + b; |
| } |
| |
| struct TypedObject { |
| TypedObject(d::TypeCheckResult type_check_result, |
| std::unique_ptr<TqObject> object) |
| : type_check_result(type_check_result), object(std::move(object)) {} |
| |
| // How we discovered the object's type, or why we failed to do so. |
| d::TypeCheckResult type_check_result; |
| |
| // Pointer to some TqObject subclass, representing the most specific known |
| // type for the object. |
| std::unique_ptr<TqObject> object; |
| |
| // Collection of other guesses at more specific types than the one represented |
| // by |object|. |
| std::vector<TypedObject> possible_types; |
| }; |
| |
| 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, \ |
| std::make_unique<Tq##ClassName>(address)}; \ |
| } |
| |
| TORQUE_INSTANCE_CHECKERS_SINGLE_FULLY_DEFINED(TYPE_NAME_CASE) |
| TORQUE_INSTANCE_CHECKERS_RANGE_FULLY_DEFINED(TYPE_NAME_CASE) |
| STRING_CLASS_TYPES(TYPE_NAME_CASE) |
| |
| #undef TYPE_NAME_CASE |
| |
| return {d::TypeCheckResult::kUnknownTypeHint, |
| std::make_unique<TqHeapObject>(address)}; |
| } |
| |
| TypedObject GetTypedObjectForString(uintptr_t address, i::InstanceType type, |
| d::TypeCheckResult type_source) { |
| class StringGetDispatcher : public i::AllStatic { |
| public: |
| #define DEFINE_METHOD(ClassName) \ |
| static inline TypedObject Handle##ClassName( \ |
| uintptr_t address, d::TypeCheckResult type_source) { \ |
| return {type_source, std::make_unique<Tq##ClassName>(address)}; \ |
| } |
| STRING_CLASS_TYPES(DEFINE_METHOD) |
| #undef DEFINE_METHOD |
| static inline TypedObject HandleInvalidString( |
| uintptr_t address, d::TypeCheckResult type_source) { |
| return {d::TypeCheckResult::kUnknownInstanceType, |
| std::make_unique<TqString>(address)}; |
| } |
| }; |
| |
| return i::StringShape(type) |
| .DispatchToSpecificTypeWithoutCast<StringGetDispatcher, TypedObject>( |
| address, type_source); |
| } |
| |
| TypedObject GetTypedObjectByInstanceType(uintptr_t address, |
| i::InstanceType type, |
| d::TypeCheckResult type_source) { |
| switch (type) { |
| #define INSTANCE_TYPE_CASE(ClassName, INSTANCE_TYPE) \ |
| case i::INSTANCE_TYPE: \ |
| return {type_source, std::make_unique<Tq##ClassName>(address)}; |
| TORQUE_INSTANCE_CHECKERS_SINGLE_FULLY_DEFINED(INSTANCE_TYPE_CASE) |
| TORQUE_INSTANCE_CHECKERS_MULTIPLE_FULLY_DEFINED(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 <= i::LAST_STRING_TYPE) { |
| return GetTypedObjectForString(address, type, type_source); |
| } |
| |
| #define INSTANCE_RANGE_CASE(ClassName, FIRST_TYPE, LAST_TYPE) \ |
| if (type >= i::FIRST_TYPE && type <= i::LAST_TYPE) { \ |
| return {type_source, std::make_unique<Tq##ClassName>(address)}; \ |
| } |
| TORQUE_INSTANCE_CHECKERS_RANGE_FULLY_DEFINED(INSTANCE_RANGE_CASE) |
| #undef INSTANCE_RANGE_CASE |
| |
| return {d::TypeCheckResult::kUnknownInstanceType, |
| std::make_unique<TqHeapObject>(address)}; |
| } |
| } |
| |
| TypedObject GetTypedHeapObject(uintptr_t address, d::MemoryAccessor accessor, |
| const char* type_hint, |
| const d::HeapAddresses& heap_addresses) { |
| auto heap_object = std::make_unique<TqHeapObject>(address); |
| Value<uintptr_t> map_ptr = heap_object->GetMapValue(accessor); |
| |
| if (map_ptr.validity != d::MemoryAccessResult::kOk) { |
| // If we can't read the Map pointer from the object, then we likely can't |
| // read anything else, so there's not any point in attempting to use the |
| // type hint. Just return a failure. |
| 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) { |
| return GetTypedObjectByInstanceType(address, type.value, |
| d::TypeCheckResult::kUsedMap); |
| } |
| |
| // We can't read the Map, so check whether it is in the list of known Maps, |
| // as another way to get its instance type. |
| KnownInstanceType known_map_type = |
| FindKnownMapInstanceTypes(map_ptr.value, heap_addresses); |
| if (known_map_type.confidence == KnownInstanceType::Confidence::kHigh) { |
| DCHECK_EQ(known_map_type.types.size(), 1); |
| return GetTypedObjectByInstanceType(address, known_map_type.types[0], |
| d::TypeCheckResult::kKnownMapPointer); |
| } |
| |
| // Create a basic result that says that the object is a HeapObject and we |
| // couldn't read its Map. |
| TypedObject result = { |
| type.validity == d::MemoryAccessResult::kAddressNotValid |
| ? d::TypeCheckResult::kMapPointerInvalid |
| : d::TypeCheckResult::kMapPointerValidButInaccessible, |
| std::move(heap_object)}; |
| |
| // If a type hint is available, it may give us something more specific than |
| // HeapObject. However, a type hint of Object would be even less specific, so |
| // we'll only use the type hint if it's a subclass of HeapObject. |
| if (type_hint != nullptr) { |
| TypedObject hint_result = GetTypedObjectByHint(address, type_hint); |
| if (result.object->IsSuperclassOf(hint_result.object.get())) { |
| result = std::move(hint_result); |
| } |
| } |
| |
| // If low-confidence results are available from known Maps, include them only |
| // if they don't contradict the primary type and would provide some additional |
| // specificity. |
| for (const i::InstanceType type_guess : known_map_type.types) { |
| TypedObject guess_result = GetTypedObjectByInstanceType( |
| address, type_guess, d::TypeCheckResult::kKnownMapPointer); |
| if (result.object->IsSuperclassOf(guess_result.object.get())) { |
| result.possible_types.push_back(std::move(guess_result)); |
| } |
| } |
| |
| return result; |
| } |
| |
| // An object visitor that accumulates the first few characters of a string. |
| class ReadStringVisitor : public TqObjectVisitor { |
| public: |
| static v8::base::Optional<std::string> Visit( |
| d::MemoryAccessor accessor, const d::HeapAddresses& heap_addresses, |
| const TqString* object) { |
| ReadStringVisitor visitor(accessor, heap_addresses); |
| object->Visit(&visitor); |
| return visitor.GetString(); |
| } |
| |
| // Returns the result as UTF-8 once visiting is complete. |
| v8::base::Optional<std::string> GetString() { |
| if (failed_) return {}; |
| 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 std::string(result.data(), write_index); |
| } |
| |
| template <typename TChar> |
| Value<TChar> ReadCharacter(uintptr_t data_address, int32_t index) { |
| TChar value{}; |
| d::MemoryAccessResult validity = |
| accessor_(data_address + index * sizeof(TChar), |
| reinterpret_cast<uint8_t*>(&value), sizeof(value)); |
| return {validity, value}; |
| } |
| |
| template <typename TChar> |
| void ReadStringCharacters(const TqString* object, uintptr_t data_address) { |
| int32_t length = GetOrFinish(object->GetLengthValue(accessor_)); |
| for (; index_ < length && index_ < limit_ && !done_; ++index_) { |
| STATIC_ASSERT(sizeof(TChar) <= sizeof(char16_t)); |
| char16_t c = static_cast<char16_t>( |
| GetOrFinish(ReadCharacter<TChar>(data_address, index_))); |
| if (!done_) AddCharacter(c); |
| } |
| } |
| |
| template <typename TChar, typename TString> |
| void ReadSeqString(const TString* object) { |
| ReadStringCharacters<TChar>(object, object->GetCharsAddress()); |
| } |
| |
| void VisitSeqOneByteString(const TqSeqOneByteString* object) override { |
| ReadSeqString<char>(object); |
| } |
| |
| void VisitSeqTwoByteString(const TqSeqTwoByteString* object) override { |
| ReadSeqString<char16_t>(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, heap_addresses_) |
| .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, heap_addresses_) |
| .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, heap_addresses_) |
| .object->Visit(this); |
| } |
| |
| void VisitThinString(const TqThinString* object) override { |
| uintptr_t actual = GetOrFinish(object->GetActualValue(accessor_)); |
| if (done_) return; |
| GetTypedHeapObject(actual, accessor_, nullptr, heap_addresses_) |
| .object->Visit(this); |
| } |
| |
| bool IsExternalStringCached(const TqExternalString* object) { |
| // The safest way to get the instance type is to use known map pointers, in |
| // case the map data is not available. |
| uintptr_t map = GetOrFinish(object->GetMapValue(accessor_)); |
| if (done_) return false; |
| auto instance_types = FindKnownMapInstanceTypes(map, heap_addresses_); |
| // Exactly one of the matched instance types should be a string type, |
| // because all maps for string types are in the same space (read-only |
| // space). The "uncached" flag on that instance type tells us whether it's |
| // safe to read the cached data. |
| for (const auto& type : instance_types.types) { |
| if ((type & i::kIsNotStringMask) == i::kStringTag && |
| (type & i::kStringRepresentationMask) == i::kExternalStringTag) { |
| return (type & i::kUncachedExternalStringMask) != |
| i::kUncachedExternalStringTag; |
| } |
| } |
| |
| // If for some reason we can't find an external string type here (maybe the |
| // caller provided an external string type as the type hint, but it doesn't |
| // actually match the in-memory map pointer), then we can't safely use the |
| // cached data. |
| return false; |
| } |
| |
| template <typename TChar> |
| void ReadExternalString(const TqExternalString* object) { |
| // Cached external strings are easy to read; uncached external strings |
| // require knowledge of the embedder. For now, we only read cached external |
| // strings. |
| if (IsExternalStringCached(object)) { |
| ExternalPointer_t resource_data = |
| GetOrFinish(object->GetResourceDataValue(accessor_)); |
| #ifdef V8_COMPRESS_POINTERS |
| uintptr_t data_address = static_cast<uintptr_t>( |
| DecodeExternalPointer(GetIsolateForPtrComprFromOnHeapAddress( |
| heap_addresses_.any_heap_pointer), |
| resource_data, kExternalStringResourceDataTag)); |
| #else |
| uintptr_t data_address = static_cast<uintptr_t>(resource_data); |
| #endif // V8_COMPRESS_POINTERS |
| if (done_) return; |
| ReadStringCharacters<TChar>(object, data_address); |
| } else { |
| // TODO(v8:9376): Come up with some way that a caller with full knowledge |
| // of a particular embedder could provide a callback function for getting |
| // uncached string data. |
| AddEllipsisAndFinish(); |
| } |
| } |
| |
| void VisitExternalOneByteString( |
| const TqExternalOneByteString* object) override { |
| ReadExternalString<char>(object); |
| } |
| |
| void VisitExternalTwoByteString( |
| const TqExternalTwoByteString* object) override { |
| ReadExternalString<char16_t>(object); |
| } |
| |
| 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: |
| ReadStringVisitor(d::MemoryAccessor accessor, |
| const d::HeapAddresses& heap_addresses) |
| : accessor_(accessor), |
| heap_addresses_(heap_addresses), |
| index_(0), |
| limit_(INT32_MAX), |
| done_(false), |
| failed_(false) {} |
| |
| // 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_) { |
| done_ = true; |
| if (string_.empty()) { |
| failed_ = true; |
| } else { |
| string_ += u"..."; |
| } |
| } |
| } |
| |
| 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_; |
| const d::HeapAddresses& heap_addresses_; |
| 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. |
| bool failed_; // Whether an error was encountered before any valid data. |
| }; |
| |
| // An object visitor that supplies extra information for some types. |
| class AddInfoVisitor : public TqObjectVisitor { |
| public: |
| // Returns a descriptive string and a list of properties for the given object. |
| // Both may be empty, and are meant as an addition or a replacement for, |
| // the Torque-generated data about the object. |
| static std::pair<std::string, std::vector<std::unique_ptr<ObjectProperty>>> |
| Visit(const TqObject* object, d::MemoryAccessor accessor, |
| const d::HeapAddresses& heap_addresses) { |
| AddInfoVisitor visitor(accessor, heap_addresses); |
| object->Visit(&visitor); |
| return {std::move(visitor.brief_), std::move(visitor.properties_)}; |
| } |
| |
| void VisitString(const TqString* object) override { |
| auto str = ReadStringVisitor::Visit(accessor_, heap_addresses_, object); |
| if (str.has_value()) { |
| brief_ = "\"" + *str + "\""; |
| } |
| } |
| |
| void VisitExternalString(const TqExternalString* object) override { |
| VisitString(object); |
| // Cast resource field to v8::String::ExternalStringResourceBase* would add |
| // more info. |
| properties_.push_back(std::make_unique<ObjectProperty>( |
| "resource", |
| CheckTypeName<v8::String::ExternalStringResourceBase*>( |
| "v8::String::ExternalStringResourceBase*"), |
| CheckTypeName<v8::String::ExternalStringResourceBase*>( |
| "v8::String::ExternalStringResourceBase*"), |
| object->GetResourceAddress(), 1, |
| sizeof(v8::String::ExternalStringResourceBase*), |
| std::vector<std::unique_ptr<StructProperty>>(), |
| d::PropertyKind::kSingle)); |
| } |
| |
| void VisitJSObject(const TqJSObject* object) override { |
| // JSObject and its subclasses can be followed directly by an array of |
| // property values. The start and end offsets of those values are described |
| // by a pair of values in its Map. |
| auto map_ptr = object->GetMapValue(accessor_); |
| if (map_ptr.validity != d::MemoryAccessResult::kOk) { |
| return; // Can't read the JSObject. Nothing useful to do. |
| } |
| TqMap map(map_ptr.value); |
| |
| // On JSObject instances, this value is the start of in-object properties. |
| // The constructor function index option is only for primitives. |
| auto start_offset = |
| map.GetInObjectPropertiesStartOrConstructorFunctionIndexValue( |
| accessor_); |
| |
| // The total size of the object in memory. This may include over-allocated |
| // expansion space that doesn't correspond to any user-accessible property. |
| auto instance_size = map.GetInstanceSizeInWordsValue(accessor_); |
| |
| if (start_offset.validity != d::MemoryAccessResult::kOk || |
| instance_size.validity != d::MemoryAccessResult::kOk) { |
| return; // Can't read the Map. Nothing useful to do. |
| } |
| int num_properties = instance_size.value - start_offset.value; |
| if (num_properties > 0) { |
| properties_.push_back(std::make_unique<ObjectProperty>( |
| "in-object properties", kObjectAsStoredInHeap, kObject, |
| object->GetMapAddress() + start_offset.value * i::kTaggedSize, |
| num_properties, i::kTaggedSize, |
| std::vector<std::unique_ptr<StructProperty>>(), |
| d::PropertyKind::kArrayOfKnownSize)); |
| } |
| } |
| |
| private: |
| AddInfoVisitor(d::MemoryAccessor accessor, |
| const d::HeapAddresses& heap_addresses) |
| : accessor_(accessor), heap_addresses_(heap_addresses) {} |
| |
| // Inputs used by this visitor: |
| |
| d::MemoryAccessor accessor_; |
| const d::HeapAddresses& heap_addresses_; |
| |
| // Outputs generated by this visitor: |
| |
| // A brief description of the object. |
| std::string brief_; |
| // A list of extra properties to append after the automatic ones that are |
| // created for all Torque-defined class fields. |
| std::vector<std::unique_ptr<ObjectProperty>> properties_; |
| }; |
| |
| std::unique_ptr<ObjectPropertiesResult> GetHeapObjectPropertiesNotCompressed( |
| uintptr_t address, d::MemoryAccessor accessor, const char* type_hint, |
| const d::HeapAddresses& heap_addresses) { |
| // 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, heap_addresses); |
| |
| TypedObject typed = |
| GetTypedHeapObject(address, accessor, type_hint, heap_addresses); |
| auto props = typed.object->GetProperties(accessor); |
| |
| // Use the AddInfoVisitor to get any extra properties or descriptive text that |
| // can't be directly derived from Torque class definitions. |
| auto extra_info = |
| AddInfoVisitor::Visit(typed.object.get(), accessor, heap_addresses); |
| brief = JoinWithSpace(brief, extra_info.first); |
| |
| // Overwrite existing properties if they have the same name. |
| for (size_t i = 0; i < extra_info.second.size(); i++) { |
| bool overwrite = false; |
| for (size_t j = 0; j < props.size(); j++) { |
| if (strcmp(props[j]->GetPublicView()->name, |
| extra_info.second[i]->GetPublicView()->name) == 0) { |
| props[j] = std::move(extra_info.second[i]); |
| overwrite = true; |
| break; |
| } |
| } |
| if (overwrite) continue; |
| props.push_back(std::move(extra_info.second[i])); |
| } |
| |
| brief = AppendAddressAndType(brief, address, typed.object->GetName()); |
| |
| // Convert the low-confidence guessed types to a list of strings as expected |
| // for the response. |
| std::vector<std::string> guessed_types; |
| for (const auto& guess : typed.possible_types) { |
| guessed_types.push_back(guess.object->GetName()); |
| } |
| |
| return std::make_unique<ObjectPropertiesResult>( |
| typed.type_check_result, brief, typed.object->GetName(), std::move(props), |
| std::move(guessed_types)); |
| } |
| |
| std::unique_ptr<ObjectPropertiesResult> GetHeapObjectPropertiesMaybeCompressed( |
| uintptr_t address, d::MemoryAccessor memory_accessor, |
| d::HeapAddresses heap_addresses, 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 = heap_addresses.any_heap_pointer; |
| if (any_uncompressed_ptr == 0) |
| any_uncompressed_ptr = heap_addresses.map_space_first_page; |
| if (any_uncompressed_ptr == 0) |
| any_uncompressed_ptr = heap_addresses.old_space_first_page; |
| if (any_uncompressed_ptr == 0) |
| any_uncompressed_ptr = heap_addresses.read_only_space_first_page; |
| FillInUnknownHeapAddresses(&heap_addresses, any_uncompressed_ptr); |
| if (any_uncompressed_ptr == 0) { |
| // We can't figure out the heap range. Just check for known objects. |
| std::string brief = FindKnownObject(address, heap_addresses); |
| brief = AppendAddressAndType(brief, address, kTaggedValue); |
| return std::make_unique<ObjectPropertiesResult>( |
| d::TypeCheckResult::kUnableToDecompress, brief, kTaggedValue); |
| } |
| |
| address = EnsureDecompressed(address, any_uncompressed_ptr); |
| |
| return GetHeapObjectPropertiesNotCompressed(address, memory_accessor, |
| type_hint, heap_addresses); |
| } |
| |
| std::unique_ptr<ObjectPropertiesResult> GetObjectProperties( |
| uintptr_t address, d::MemoryAccessor memory_accessor, |
| const d::HeapAddresses& heap_addresses, const char* type_hint) { |
| if (static_cast<uint32_t>(address) == i::kClearedWeakHeapObjectLower32) { |
| return std::make_unique<ObjectPropertiesResult>( |
| d::TypeCheckResult::kWeakRef, "cleared weak ref", kHeapObject); |
| } |
| bool is_weak = (address & i::kHeapObjectTagMask) == i::kWeakHeapObjectTag; |
| if (is_weak) { |
| address &= ~i::kWeakHeapObjectMask; |
| } |
| if (i::Internals::HasHeapObjectTag(address)) { |
| std::unique_ptr<ObjectPropertiesResult> result = |
| GetHeapObjectPropertiesMaybeCompressed(address, memory_accessor, |
| heap_addresses, 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 std::make_unique<ObjectPropertiesResult>(d::TypeCheckResult::kSmi, |
| stream.str(), kSmi); |
| } |
| |
| std::unique_ptr<StackFrameResult> GetStackFrame( |
| uintptr_t frame_pointer, d::MemoryAccessor memory_accessor) { |
| // Read the data at frame_pointer + kContextOrFrameTypeOffset. |
| intptr_t context_or_frame_type = 0; |
| d::MemoryAccessResult validity = memory_accessor( |
| frame_pointer + CommonFrameConstants::kContextOrFrameTypeOffset, |
| reinterpret_cast<void*>(&context_or_frame_type), sizeof(intptr_t)); |
| auto props = std::vector<std::unique_ptr<ObjectProperty>>(); |
| if (validity == d::MemoryAccessResult::kOk) { |
| // If it is context, not frame marker then add new property |
| // "currently_executing_function". |
| if (!StackFrame::IsTypeMarker(context_or_frame_type)) { |
| props.push_back(std::make_unique<ObjectProperty>( |
| "currently_executing_jsfunction", |
| CheckTypeName<v8::internal::JSFunction>("v8::internal::JSFunction"), |
| CheckTypeName<v8::internal::JSFunction*>("v8::internal::JSFunction"), |
| frame_pointer + StandardFrameConstants::kFunctionOffset, 1, |
| sizeof(v8::internal::JSFunction), |
| std::vector<std::unique_ptr<StructProperty>>(), |
| d::PropertyKind::kSingle)); |
| } |
| } |
| |
| return std::make_unique<StackFrameResult>(std::move(props)); |
| } |
| |
| } // namespace debug_helper_internal |
| } // namespace internal |
| } // namespace v8 |
| |
| namespace di = v8::internal::debug_helper_internal; |
| |
| extern "C" { |
| V8_DEBUG_HELPER_EXPORT d::ObjectPropertiesResult* |
| _v8_debug_helper_GetObjectProperties(uintptr_t object, |
| d::MemoryAccessor memory_accessor, |
| const d::HeapAddresses& heap_addresses, |
| const char* type_hint) { |
| return di::GetObjectProperties(object, memory_accessor, heap_addresses, |
| 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); |
| } |
| |
| V8_DEBUG_HELPER_EXPORT d::StackFrameResult* _v8_debug_helper_GetStackFrame( |
| uintptr_t frame_pointer, d::MemoryAccessor memory_accessor) { |
| return di::GetStackFrame(frame_pointer, memory_accessor) |
| .release() |
| ->GetPublicView(); |
| } |
| V8_DEBUG_HELPER_EXPORT void _v8_debug_helper_Free_StackFrameResult( |
| d::StackFrameResult* result) { |
| std::unique_ptr<di::StackFrameResult> ptr( |
| static_cast<di::StackFrameResultExtended*>(result)->base); |
| } |
| } |