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