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 <vector>
9
10 #include "src/assembler-inl.h"
11 #include "src/code-reference.h"
12 #include "src/code-stubs.h"
13 #include "src/debug/debug.h"
14 #include "src/deoptimizer.h"
15 #include "src/disasm.h"
16 #include "src/ic/ic.h"
17 #include "src/instruction-stream.h"
18 #include "src/macro-assembler.h"
19 #include "src/objects-inl.h"
20 #include "src/snapshot/serializer-common.h"
21 #include "src/string-stream.h"
22 #include "src/wasm/wasm-code-manager.h"
23 #include "src/wasm/wasm-engine.h"
24
25 namespace v8 {
26 namespace internal {
27
28 #ifdef ENABLE_DISASSEMBLER
29
30 class V8NameConverter: public disasm::NameConverter {
31 public:
V8NameConverter(Isolate * isolate,CodeReference code={})32 explicit V8NameConverter(Isolate* isolate, CodeReference code = {})
33 : isolate_(isolate), code_(code) {}
34 const char* NameOfAddress(byte* pc) const override;
35 const char* NameInCode(byte* addr) const override;
36 const char* RootRelativeName(int offset) const override;
37
code() const38 const CodeReference& code() const { return code_; }
39
40 private:
41 Isolate* isolate_;
42 CodeReference code_;
43
44 EmbeddedVector<char, 128> v8_buffer_;
45 };
46
47
NameOfAddress(byte * pc) const48 const char* V8NameConverter::NameOfAddress(byte* pc) const {
49 if (!code_.is_null()) {
50 const char* name =
51 isolate_ ? isolate_->builtins()->Lookup(reinterpret_cast<Address>(pc))
52 : nullptr;
53
54 if (name != nullptr) {
55 SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc), name);
56 return v8_buffer_.start();
57 }
58
59 int offs = static_cast<int>(reinterpret_cast<Address>(pc) -
60 code_.instruction_start());
61 // print as code offset, if it seems reasonable
62 if (0 <= offs && offs < code_.instruction_size()) {
63 SNPrintF(v8_buffer_, "%p <+0x%x>", static_cast<void*>(pc), offs);
64 return v8_buffer_.start();
65 }
66
67 wasm::WasmCode* wasm_code =
68 isolate_ ? isolate_->wasm_engine()->code_manager()->LookupCode(
69 reinterpret_cast<Address>(pc))
70 : nullptr;
71 if (wasm_code != nullptr) {
72 SNPrintF(v8_buffer_, "%p (%s)", static_cast<void*>(pc),
73 wasm::GetWasmCodeKindAsString(wasm_code->kind()));
74 return v8_buffer_.start();
75 }
76 }
77
78 return disasm::NameConverter::NameOfAddress(pc);
79 }
80
81
NameInCode(byte * addr) const82 const char* V8NameConverter::NameInCode(byte* addr) const {
83 // The V8NameConverter is used for well known code, so we can "safely"
84 // dereference pointers in generated code.
85 return code_.is_null() ? "" : reinterpret_cast<const char*>(addr);
86 }
87
RootRelativeName(int offset) const88 const char* V8NameConverter::RootRelativeName(int offset) const {
89 if (isolate_ == nullptr) return nullptr;
90
91 const int kRootsStart = 0;
92 const int kRootsEnd = Heap::roots_to_external_reference_table_offset();
93 const int kExtRefsStart = Heap::roots_to_external_reference_table_offset();
94 const int kExtRefsEnd = Heap::roots_to_builtins_offset();
95
96 if (kRootsStart <= offset && offset < kRootsEnd) {
97 uint32_t offset_in_roots_table = offset - kRootsStart;
98
99 // Fail safe in the unlikely case of an arbitrary root-relative offset.
100 if (offset_in_roots_table % kPointerSize != 0) return nullptr;
101
102 Heap::RootListIndex root_index =
103 static_cast<Heap::RootListIndex>(offset_in_roots_table / kPointerSize);
104
105 HeapStringAllocator allocator;
106 StringStream accumulator(&allocator);
107 isolate_->heap()->root(root_index)->ShortPrint(&accumulator);
108 std::unique_ptr<char[]> obj_name = accumulator.ToCString();
109
110 SNPrintF(v8_buffer_, "root (%s)", obj_name.get());
111 return v8_buffer_.start();
112 } else if (kExtRefsStart <= offset && offset < kExtRefsEnd) {
113 uint32_t offset_in_extref_table = offset - kExtRefsStart;
114
115 // Fail safe in the unlikely case of an arbitrary root-relative offset.
116 if (offset_in_extref_table % ExternalReferenceTable::EntrySize() != 0) {
117 return nullptr;
118 }
119
120 // Likewise if the external reference table is uninitialized.
121 if (!isolate_->heap()->external_reference_table()->is_initialized()) {
122 return nullptr;
123 }
124
125 SNPrintF(v8_buffer_, "external reference (%s)",
126 isolate_->heap()->external_reference_table()->NameFromOffset(
127 offset_in_extref_table));
128 return v8_buffer_.start();
129 } else {
130 return nullptr;
131 }
132 }
133
DumpBuffer(std::ostream * os,StringBuilder * out)134 static void DumpBuffer(std::ostream* os, StringBuilder* out) {
135 (*os) << out->Finalize() << std::endl;
136 out->Reset();
137 }
138
139
140 static const int kOutBufferSize = 2048 + String::kMaxShortPrintLength;
141 static const int kRelocInfoPosition = 57;
142
PrintRelocInfo(StringBuilder * out,Isolate * isolate,const ExternalReferenceEncoder * ref_encoder,std::ostream * os,RelocInfo * relocinfo,bool first_reloc_info=true)143 static void PrintRelocInfo(StringBuilder* out, Isolate* isolate,
144 const ExternalReferenceEncoder* ref_encoder,
145 std::ostream* os, RelocInfo* relocinfo,
146 bool first_reloc_info = true) {
147 // Indent the printing of the reloc info.
148 if (first_reloc_info) {
149 // The first reloc info is printed after the disassembled instruction.
150 out->AddPadding(' ', kRelocInfoPosition - out->position());
151 } else {
152 // Additional reloc infos are printed on separate lines.
153 DumpBuffer(os, out);
154 out->AddPadding(' ', kRelocInfoPosition);
155 }
156
157 RelocInfo::Mode rmode = relocinfo->rmode();
158 if (rmode == RelocInfo::DEOPT_SCRIPT_OFFSET) {
159 out->AddFormatted(" ;; debug: deopt position, script offset '%d'",
160 static_cast<int>(relocinfo->data()));
161 } else if (rmode == RelocInfo::DEOPT_INLINING_ID) {
162 out->AddFormatted(" ;; debug: deopt position, inlining id '%d'",
163 static_cast<int>(relocinfo->data()));
164 } else if (rmode == RelocInfo::DEOPT_REASON) {
165 DeoptimizeReason reason = static_cast<DeoptimizeReason>(relocinfo->data());
166 out->AddFormatted(" ;; debug: deopt reason '%s'",
167 DeoptimizeReasonToString(reason));
168 } else if (rmode == RelocInfo::DEOPT_ID) {
169 out->AddFormatted(" ;; debug: deopt index %d",
170 static_cast<int>(relocinfo->data()));
171 } else if (rmode == RelocInfo::EMBEDDED_OBJECT) {
172 HeapStringAllocator allocator;
173 StringStream accumulator(&allocator);
174 relocinfo->target_object()->ShortPrint(&accumulator);
175 std::unique_ptr<char[]> obj_name = accumulator.ToCString();
176 out->AddFormatted(" ;; object: %s", obj_name.get());
177 } else if (rmode == RelocInfo::EXTERNAL_REFERENCE) {
178 const char* reference_name =
179 ref_encoder ? ref_encoder->NameOfAddress(
180 isolate, relocinfo->target_external_reference())
181 : "unknown";
182 out->AddFormatted(" ;; external reference (%s)", reference_name);
183 } else if (RelocInfo::IsCodeTargetMode(rmode)) {
184 out->AddFormatted(" ;; code:");
185 Code* code = isolate->heap()->GcSafeFindCodeForInnerPointer(
186 relocinfo->target_address());
187 Code::Kind kind = code->kind();
188 if (kind == Code::STUB) {
189 // Get the STUB key and extract major and minor key.
190 uint32_t key = code->stub_key();
191 uint32_t minor_key = CodeStub::MinorKeyFromKey(key);
192 CodeStub::Major major_key = CodeStub::GetMajorKey(code);
193 DCHECK(major_key == CodeStub::MajorKeyFromKey(key));
194 out->AddFormatted(" %s, %s, ", Code::Kind2String(kind),
195 CodeStub::MajorName(major_key));
196 out->AddFormatted("minor: %d", minor_key);
197 } else if (code->is_builtin()) {
198 out->AddFormatted(" Builtin::%s", Builtins::name(code->builtin_index()));
199 } else {
200 out->AddFormatted(" %s", Code::Kind2String(kind));
201 }
202 } else if (RelocInfo::IsRuntimeEntry(rmode) && isolate &&
203 isolate->deoptimizer_data() != nullptr) {
204 // A runtime entry relocinfo might be a deoptimization bailout.
205 Address addr = relocinfo->target_address();
206 DeoptimizeKind type;
207 if (Deoptimizer::IsDeoptimizationEntry(isolate, addr, &type)) {
208 int id = relocinfo->GetDeoptimizationId(isolate, type);
209 out->AddFormatted(" ;; %s deoptimization bailout %d",
210 Deoptimizer::MessageFor(type), id);
211 } else {
212 out->AddFormatted(" ;; %s", RelocInfo::RelocModeName(rmode));
213 }
214 } else {
215 out->AddFormatted(" ;; %s", RelocInfo::RelocModeName(rmode));
216 }
217 }
218
DecodeIt(Isolate * isolate,ExternalReferenceEncoder * ref_encoder,std::ostream * os,CodeReference code,const V8NameConverter & converter,byte * begin,byte * end,Address current_pc)219 static int DecodeIt(Isolate* isolate, ExternalReferenceEncoder* ref_encoder,
220 std::ostream* os, CodeReference code,
221 const V8NameConverter& converter, byte* begin, byte* end,
222 Address current_pc) {
223 v8::internal::EmbeddedVector<char, 128> decode_buffer;
224 v8::internal::EmbeddedVector<char, kOutBufferSize> out_buffer;
225 StringBuilder out(out_buffer.start(), out_buffer.length());
226 byte* pc = begin;
227 disasm::Disassembler d(converter,
228 disasm::Disassembler::kContinueOnUnimplementedOpcode);
229 RelocIterator* it = nullptr;
230 if (!code.is_null()) {
231 it = new RelocIterator(code);
232 } else {
233 // No relocation information when printing code stubs.
234 }
235 int constants = -1; // no constants being decoded at the start
236
237 while (pc < end) {
238 // First decode instruction so that we know its length.
239 byte* prev_pc = pc;
240 if (constants > 0) {
241 SNPrintF(decode_buffer,
242 "%08x constant",
243 *reinterpret_cast<int32_t*>(pc));
244 constants--;
245 pc += 4;
246 } else {
247 int num_const = d.ConstantPoolSizeAt(pc);
248 if (num_const >= 0) {
249 SNPrintF(decode_buffer,
250 "%08x constant pool begin (num_const = %d)",
251 *reinterpret_cast<int32_t*>(pc), num_const);
252 constants = num_const;
253 pc += 4;
254 } else if (it != nullptr && !it->done() &&
255 it->rinfo()->pc() == reinterpret_cast<Address>(pc) &&
256 it->rinfo()->rmode() == RelocInfo::INTERNAL_REFERENCE) {
257 // raw pointer embedded in code stream, e.g., jump table
258 byte* ptr = *reinterpret_cast<byte**>(pc);
259 SNPrintF(
260 decode_buffer, "%08" V8PRIxPTR " jump table entry %4" PRIuS,
261 reinterpret_cast<intptr_t>(ptr), static_cast<size_t>(ptr - begin));
262 pc += sizeof(ptr);
263 } else {
264 decode_buffer[0] = '\0';
265 pc += d.InstructionDecode(decode_buffer, pc);
266 }
267 }
268
269 // Collect RelocInfo for this instruction (prev_pc .. pc-1)
270 std::vector<const char*> comments;
271 std::vector<Address> pcs;
272 std::vector<RelocInfo::Mode> rmodes;
273 std::vector<intptr_t> datas;
274 if (it != nullptr) {
275 while (!it->done() && it->rinfo()->pc() < reinterpret_cast<Address>(pc)) {
276 if (RelocInfo::IsComment(it->rinfo()->rmode())) {
277 // For comments just collect the text.
278 comments.push_back(
279 reinterpret_cast<const char*>(it->rinfo()->data()));
280 } else {
281 // For other reloc info collect all data.
282 pcs.push_back(it->rinfo()->pc());
283 rmodes.push_back(it->rinfo()->rmode());
284 datas.push_back(it->rinfo()->data());
285 }
286 it->next();
287 }
288 }
289
290 // Comments.
291 for (size_t i = 0; i < comments.size(); i++) {
292 out.AddFormatted(" %s", comments[i]);
293 DumpBuffer(os, &out);
294 }
295
296 // Instruction address and instruction offset.
297 if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) {
298 // If this is the given "current" pc, make it yellow and bold.
299 out.AddFormatted("\033[33;1m");
300 }
301 out.AddFormatted("%p %4" V8PRIxPTRDIFF " ", static_cast<void*>(prev_pc),
302 prev_pc - begin);
303
304 // Instruction.
305 out.AddFormatted("%s", decode_buffer.start());
306
307 // Print all the reloc info for this instruction which are not comments.
308 for (size_t i = 0; i < pcs.size(); i++) {
309 // Put together the reloc info
310 const CodeReference& host = code;
311 Address constant_pool =
312 host.is_null() ? kNullAddress : host.constant_pool();
313 RelocInfo relocinfo(pcs[i], rmodes[i], datas[i], nullptr, constant_pool);
314
315 bool first_reloc_info = (i == 0);
316 PrintRelocInfo(&out, isolate, ref_encoder, os, &relocinfo,
317 first_reloc_info);
318 }
319
320 // If this is a constant pool load and we haven't found any RelocInfo
321 // already, check if we can find some RelocInfo for the target address in
322 // the constant pool.
323 if (pcs.empty() && !code.is_null()) {
324 RelocInfo dummy_rinfo(reinterpret_cast<Address>(prev_pc), RelocInfo::NONE,
325 0, nullptr);
326 if (dummy_rinfo.IsInConstantPool()) {
327 Address constant_pool_entry_address =
328 dummy_rinfo.constant_pool_entry_address();
329 RelocIterator reloc_it(code);
330 while (!reloc_it.done()) {
331 if (reloc_it.rinfo()->IsInConstantPool() &&
332 (reloc_it.rinfo()->constant_pool_entry_address() ==
333 constant_pool_entry_address)) {
334 PrintRelocInfo(&out, isolate, ref_encoder, os, reloc_it.rinfo());
335 break;
336 }
337 reloc_it.next();
338 }
339 }
340 }
341
342 if (FLAG_log_colour && reinterpret_cast<Address>(prev_pc) == current_pc) {
343 out.AddFormatted("\033[m");
344 }
345
346 DumpBuffer(os, &out);
347 }
348
349 // Emit comments following the last instruction (if any).
350 if (it != nullptr) {
351 for ( ; !it->done(); it->next()) {
352 if (RelocInfo::IsComment(it->rinfo()->rmode())) {
353 out.AddFormatted(" %s",
354 reinterpret_cast<const char*>(it->rinfo()->data()));
355 DumpBuffer(os, &out);
356 }
357 }
358 }
359
360 delete it;
361 return static_cast<int>(pc - begin);
362 }
363
Decode(Isolate * isolate,std::ostream * os,byte * begin,byte * end,CodeReference code,Address current_pc)364 int Disassembler::Decode(Isolate* isolate, std::ostream* os, byte* begin,
365 byte* end, CodeReference code, Address current_pc) {
366 V8NameConverter v8NameConverter(isolate, code);
367 bool decode_off_heap = isolate && InstructionStream::PcIsOffHeap(
368 isolate, bit_cast<Address>(begin));
369 CodeReference code_ref = decode_off_heap ? CodeReference() : code;
370 if (isolate) {
371 // We have an isolate, so support external reference names.
372 SealHandleScope shs(isolate);
373 DisallowHeapAllocation no_alloc;
374 ExternalReferenceEncoder ref_encoder(isolate);
375 return DecodeIt(isolate, &ref_encoder, os, code_ref, v8NameConverter, begin,
376 end, current_pc);
377 } else {
378 // No isolate => isolate-independent code. No external reference names.
379 return DecodeIt(nullptr, nullptr, os, code_ref, v8NameConverter, begin, end,
380 current_pc);
381 }
382 }
383
384 #else // ENABLE_DISASSEMBLER
385
386 int Disassembler::Decode(Isolate* isolate, std::ostream* os, byte* begin,
387 byte* end, CodeReference code, Address current_pc) {
388 return 0;
389 }
390
391 #endif // ENABLE_DISASSEMBLER
392
393 } // namespace internal
394 } // namespace v8
395