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