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     if (relocinfo->host().is_null()) {
247       relocinfo->target_object_no_host(isolate).ShortPrint(&accumulator);
248     } else {
249       relocinfo->target_object().ShortPrint(&accumulator);
250     }
251     std::unique_ptr<char[]> obj_name = accumulator.ToCString();
252     const bool is_compressed = RelocInfo::IsCompressedEmbeddedObject(rmode);
253     out << "    ;; " << (is_compressed ? "(compressed) " : "")
254         << "object: " << obj_name.get();
255   } else if (rmode == RelocInfo::EXTERNAL_REFERENCE) {
256     Address address = relocinfo->target_external_reference();
257     const char* reference_name =
258         ref_encoder
259             ? ref_encoder->NameOfAddress(isolate, address)
260             : ExternalReferenceTable::NameOfIsolateIndependentAddress(address);
261     out << "    ;; external reference (" << reference_name << ")";
262   } else if (RelocInfo::IsCodeTargetMode(rmode)) {
263     out << "    ;; code:";
264     Code code = isolate->heap()->GcSafeFindCodeForInnerPointer(
265         relocinfo->target_address());
266     CodeKind kind = code.kind();
267     if (code.is_builtin()) {
268       out << " Builtin::" << Builtins::name(code.builtin_id());
269     } else {
270       out << " " << CodeKindToString(kind);
271     }
272 #if V8_ENABLE_WEBASSEMBLY
273   } else if (RelocInfo::IsWasmStubCall(rmode) && host.is_wasm_code()) {
274     // Host is isolate-independent, try wasm native module instead.
275     const char* runtime_stub_name = GetRuntimeStubName(
276         host.as_wasm_code()->native_module()->GetRuntimeStubId(
277             relocinfo->wasm_stub_call_address()));
278     out << "    ;; wasm stub: " << runtime_stub_name;
279 #endif  // V8_ENABLE_WEBASSEMBLY
280   } else if (RelocInfo::IsRuntimeEntry(rmode) && isolate != nullptr) {
281     // A runtime entry relocinfo might be a deoptimization bailout.
282     Address addr = relocinfo->target_address();
283     DeoptimizeKind type;
284     if (Deoptimizer::IsDeoptimizationEntry(isolate, addr, &type)) {
285       out << "    ;; " << Deoptimizer::MessageFor(type, false)
286           << " deoptimization bailout";
287     } else {
288       out << "    ;; " << RelocInfo::RelocModeName(rmode);
289     }
290   } else {
291     out << "    ;; " << RelocInfo::RelocModeName(rmode);
292   }
293 }
294 
DecodeIt(Isolate * isolate,ExternalReferenceEncoder * ref_encoder,std::ostream & os,CodeReference code,const V8NameConverter & converter,byte * begin,byte * end,Address current_pc)295 static int DecodeIt(Isolate* isolate, ExternalReferenceEncoder* ref_encoder,
296                     std::ostream& os, CodeReference code,
297                     const V8NameConverter& converter, byte* begin, byte* end,
298                     Address current_pc) {
299   CHECK(!code.is_null());
300   v8::base::EmbeddedVector<char, 128> decode_buffer;
301   std::ostringstream out;
302   byte* pc = begin;
303   disasm::Disassembler d(converter,
304                          disasm::Disassembler::kContinueOnUnimplementedOpcode);
305   RelocIterator* it = nullptr;
306   CodeCommentsIterator cit(code.code_comments(), code.code_comments_size());
307   // Relocation exists if we either have no isolate (wasm code),
308   // or we have an isolate and it is not an off-heap instruction stream.
309   if (!isolate ||
310       !InstructionStream::PcIsOffHeap(isolate, bit_cast<Address>(begin))) {
311     it = new RelocIterator(code);
312   } else {
313     // No relocation information when printing code stubs.
314   }
315   int constants = -1;  // no constants being decoded at the start
316 
317   while (pc < end) {
318     // First decode instruction so that we know its length.
319     byte* prev_pc = pc;
320     bool decoding_constant_pool = constants > 0;
321     if (decoding_constant_pool) {
322       SNPrintF(
323           decode_buffer, "%08x       constant",
324           base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc)));
325       constants--;
326       pc += 4;
327     } else {
328       int num_const = d.ConstantPoolSizeAt(pc);
329       if (num_const >= 0) {
330         SNPrintF(
331             decode_buffer, "%08x       constant pool begin (num_const = %d)",
332             base::ReadUnalignedValue<int32_t>(reinterpret_cast<Address>(pc)),
333             num_const);
334         constants = num_const;
335         pc += 4;
336       } else if (it != nullptr && !it->done() &&
337                  it->rinfo()->pc() == reinterpret_cast<Address>(pc) &&
338                  (it->rinfo()->rmode() == RelocInfo::INTERNAL_REFERENCE ||
339                   it->rinfo()->rmode() == RelocInfo::LITERAL_CONSTANT ||
340                   it->rinfo()->rmode() == RelocInfo::DATA_EMBEDDED_OBJECT)) {
341         // raw pointer embedded in code stream, e.g., jump table
342         byte* ptr =
343             base::ReadUnalignedValue<byte*>(reinterpret_cast<Address>(pc));
344         if (RelocInfo::IsInternalReference(it->rinfo()->rmode())) {
345           SNPrintF(decode_buffer,
346                    "%08" V8PRIxPTR "       jump table entry %4zu",
347                    reinterpret_cast<intptr_t>(ptr),
348                    static_cast<size_t>(ptr - begin));
349         } else {
350           const char* kType = RelocInfo::IsLiteralConstant(it->rinfo()->rmode())
351                                   ? "    literal constant"
352                                   : "embedded data object";
353           SNPrintF(decode_buffer, "%08" V8PRIxPTR "       %s 0x%08" V8PRIxPTR,
354                    reinterpret_cast<intptr_t>(ptr), kType,
355                    reinterpret_cast<intptr_t>(ptr));
356         }
357         pc += sizeof(ptr);
358       } else {
359         decode_buffer[0] = '\0';
360         pc += d.InstructionDecode(decode_buffer, pc);
361       }
362     }
363 
364     // Collect RelocInfo for this instruction (prev_pc .. pc-1)
365     std::vector<const char*> comments;
366     std::vector<Address> pcs;
367     std::vector<RelocInfo::Mode> rmodes;
368     std::vector<intptr_t> datas;
369     if (it != nullptr) {
370       while (!it->done() && it->rinfo()->pc() < reinterpret_cast<Address>(pc)) {
371         // Collect all data.
372         pcs.push_back(it->rinfo()->pc());
373         rmodes.push_back(it->rinfo()->rmode());
374         datas.push_back(it->rinfo()->data());
375         it->next();
376       }
377     }
378     while (cit.HasCurrent() &&
379            cit.GetPCOffset() < static_cast<Address>(pc - begin)) {
380       comments.push_back(cit.GetComment());
381       cit.Next();
382     }
383 
384     // Comments.
385     for (size_t i = 0; i < comments.size(); i++) {
386       out << "                  " << comments[i];
387       DumpBuffer(os, out);
388     }
389 
390     // Instruction address and instruction offset.
391     if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) {
392       // If this is the given "current" pc, make it yellow and bold.
393       out << "\033[33;1m";
394     }
395     out << static_cast<void*>(prev_pc) << "  " << std::setw(4) << std::hex
396         << prev_pc - begin << "  ";
397 
398     // Instruction.
399     out << decode_buffer.begin();
400 
401     // Print all the reloc info for this instruction which are not comments.
402     for (size_t i = 0; i < pcs.size(); i++) {
403       // Put together the reloc info
404       const CodeReference& host = code;
405       Address constant_pool =
406           host.is_null() ? kNullAddress : host.constant_pool();
407       Code code_pointer;
408       if (!host.is_null() && host.is_js()) {
409         code_pointer = *host.as_js_code();
410       }
411 
412       RelocInfo relocinfo(pcs[i], rmodes[i], datas[i], code_pointer,
413                           constant_pool);
414 
415       bool first_reloc_info = (i == 0);
416       PrintRelocInfo(out, isolate, ref_encoder, os, code, &relocinfo,
417                      first_reloc_info);
418     }
419 
420     // If this is a constant pool load and we haven't found any RelocInfo
421     // already, check if we can find some RelocInfo for the target address in
422     // the constant pool.
423     // Make sure we're also not currently in the middle of decoding a constant
424     // pool itself, rather than a contant pool load. Since it can store any
425     // bytes, a constant could accidentally match with the bit-pattern checked
426     // by IsInConstantPool() below.
427     if (pcs.empty() && !code.is_null() && !decoding_constant_pool) {
428       RelocInfo dummy_rinfo(reinterpret_cast<Address>(prev_pc), RelocInfo::NONE,
429                             0, Code());
430       if (dummy_rinfo.IsInConstantPool()) {
431         Address constant_pool_entry_address =
432             dummy_rinfo.constant_pool_entry_address();
433         RelocIterator reloc_it(code);
434         while (!reloc_it.done()) {
435           if (reloc_it.rinfo()->IsInConstantPool() &&
436               (reloc_it.rinfo()->constant_pool_entry_address() ==
437                constant_pool_entry_address)) {
438             PrintRelocInfo(out, isolate, ref_encoder, os, code,
439                            reloc_it.rinfo());
440             break;
441           }
442           reloc_it.next();
443         }
444       }
445     }
446 
447     if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) {
448       out << "\033[m";
449     }
450 
451     DumpBuffer(os, out);
452   }
453 
454   // Emit comments following the last instruction (if any).
455   while (cit.HasCurrent() &&
456          cit.GetPCOffset() < static_cast<Address>(pc - begin)) {
457     out << "                  " << cit.GetComment();
458     DumpBuffer(os, out);
459     cit.Next();
460   }
461 
462   delete it;
463   return static_cast<int>(pc - begin);
464 }
465 
Decode(Isolate * isolate,std::ostream & os,byte * begin,byte * end,CodeReference code,Address current_pc)466 int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin,
467                          byte* end, CodeReference code, Address current_pc) {
468   DCHECK_WITH_MSG(FLAG_text_is_readable,
469                   "Builtins disassembly requires a readable .text section");
470   V8NameConverter v8NameConverter(isolate, code);
471   if (isolate) {
472     // We have an isolate, so support external reference names from V8 and
473     // embedder.
474     SealHandleScope shs(isolate);
475     DisallowGarbageCollection no_alloc;
476     ExternalReferenceEncoder ref_encoder(isolate);
477     return DecodeIt(isolate, &ref_encoder, os, code, v8NameConverter, begin,
478                     end, current_pc);
479   } else {
480     // No isolate => isolate-independent code. Only V8 External references
481     // available.
482     return DecodeIt(nullptr, nullptr, os, code, v8NameConverter, begin, end,
483                     current_pc);
484   }
485 }
486 
487 #else  // ENABLE_DISASSEMBLER
488 
489 int Disassembler::Decode(Isolate* isolate, std::ostream& os, byte* begin,
490                          byte* end, CodeReference code, Address current_pc) {
491   return 0;
492 }
493 
494 #endif  // ENABLE_DISASSEMBLER
495 
496 }  // namespace internal
497 }  // namespace v8
498