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