• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
36 namespace v8 {
37 namespace internal {
38 
39 #ifdef ENABLE_DISASSEMBLER
40 
41 class V8NameConverter : public disasm::NameConverter {
42  public:
V8NameConverter(Isolate * isolate,CodeReference code={})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 
code() const49   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 
InitExternalRefsCache() const66 void 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 
NameOfAddress(byte * pc) const85 const 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 
NameInCode(byte * addr) const118 const 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 
RootRelativeName(int offset) const124 const 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.
DumpBuffer(std::ostream & os,std::ostringstream & out)201 static void DumpBuffer(std::ostream& os, std::ostringstream& out) {
202   os << out.str() << std::endl;
203   out.str("");
204 }
205 
206 static const int kRelocInfoPosition = 57;
207 
PrintRelocInfo(std::ostringstream & out,Isolate * isolate,const ExternalReferenceEncoder * ref_encoder,std::ostream & os,CodeReference host,RelocInfo * relocinfo,bool first_reloc_info=true)208 static 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 
DecodeIt(Isolate * isolate,ExternalReferenceEncoder * ref_encoder,std::ostream & os,CodeReference code,const V8NameConverter & converter,byte * begin,byte * end,Address current_pc)291 static 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 
Decode(Isolate * isolate,std::ostream & os,byte * begin,byte * end,CodeReference code,Address current_pc)462 int 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 
485 int 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