1// Copyright 2011 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/diagnostics/disassembler.h" 6 7#include <algorithm> 8#include <iomanip> 9#include <memory> 10#include <sstream> 11#include <unordered_map> 12#include <vector> 13 14#include "src/base/memory.h" 15#include "src/base/strings.h" 16#include "src/base/vector.h" 17#include "src/codegen/assembler-inl.h" 18#include "src/codegen/code-comments.h" 19#include "src/codegen/code-reference.h" 20#include "src/codegen/external-reference-encoder.h" 21#include "src/codegen/macro-assembler.h" 22#include "src/debug/debug.h" 23#include "src/deoptimizer/deoptimizer.h" 24#include "src/diagnostics/disasm.h" 25#include "src/execution/isolate-data.h" 26#include "src/ic/ic.h" 27#include "src/objects/objects-inl.h" 28#include "src/snapshot/embedded/embedded-data.h" 29#include "src/strings/string-stream.h" 30 31#if V8_ENABLE_WEBASSEMBLY 32#include "src/wasm/wasm-code-manager.h" 33#include "src/wasm/wasm-engine.h" 34#endif // V8_ENABLE_WEBASSEMBLY 35 36namespace v8 { 37namespace internal { 38 39#ifdef ENABLE_DISASSEMBLER 40 41class V8NameConverter : public disasm::NameConverter { 42 public: 43 explicit V8NameConverter(Isolate* isolate, CodeReference code = {}) 44 : isolate_(isolate), code_(code) {} 45 const char* NameOfAddress(byte* pc) const override; 46 const char* NameInCode(byte* addr) const override; 47 const char* RootRelativeName(int offset) const override; 48 49 const CodeReference& code() const { return code_; } 50 51 private: 52 void InitExternalRefsCache() const; 53 54 Isolate* isolate_; 55 CodeReference code_; 56 57 base::EmbeddedVector<char, 128> v8_buffer_; 58 59 // Map from root-register relative offset of the external reference value to 60 // the external reference name (stored in the external reference table). 61 // This cache is used to recognize [root_reg + offs] patterns as direct 62 // access to certain external reference's value. 63 mutable std::unordered_map<int, const char*> directly_accessed_external_refs_; 64}; 65 66void V8NameConverter::InitExternalRefsCache() const { 67 ExternalReferenceTable* external_reference_table = 68 isolate_->external_reference_table(); 69 if (!external_reference_table->is_initialized()) return; 70 71 base::AddressRegion addressable_region = 72 isolate_->root_register_addressable_region(); 73 Address isolate_root = isolate_->isolate_root(); 74 75 for (uint32_t i = 0; i < ExternalReferenceTable::kSize; i++) { 76 Address address = external_reference_table->address(i); 77 if (addressable_region.contains(address)) { 78 int offset = static_cast<int>(address - isolate_root); 79 const char* name = external_reference_table->name(i); 80 directly_accessed_external_refs_.insert({offset, name}); 81 } 82 } 83} 84 85const char* V8NameConverter::NameOfAddress(byte* pc) const { 86 if (!code_.is_null()) { 87 const char* name = 88 isolate_ ? isolate_->builtins()->Lookup(reinterpret_cast<Address>(pc)) 89 : nullptr; 90 91 if (name != nullptr) { 92 SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc), name); 93 return v8_buffer_.begin(); 94 } 95 96 int offs = static_cast<int>(reinterpret_cast<Address>(pc) - 97 code_.instruction_start()); 98 // print as code offset, if it seems reasonable 99 if (0 <= offs && offs < code_.instruction_size()) { 100 SNPrintF(v8_buffer_, "%p <+0x%x>", static_cast<void*>(pc), offs); 101 return v8_buffer_.begin(); 102 } 103 104#if V8_ENABLE_WEBASSEMBLY 105 wasm::WasmCodeRefScope wasm_code_ref_scope; 106 if (auto* wasm_code = wasm::GetWasmCodeManager()->LookupCode( 107 reinterpret_cast<Address>(pc))) { 108 SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc), 109 wasm::GetWasmCodeKindAsString(wasm_code->kind())); 110 return v8_buffer_.begin(); 111 } 112#endif // V8_ENABLE_WEBASSEMBLY 113 } 114 115 return disasm::NameConverter::NameOfAddress(pc); 116} 117 118const char* V8NameConverter::NameInCode(byte* addr) const { 119 // The V8NameConverter is used for well known code, so we can "safely" 120 // dereference pointers in generated code. 121 return code_.is_null() ? "" : reinterpret_cast<const char*>(addr); 122} 123 124const char* V8NameConverter::RootRelativeName(int offset) const { 125 if (isolate_ == nullptr) return nullptr; 126 127 const int kRootsTableStart = IsolateData::roots_table_offset(); 128 const unsigned kRootsTableSize = sizeof(RootsTable); 129 const int kExtRefsTableStart = IsolateData::external_reference_table_offset(); 130 const unsigned kExtRefsTableSize = ExternalReferenceTable::kSizeInBytes; 131 const int kBuiltinTier0TableStart = IsolateData::builtin_tier0_table_offset(); 132 const unsigned kBuiltinTier0TableSize = 133 Builtins::kBuiltinTier0Count * kSystemPointerSize; 134 const int kBuiltinTableStart = IsolateData::builtin_table_offset(); 135 const unsigned kBuiltinTableSize = 136 Builtins::kBuiltinCount * kSystemPointerSize; 137 138 if (static_cast<unsigned>(offset - kRootsTableStart) < kRootsTableSize) { 139 uint32_t offset_in_roots_table = offset - kRootsTableStart; 140 141 // Fail safe in the unlikely case of an arbitrary root-relative offset. 142 if (offset_in_roots_table % kSystemPointerSize != 0) return nullptr; 143 144 RootIndex root_index = 145 static_cast<RootIndex>(offset_in_roots_table / kSystemPointerSize); 146 147 SNPrintF(v8_buffer_, "root (%s)", RootsTable::name(root_index)); 148 return v8_buffer_.begin(); 149 } else if (static_cast<unsigned>(offset - kExtRefsTableStart) < 150 kExtRefsTableSize) { 151 uint32_t offset_in_extref_table = offset - kExtRefsTableStart; 152 153 // Fail safe in the unlikely case of an arbitrary root-relative offset. 154 if (offset_in_extref_table % ExternalReferenceTable::kEntrySize != 0) { 155 return nullptr; 156 } 157 158 // Likewise if the external reference table is uninitialized. 159 if (!isolate_->external_reference_table()->is_initialized()) { 160 return nullptr; 161 } 162 163 SNPrintF(v8_buffer_, "external reference (%s)", 164 isolate_->external_reference_table()->NameFromOffset( 165 offset_in_extref_table)); 166 return v8_buffer_.begin(); 167 } else if (static_cast<unsigned>(offset - kBuiltinTier0TableStart) < 168 kBuiltinTier0TableSize) { 169 uint32_t offset_in_builtins_table = (offset - kBuiltinTier0TableStart); 170 171 Builtin builtin = 172 Builtins::FromInt(offset_in_builtins_table / kSystemPointerSize); 173 const char* name = Builtins::name(builtin); 174 SNPrintF(v8_buffer_, "builtin (%s)", name); 175 return v8_buffer_.begin(); 176 } else if (static_cast<unsigned>(offset - kBuiltinTableStart) < 177 kBuiltinTableSize) { 178 uint32_t offset_in_builtins_table = (offset - kBuiltinTableStart); 179 180 Builtin builtin = 181 Builtins::FromInt(offset_in_builtins_table / kSystemPointerSize); 182 const char* name = Builtins::name(builtin); 183 SNPrintF(v8_buffer_, "builtin (%s)", name); 184 return v8_buffer_.begin(); 185 } else { 186 // It must be a direct access to one of the external values. 187 if (directly_accessed_external_refs_.empty()) { 188 InitExternalRefsCache(); 189 } 190 191 auto iter = directly_accessed_external_refs_.find(offset); 192 if (iter != directly_accessed_external_refs_.end()) { 193 SNPrintF(v8_buffer_, "external value (%s)", iter->second); 194 return v8_buffer_.begin(); 195 } 196 return nullptr; 197 } 198} 199 200// Output the contents of the string stream and empty it. 201static void DumpBuffer(std::ostream& os, std::ostringstream& out) { 202 os << out.str() << std::endl; 203 out.str(""); 204} 205 206static const int kRelocInfoPosition = 57; 207 208static void PrintRelocInfo(std::ostringstream& out, Isolate* isolate, 209 const ExternalReferenceEncoder* ref_encoder, 210 std::ostream& os, CodeReference host, 211 RelocInfo* relocinfo, bool first_reloc_info = true) { 212 // Indent the printing of the reloc info. 213 int padding = kRelocInfoPosition; 214 if (first_reloc_info) { 215 // The first reloc info is printed after the disassembled instruction. 216 padding -= std::min(padding, static_cast<int>(out.tellp())); 217 } else { 218 // Additional reloc infos are printed on separate lines. 219 DumpBuffer(os, out); 220 } 221 std::fill_n(std::ostream_iterator<char>(out), padding, ' '); 222 223 RelocInfo::Mode rmode = relocinfo->rmode(); 224 if (rmode == RelocInfo::DEOPT_SCRIPT_OFFSET) { 225 out << " ;; debug: deopt position, script offset '" 226 << static_cast<int>(relocinfo->data()) << "'"; 227 } else if (rmode == RelocInfo::DEOPT_INLINING_ID) { 228 out << " ;; debug: deopt position, inlining id '" 229 << static_cast<int>(relocinfo->data()) << "'"; 230 } else if (rmode == RelocInfo::DEOPT_REASON) { 231 DeoptimizeReason reason = static_cast<DeoptimizeReason>(relocinfo->data()); 232 out << " ;; debug: deopt reason '" << DeoptimizeReasonToString(reason) 233 << "'"; 234 } else if (rmode == RelocInfo::DEOPT_ID) { 235 out << " ;; debug: deopt index " << static_cast<int>(relocinfo->data()); 236 } else if (rmode == RelocInfo::DEOPT_NODE_ID) { 237#ifdef DEBUG 238 out << " ;; debug: deopt node id " 239 << static_cast<uint32_t>(relocinfo->data()); 240#else // DEBUG 241 UNREACHABLE(); 242#endif // DEBUG 243 } else if (RelocInfo::IsEmbeddedObjectMode(rmode)) { 244 HeapStringAllocator allocator; 245 StringStream accumulator(&allocator); 246 relocinfo->target_object(isolate).ShortPrint(&accumulator); 247 std::unique_ptr<char[]> obj_name = accumulator.ToCString(); 248 const bool is_compressed = RelocInfo::IsCompressedEmbeddedObject(rmode); 249 out << " ;; " << (is_compressed ? "(compressed) " : "") 250 << "object: " << obj_name.get(); 251 } else if (rmode == RelocInfo::EXTERNAL_REFERENCE) { 252 Address address = relocinfo->target_external_reference(); 253 const char* reference_name = 254 ref_encoder 255 ? ref_encoder->NameOfAddress(isolate, address) 256 : ExternalReferenceTable::NameOfIsolateIndependentAddress(address); 257 out << " ;; external reference (" << reference_name << ")"; 258 } else if (RelocInfo::IsCodeTargetMode(rmode)) { 259 out << " ;; code:"; 260 Code code = isolate->heap()->GcSafeFindCodeForInnerPointer( 261 relocinfo->target_address()); 262 CodeKind kind = code.kind(); 263 if (code.is_builtin()) { 264 out << " Builtin::" << Builtins::name(code.builtin_id()); 265 } else { 266 out << " " << CodeKindToString(kind); 267 } 268#if V8_ENABLE_WEBASSEMBLY 269 } else if (RelocInfo::IsWasmStubCall(rmode) && host.is_wasm_code()) { 270 // Host is isolate-independent, try wasm native module instead. 271 const char* runtime_stub_name = GetRuntimeStubName( 272 host.as_wasm_code()->native_module()->GetRuntimeStubId( 273 relocinfo->wasm_stub_call_address())); 274 out << " ;; wasm stub: " << runtime_stub_name; 275#endif // V8_ENABLE_WEBASSEMBLY 276 } else if (RelocInfo::IsRuntimeEntry(rmode) && isolate != nullptr) { 277 // A runtime entry relocinfo might be a deoptimization bailout. 278 Address addr = relocinfo->target_address(); 279 DeoptimizeKind type; 280 if (Deoptimizer::IsDeoptimizationEntry(isolate, addr, &type)) { 281 out << " ;; " << Deoptimizer::MessageFor(type) 282 << " deoptimization bailout"; 283 } else { 284 out << " ;; " << RelocInfo::RelocModeName(rmode); 285 } 286 } else { 287 out << " ;; " << RelocInfo::RelocModeName(rmode); 288 } 289} 290 291static int DecodeIt(Isolate* isolate, ExternalReferenceEncoder* ref_encoder, 292 std::ostream& os, CodeReference code, 293 const V8NameConverter& converter, byte* begin, byte* end, 294 Address current_pc) { 295 CHECK(!code.is_null()); 296 v8::base::EmbeddedVector<char, 128> decode_buffer; 297 std::ostringstream out; 298 byte* pc = begin; 299 disasm::Disassembler d(converter, 300 disasm::Disassembler::kContinueOnUnimplementedOpcode); 301 RelocIterator* it = nullptr; 302 CodeCommentsIterator cit(code.code_comments(), code.code_comments_size()); 303 // Relocation exists if we either have no isolate (wasm code), 304 // or we have an isolate and it is not an off-heap instruction stream. 305 if (!isolate || !OffHeapInstructionStream::PcIsOffHeap( 306 isolate, bit_cast<Address>(begin))) { 307 it = new RelocIterator(code); 308 } else { 309 // No relocation information when printing code stubs. 310 } 311 int constants = -1; // no constants being decoded at the start 312 313 while (pc < end) { 314 // First decode instruction so that we know its length. 315 byte* prev_pc = pc; 316 bool decoding_constant_pool = constants > 0; 317 if (decoding_constant_pool) { 318 SNPrintF( 319 decode_buffer, "%08x constant", 320 base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc))); 321 constants--; 322 pc += 4; 323 } else { 324 int num_const = d.ConstantPoolSizeAt(pc); 325 if (num_const >= 0) { 326 SNPrintF( 327 decode_buffer, "%08x constant pool begin (num_const = %d)", 328 base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc)), 329 num_const); 330 constants = num_const; 331 pc += 4; 332 } else if (it != nullptr && !it->done() && 333 it->rinfo()->pc() == reinterpret_cast<Address>(pc) && 334 (it->rinfo()->rmode() == RelocInfo::INTERNAL_REFERENCE || 335 it->rinfo()->rmode() == RelocInfo::LITERAL_CONSTANT || 336 it->rinfo()->rmode() == RelocInfo::DATA_EMBEDDED_OBJECT)) { 337 // raw pointer embedded in code stream, e.g., jump table 338 byte* ptr = 339 base::ReadUnalignedValue<byte*>(reinterpret_cast<Address>(pc)); 340 if (RelocInfo::IsInternalReference(it->rinfo()->rmode())) { 341 SNPrintF(decode_buffer, 342 "%08" V8PRIxPTR " jump table entry %4zu", 343 reinterpret_cast<intptr_t>(ptr), 344 static_cast<size_t>(ptr - begin)); 345 } else { 346 const char* kType = RelocInfo::IsLiteralConstant(it->rinfo()->rmode()) 347 ? " literal constant" 348 : "embedded data object"; 349 SNPrintF(decode_buffer, "%08" V8PRIxPTR " %s 0x%08" V8PRIxPTR, 350 reinterpret_cast<intptr_t>(ptr), kType, 351 reinterpret_cast<intptr_t>(ptr)); 352 } 353 pc += sizeof(ptr); 354 } else { 355 decode_buffer[0] = '\0'; 356 pc += d.InstructionDecode(decode_buffer, pc); 357 } 358 } 359 360 // Collect RelocInfo for this instruction (prev_pc .. pc-1) 361 std::vector<const char*> comments; 362 std::vector<Address> pcs; 363 std::vector<RelocInfo::Mode> rmodes; 364 std::vector<intptr_t> datas; 365 if (it != nullptr) { 366 while (!it->done() && it->rinfo()->pc() < reinterpret_cast<Address>(pc)) { 367 // Collect all data. 368 pcs.push_back(it->rinfo()->pc()); 369 rmodes.push_back(it->rinfo()->rmode()); 370 datas.push_back(it->rinfo()->data()); 371 it->next(); 372 } 373 } 374 while (cit.HasCurrent() && 375 cit.GetPCOffset() < static_cast<Address>(pc - begin)) { 376 comments.push_back(cit.GetComment()); 377 cit.Next(); 378 } 379 380 // Comments. 381 for (size_t i = 0; i < comments.size(); i++) { 382 out << " " << comments[i]; 383 DumpBuffer(os, out); 384 } 385 386 // Instruction address and instruction offset. 387 if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) { 388 // If this is the given "current" pc, make it yellow and bold. 389 out << "\033[33;1m"; 390 } 391 out << static_cast<void*>(prev_pc) << " " << std::setw(4) << std::hex 392 << prev_pc - begin << " "; 393 394 // Instruction. 395 out << decode_buffer.begin(); 396 397 // Print all the reloc info for this instruction which are not comments. 398 for (size_t i = 0; i < pcs.size(); i++) { 399 // Put together the reloc info 400 const CodeReference& host = code; 401 Address constant_pool = 402 host.is_null() ? kNullAddress : host.constant_pool(); 403 Code code_pointer; 404 if (!host.is_null() && host.is_js()) { 405 code_pointer = *host.as_js_code(); 406 } 407 408 RelocInfo relocinfo(pcs[i], rmodes[i], datas[i], code_pointer, 409 constant_pool); 410 411 bool first_reloc_info = (i == 0); 412 PrintRelocInfo(out, isolate, ref_encoder, os, code, &relocinfo, 413 first_reloc_info); 414 } 415 416 // If this is a constant pool load and we haven't found any RelocInfo 417 // already, check if we can find some RelocInfo for the target address in 418 // the constant pool. 419 // Make sure we're also not currently in the middle of decoding a constant 420 // pool itself, rather than a contant pool load. Since it can store any 421 // bytes, a constant could accidentally match with the bit-pattern checked 422 // by IsInConstantPool() below. 423 if (pcs.empty() && !code.is_null() && !decoding_constant_pool) { 424 RelocInfo dummy_rinfo(reinterpret_cast<Address>(prev_pc), 425 RelocInfo::NO_INFO, 0, Code()); 426 if (dummy_rinfo.IsInConstantPool()) { 427 Address constant_pool_entry_address = 428 dummy_rinfo.constant_pool_entry_address(); 429 RelocIterator reloc_it(code); 430 while (!reloc_it.done()) { 431 if (reloc_it.rinfo()->IsInConstantPool() && 432 (reloc_it.rinfo()->constant_pool_entry_address() == 433 constant_pool_entry_address)) { 434 PrintRelocInfo(out, isolate, ref_encoder, os, code, 435 reloc_it.rinfo()); 436 break; 437 } 438 reloc_it.next(); 439 } 440 } 441 } 442 443 if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) { 444 out << "\033[m"; 445 } 446 447 DumpBuffer(os, out); 448 } 449 450 // Emit comments following the last instruction (if any). 451 while (cit.HasCurrent() && 452 cit.GetPCOffset() < static_cast<Address>(pc - begin)) { 453 out << " " << cit.GetComment(); 454 DumpBuffer(os, out); 455 cit.Next(); 456 } 457 458 delete it; 459 return static_cast<int>(pc - begin); 460} 461 462int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin, 463 byte* end, CodeReference code, Address current_pc) { 464 DCHECK_WITH_MSG(FLAG_text_is_readable, 465 "Builtins disassembly requires a readable .text section"); 466 V8NameConverter v8NameConverter(isolate, code); 467 if (isolate) { 468 // We have an isolate, so support external reference names from V8 and 469 // embedder. 470 SealHandleScope shs(isolate); 471 DisallowGarbageCollection no_alloc; 472 ExternalReferenceEncoder ref_encoder(isolate); 473 return DecodeIt(isolate, &ref_encoder, os, code, v8NameConverter, begin, 474 end, current_pc); 475 } else { 476 // No isolate => isolate-independent code. Only V8 External references 477 // available. 478 return DecodeIt(nullptr, nullptr, os, code, v8NameConverter, begin, end, 479 current_pc); 480 } 481} 482 483#else // ENABLE_DISASSEMBLER 484 485int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin, 486 byte* end, CodeReference code, Address current_pc) { 487 return 0; 488} 489 490#endif // ENABLE_DISASSEMBLER 491 492} // namespace internal 493} // namespace v8 494