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