// Copyright 2011 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 "src/diagnostics/disassembler.h" #include <algorithm> #include <iomanip> #include <memory> #include <sstream> #include <unordered_map> #include <vector> #include "src/base/memory.h" #include "src/base/strings.h" #include "src/base/vector.h" #include "src/codegen/assembler-inl.h" #include "src/codegen/code-comments.h" #include "src/codegen/code-reference.h" #include "src/codegen/external-reference-encoder.h" #include "src/codegen/macro-assembler.h" #include "src/debug/debug.h" #include "src/deoptimizer/deoptimizer.h" #include "src/diagnostics/disasm.h" #include "src/execution/isolate-data.h" #include "src/ic/ic.h" #include "src/objects/objects-inl.h" #include "src/snapshot/embedded/embedded-data.h" #include "src/strings/string-stream.h" #if V8_ENABLE_WEBASSEMBLY #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-engine.h" #endif // V8_ENABLE_WEBASSEMBLY namespace v8 { namespace internal { #ifdef ENABLE_DISASSEMBLER class V8NameConverter : public disasm::NameConverter { public: explicit V8NameConverter(Isolate* isolate, CodeReference code = {}) : isolate_(isolate), code_(code) {} const char* NameOfAddress(byte* pc) const override; const char* NameInCode(byte* addr) const override; const char* RootRelativeName(int offset) const override; const CodeReference& code() const { return code_; } private: void InitExternalRefsCache() const; Isolate* isolate_; CodeReference code_; base::EmbeddedVector<char, 128> v8_buffer_; // Map from root-register relative offset of the external reference value to // the external reference name (stored in the external reference table). // This cache is used to recognize [root_reg + offs] patterns as direct // access to certain external reference's value. mutable std::unordered_map<int, const char*> directly_accessed_external_refs_; }; void V8NameConverter::InitExternalRefsCache() const { ExternalReferenceTable* external_reference_table = isolate_->external_reference_table(); if (!external_reference_table->is_initialized()) return; base::AddressRegion addressable_region = isolate_->root_register_addressable_region(); Address isolate_root = isolate_->isolate_root(); for (uint32_t i = 0; i < ExternalReferenceTable::kSize; i++) { Address address = external_reference_table->address(i); if (addressable_region.contains(address)) { int offset = static_cast<int>(address - isolate_root); const char* name = external_reference_table->name(i); directly_accessed_external_refs_.insert({offset, name}); } } } const char* V8NameConverter::NameOfAddress(byte* pc) const { if (!code_.is_null()) { const char* name = isolate_ ? isolate_->builtins()->Lookup(reinterpret_cast<Address>(pc)) : nullptr; if (name != nullptr) { SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc), name); return v8_buffer_.begin(); } int offs = static_cast<int>(reinterpret_cast<Address>(pc) - code_.instruction_start()); // print as code offset, if it seems reasonable if (0 <= offs && offs < code_.instruction_size()) { SNPrintF(v8_buffer_, "%p <+0x%x>", static_cast<void*>(pc), offs); return v8_buffer_.begin(); } #if V8_ENABLE_WEBASSEMBLY wasm::WasmCodeRefScope wasm_code_ref_scope; if (auto* wasm_code = wasm::GetWasmCodeManager()->LookupCode( reinterpret_cast<Address>(pc))) { SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc), wasm::GetWasmCodeKindAsString(wasm_code->kind())); return v8_buffer_.begin(); } #endif // V8_ENABLE_WEBASSEMBLY } return disasm::NameConverter::NameOfAddress(pc); } const char* V8NameConverter::NameInCode(byte* addr) const { // The V8NameConverter is used for well known code, so we can "safely" // dereference pointers in generated code. return code_.is_null() ? "" : reinterpret_cast<const char*>(addr); } const char* V8NameConverter::RootRelativeName(int offset) const { if (isolate_ == nullptr) return nullptr; const int kRootsTableStart = IsolateData::roots_table_offset(); const unsigned kRootsTableSize = sizeof(RootsTable); const int kExtRefsTableStart = IsolateData::external_reference_table_offset(); const unsigned kExtRefsTableSize = ExternalReferenceTable::kSizeInBytes; const int kBuiltinTier0TableStart = IsolateData::builtin_tier0_table_offset(); const unsigned kBuiltinTier0TableSize = Builtins::kBuiltinTier0Count * kSystemPointerSize; const int kBuiltinTableStart = IsolateData::builtin_table_offset(); const unsigned kBuiltinTableSize = Builtins::kBuiltinCount * kSystemPointerSize; if (static_cast<unsigned>(offset - kRootsTableStart) < kRootsTableSize) { uint32_t offset_in_roots_table = offset - kRootsTableStart; // Fail safe in the unlikely case of an arbitrary root-relative offset. if (offset_in_roots_table % kSystemPointerSize != 0) return nullptr; RootIndex root_index = static_cast<RootIndex>(offset_in_roots_table / kSystemPointerSize); SNPrintF(v8_buffer_, "root (%s)", RootsTable::name(root_index)); return v8_buffer_.begin(); } else if (static_cast<unsigned>(offset - kExtRefsTableStart) < kExtRefsTableSize) { uint32_t offset_in_extref_table = offset - kExtRefsTableStart; // Fail safe in the unlikely case of an arbitrary root-relative offset. if (offset_in_extref_table % ExternalReferenceTable::kEntrySize != 0) { return nullptr; } // Likewise if the external reference table is uninitialized. if (!isolate_->external_reference_table()->is_initialized()) { return nullptr; } SNPrintF(v8_buffer_, "external reference (%s)", isolate_->external_reference_table()->NameFromOffset( offset_in_extref_table)); return v8_buffer_.begin(); } else if (static_cast<unsigned>(offset - kBuiltinTier0TableStart) < kBuiltinTier0TableSize) { uint32_t offset_in_builtins_table = (offset - kBuiltinTier0TableStart); Builtin builtin = Builtins::FromInt(offset_in_builtins_table / kSystemPointerSize); const char* name = Builtins::name(builtin); SNPrintF(v8_buffer_, "builtin (%s)", name); return v8_buffer_.begin(); } else if (static_cast<unsigned>(offset - kBuiltinTableStart) < kBuiltinTableSize) { uint32_t offset_in_builtins_table = (offset - kBuiltinTableStart); Builtin builtin = Builtins::FromInt(offset_in_builtins_table / kSystemPointerSize); const char* name = Builtins::name(builtin); SNPrintF(v8_buffer_, "builtin (%s)", name); return v8_buffer_.begin(); } else { // It must be a direct access to one of the external values. if (directly_accessed_external_refs_.empty()) { InitExternalRefsCache(); } auto iter = directly_accessed_external_refs_.find(offset); if (iter != directly_accessed_external_refs_.end()) { SNPrintF(v8_buffer_, "external value (%s)", iter->second); return v8_buffer_.begin(); } return nullptr; } } // Output the contents of the string stream and empty it. static void DumpBuffer(std::ostream& os, std::ostringstream& out) { os << out.str() << std::endl; out.str(""); } static const int kRelocInfoPosition = 57; static void PrintRelocInfo(std::ostringstream& out, Isolate* isolate, const ExternalReferenceEncoder* ref_encoder, std::ostream& os, CodeReference host, RelocInfo* relocinfo, bool first_reloc_info = true) { // Indent the printing of the reloc info. int padding = kRelocInfoPosition; if (first_reloc_info) { // The first reloc info is printed after the disassembled instruction. padding -= std::min(padding, static_cast<int>(out.tellp())); } else { // Additional reloc infos are printed on separate lines. DumpBuffer(os, out); } std::fill_n(std::ostream_iterator<char>(out), padding, ' '); RelocInfo::Mode rmode = relocinfo->rmode(); if (rmode == RelocInfo::DEOPT_SCRIPT_OFFSET) { out << " ;; debug: deopt position, script offset '" << static_cast<int>(relocinfo->data()) << "'"; } else if (rmode == RelocInfo::DEOPT_INLINING_ID) { out << " ;; debug: deopt position, inlining id '" << static_cast<int>(relocinfo->data()) << "'"; } else if (rmode == RelocInfo::DEOPT_REASON) { DeoptimizeReason reason = static_cast<DeoptimizeReason>(relocinfo->data()); out << " ;; debug: deopt reason '" << DeoptimizeReasonToString(reason) << "'"; } else if (rmode == RelocInfo::DEOPT_ID) { out << " ;; debug: deopt index " << static_cast<int>(relocinfo->data()); } else if (rmode == RelocInfo::DEOPT_NODE_ID) { #ifdef DEBUG out << " ;; debug: deopt node id " << static_cast<uint32_t>(relocinfo->data()); #else // DEBUG UNREACHABLE(); #endif // DEBUG } else if (RelocInfo::IsEmbeddedObjectMode(rmode)) { HeapStringAllocator allocator; StringStream accumulator(&allocator); relocinfo->target_object(isolate).ShortPrint(&accumulator); std::unique_ptr<char[]> obj_name = accumulator.ToCString(); const bool is_compressed = RelocInfo::IsCompressedEmbeddedObject(rmode); out << " ;; " << (is_compressed ? "(compressed) " : "") << "object: " << obj_name.get(); } else if (rmode == RelocInfo::EXTERNAL_REFERENCE) { Address address = relocinfo->target_external_reference(); const char* reference_name = ref_encoder ? ref_encoder->NameOfAddress(isolate, address) : ExternalReferenceTable::NameOfIsolateIndependentAddress(address); out << " ;; external reference (" << reference_name << ")"; } else if (RelocInfo::IsCodeTargetMode(rmode)) { out << " ;; code:"; CodeT code = isolate->heap() ->GcSafeFindCodeForInnerPointer(relocinfo->target_address()) .ToCodeT(); CodeKind kind = code.kind(); if (code.is_builtin()) { out << " Builtin::" << Builtins::name(code.builtin_id()); } else { out << " " << CodeKindToString(kind); } #if V8_ENABLE_WEBASSEMBLY } else if (RelocInfo::IsWasmStubCall(rmode) && host.is_wasm_code()) { // Host is isolate-independent, try wasm native module instead. const char* runtime_stub_name = GetRuntimeStubName( host.as_wasm_code()->native_module()->GetRuntimeStubId( relocinfo->wasm_stub_call_address())); out << " ;; wasm stub: " << runtime_stub_name; #endif // V8_ENABLE_WEBASSEMBLY } else if (RelocInfo::IsRuntimeEntry(rmode) && isolate != nullptr) { // A runtime entry relocinfo might be a deoptimization bailout. Address addr = relocinfo->target_address(); DeoptimizeKind type; if (Deoptimizer::IsDeoptimizationEntry(isolate, addr, &type)) { out << " ;; " << Deoptimizer::MessageFor(type) << " deoptimization bailout"; } else { out << " ;; " << RelocInfo::RelocModeName(rmode); } } else { out << " ;; " << RelocInfo::RelocModeName(rmode); } } static int DecodeIt(Isolate* isolate, ExternalReferenceEncoder* ref_encoder, std::ostream& os, CodeReference code, const V8NameConverter& converter, byte* begin, byte* end, Address current_pc) { CHECK(!code.is_null()); v8::base::EmbeddedVector<char, 128> decode_buffer; std::ostringstream out; byte* pc = begin; disasm::Disassembler d(converter, disasm::Disassembler::kContinueOnUnimplementedOpcode); RelocIterator* it = nullptr; CodeCommentsIterator cit(code.code_comments(), code.code_comments_size()); // Relocation exists if we either have no isolate (wasm code), // or we have an isolate and it is not an off-heap instruction stream. if (!isolate || !OffHeapInstructionStream::PcIsOffHeap( isolate, base::bit_cast<Address>(begin))) { it = new RelocIterator(code); } else { // No relocation information when printing code stubs. } int constants = -1; // no constants being decoded at the start while (pc < end) { // First decode instruction so that we know its length. byte* prev_pc = pc; bool decoding_constant_pool = constants > 0; if (decoding_constant_pool) { SNPrintF( decode_buffer, "%08x constant", base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc))); constants--; pc += 4; } else { int num_const = d.ConstantPoolSizeAt(pc); if (num_const >= 0) { SNPrintF( decode_buffer, "%08x constant pool begin (num_const = %d)", base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc)), num_const); constants = num_const; pc += 4; } else if (it != nullptr && !it->done() && it->rinfo()->pc() == reinterpret_cast<Address>(pc) && (it->rinfo()->rmode() == RelocInfo::INTERNAL_REFERENCE || it->rinfo()->rmode() == RelocInfo::LITERAL_CONSTANT || it->rinfo()->rmode() == RelocInfo::DATA_EMBEDDED_OBJECT)) { // raw pointer embedded in code stream, e.g., jump table byte* ptr = base::ReadUnalignedValue<byte*>(reinterpret_cast<Address>(pc)); if (RelocInfo::IsInternalReference(it->rinfo()->rmode())) { SNPrintF(decode_buffer, "%08" V8PRIxPTR " jump table entry %4zu", reinterpret_cast<intptr_t>(ptr), static_cast<size_t>(ptr - begin)); } else { const char* kType = RelocInfo::IsLiteralConstant(it->rinfo()->rmode()) ? " literal constant" : "embedded data object"; SNPrintF(decode_buffer, "%08" V8PRIxPTR " %s 0x%08" V8PRIxPTR, reinterpret_cast<intptr_t>(ptr), kType, reinterpret_cast<intptr_t>(ptr)); } pc += sizeof(ptr); } else { decode_buffer[0] = '\0'; pc += d.InstructionDecode(decode_buffer, pc); } } // Collect RelocInfo for this instruction (prev_pc .. pc-1) std::vector<const char*> comments; std::vector<Address> pcs; std::vector<RelocInfo::Mode> rmodes; std::vector<intptr_t> datas; if (it != nullptr) { while (!it->done() && it->rinfo()->pc() < reinterpret_cast<Address>(pc)) { // Collect all data. pcs.push_back(it->rinfo()->pc()); rmodes.push_back(it->rinfo()->rmode()); datas.push_back(it->rinfo()->data()); it->next(); } } while (cit.HasCurrent() && cit.GetPCOffset() < static_cast<Address>(pc - begin)) { comments.push_back(cit.GetComment()); cit.Next(); } // Comments. for (size_t i = 0; i < comments.size(); i++) { out << " " << comments[i]; DumpBuffer(os, out); } // Instruction address and instruction offset. if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) { // If this is the given "current" pc, make it yellow and bold. out << "\033[33;1m"; } out << static_cast<void*>(prev_pc) << " " << std::setw(4) << std::hex << prev_pc - begin << " "; // Instruction. out << decode_buffer.begin(); // Print all the reloc info for this instruction which are not comments. for (size_t i = 0; i < pcs.size(); i++) { // Put together the reloc info const CodeReference& host = code; Address constant_pool = host.is_null() ? kNullAddress : host.constant_pool(); Code code_pointer; if (!host.is_null() && host.is_js()) { code_pointer = *host.as_js_code(); } RelocInfo relocinfo(pcs[i], rmodes[i], datas[i], code_pointer, constant_pool); bool first_reloc_info = (i == 0); PrintRelocInfo(out, isolate, ref_encoder, os, code, &relocinfo, first_reloc_info); } // If this is a constant pool load and we haven't found any RelocInfo // already, check if we can find some RelocInfo for the target address in // the constant pool. // Make sure we're also not currently in the middle of decoding a constant // pool itself, rather than a contant pool load. Since it can store any // bytes, a constant could accidentally match with the bit-pattern checked // by IsInConstantPool() below. if (pcs.empty() && !code.is_null() && !decoding_constant_pool) { RelocInfo dummy_rinfo(reinterpret_cast<Address>(prev_pc), RelocInfo::NO_INFO, 0, Code()); if (dummy_rinfo.IsInConstantPool()) { Address constant_pool_entry_address = dummy_rinfo.constant_pool_entry_address(); RelocIterator reloc_it(code); while (!reloc_it.done()) { if (reloc_it.rinfo()->IsInConstantPool() && (reloc_it.rinfo()->constant_pool_entry_address() == constant_pool_entry_address)) { PrintRelocInfo(out, isolate, ref_encoder, os, code, reloc_it.rinfo()); break; } reloc_it.next(); } } } if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) { out << "\033[m"; } DumpBuffer(os, out); } // Emit comments following the last instruction (if any). while (cit.HasCurrent() && cit.GetPCOffset() < static_cast<Address>(pc - begin)) { out << " " << cit.GetComment(); DumpBuffer(os, out); cit.Next(); } delete it; return static_cast<int>(pc - begin); } int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin, byte* end, CodeReference code, Address current_pc) { DCHECK_WITH_MSG(FLAG_text_is_readable, "Builtins disassembly requires a readable .text section"); V8NameConverter v8NameConverter(isolate, code); if (isolate) { // We have an isolate, so support external reference names from V8 and // embedder. SealHandleScope shs(isolate); DisallowGarbageCollection no_alloc; ExternalReferenceEncoder ref_encoder(isolate); return DecodeIt(isolate, &ref_encoder, os, code, v8NameConverter, begin, end, current_pc); } else { // No isolate => isolate-independent code. Only V8 External references // available. return DecodeIt(nullptr, nullptr, os, code, v8NameConverter, begin, end, current_pc); } } #else // ENABLE_DISASSEMBLER int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin, byte* end, CodeReference code, Address current_pc) { return 0; } #endif // ENABLE_DISASSEMBLER } // namespace internal } // namespace v8