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