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