• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 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/wasm/wasm-debug.h"
6 
7 #include <iomanip>
8 #include <unordered_map>
9 
10 #include "src/base/optional.h"
11 #include "src/base/platform/wrappers.h"
12 #include "src/codegen/assembler-inl.h"
13 #include "src/common/assert-scope.h"
14 #include "src/compiler/wasm-compiler.h"
15 #include "src/debug/debug-evaluate.h"
16 #include "src/execution/frames-inl.h"
17 #include "src/heap/factory.h"
18 #include "src/wasm/baseline/liftoff-compiler.h"
19 #include "src/wasm/baseline/liftoff-register.h"
20 #include "src/wasm/module-decoder.h"
21 #include "src/wasm/value-type.h"
22 #include "src/wasm/wasm-code-manager.h"
23 #include "src/wasm/wasm-engine.h"
24 #include "src/wasm/wasm-limits.h"
25 #include "src/wasm/wasm-module.h"
26 #include "src/wasm/wasm-objects-inl.h"
27 #include "src/wasm/wasm-opcodes-inl.h"
28 #include "src/wasm/wasm-subtyping.h"
29 #include "src/wasm/wasm-value.h"
30 #include "src/zone/accounting-allocator.h"
31 
32 namespace v8 {
33 namespace internal {
34 namespace wasm {
35 
36 namespace {
37 
38 using ImportExportKey = std::pair<ImportExportKindCode, uint32_t>;
39 
40 enum ReturnLocation { kAfterBreakpoint, kAfterWasmCall };
41 
FindNewPC(WasmFrame * frame,WasmCode * wasm_code,int byte_offset,ReturnLocation return_location)42 Address FindNewPC(WasmFrame* frame, WasmCode* wasm_code, int byte_offset,
43                   ReturnLocation return_location) {
44   base::Vector<const uint8_t> new_pos_table = wasm_code->source_positions();
45 
46   DCHECK_LE(0, byte_offset);
47 
48   // Find the size of the call instruction by computing the distance from the
49   // source position entry to the return address.
50   WasmCode* old_code = frame->wasm_code();
51   int pc_offset = static_cast<int>(frame->pc() - old_code->instruction_start());
52   base::Vector<const uint8_t> old_pos_table = old_code->source_positions();
53   SourcePositionTableIterator old_it(old_pos_table);
54   int call_offset = -1;
55   while (!old_it.done() && old_it.code_offset() < pc_offset) {
56     call_offset = old_it.code_offset();
57     old_it.Advance();
58   }
59   DCHECK_LE(0, call_offset);
60   int call_instruction_size = pc_offset - call_offset;
61 
62   // If {return_location == kAfterBreakpoint} we search for the first code
63   // offset which is marked as instruction (i.e. not the breakpoint).
64   // If {return_location == kAfterWasmCall} we return the last code offset
65   // associated with the byte offset.
66   SourcePositionTableIterator it(new_pos_table);
67   while (!it.done() && it.source_position().ScriptOffset() != byte_offset) {
68     it.Advance();
69   }
70   if (return_location == kAfterBreakpoint) {
71     while (!it.is_statement()) it.Advance();
72     DCHECK_EQ(byte_offset, it.source_position().ScriptOffset());
73     return wasm_code->instruction_start() + it.code_offset() +
74            call_instruction_size;
75   }
76 
77   DCHECK_EQ(kAfterWasmCall, return_location);
78   int code_offset;
79   do {
80     code_offset = it.code_offset();
81     it.Advance();
82   } while (!it.done() && it.source_position().ScriptOffset() == byte_offset);
83   return wasm_code->instruction_start() + code_offset + call_instruction_size;
84 }
85 
86 }  // namespace
87 
Print(std::ostream & os) const88 void DebugSideTable::Print(std::ostream& os) const {
89   os << "Debug side table (" << num_locals_ << " locals, " << entries_.size()
90      << " entries):\n";
91   for (auto& entry : entries_) entry.Print(os);
92   os << "\n";
93 }
94 
Print(std::ostream & os) const95 void DebugSideTable::Entry::Print(std::ostream& os) const {
96   os << std::setw(6) << std::hex << pc_offset_ << std::dec << " stack height "
97      << stack_height_ << " [";
98   for (auto& value : changed_values_) {
99     os << " " << value.type.name() << ":";
100     switch (value.storage) {
101       case kConstant:
102         os << "const#" << value.i32_const;
103         break;
104       case kRegister:
105         os << "reg#" << value.reg_code;
106         break;
107       case kStack:
108         os << "stack#" << value.stack_offset;
109         break;
110     }
111   }
112   os << " ]\n";
113 }
114 
115 class DebugInfoImpl {
116  public:
DebugInfoImpl(NativeModule * native_module)117   explicit DebugInfoImpl(NativeModule* native_module)
118       : native_module_(native_module) {}
119 
120   DebugInfoImpl(const DebugInfoImpl&) = delete;
121   DebugInfoImpl& operator=(const DebugInfoImpl&) = delete;
122 
GetNumLocals(Address pc)123   int GetNumLocals(Address pc) {
124     FrameInspectionScope scope(this, pc);
125     if (!scope.is_inspectable()) return 0;
126     return scope.debug_side_table->num_locals();
127   }
128 
GetLocalValue(int local,Address pc,Address fp,Address debug_break_fp,Isolate * isolate)129   WasmValue GetLocalValue(int local, Address pc, Address fp,
130                           Address debug_break_fp, Isolate* isolate) {
131     FrameInspectionScope scope(this, pc);
132     return GetValue(scope.debug_side_table, scope.debug_side_table_entry, local,
133                     fp, debug_break_fp, isolate);
134   }
135 
GetStackDepth(Address pc)136   int GetStackDepth(Address pc) {
137     FrameInspectionScope scope(this, pc);
138     if (!scope.is_inspectable()) return 0;
139     int num_locals = scope.debug_side_table->num_locals();
140     int stack_height = scope.debug_side_table_entry->stack_height();
141     return stack_height - num_locals;
142   }
143 
GetStackValue(int index,Address pc,Address fp,Address debug_break_fp,Isolate * isolate)144   WasmValue GetStackValue(int index, Address pc, Address fp,
145                           Address debug_break_fp, Isolate* isolate) {
146     FrameInspectionScope scope(this, pc);
147     int num_locals = scope.debug_side_table->num_locals();
148     int value_count = scope.debug_side_table_entry->stack_height();
149     if (num_locals + index >= value_count) return {};
150     return GetValue(scope.debug_side_table, scope.debug_side_table_entry,
151                     num_locals + index, fp, debug_break_fp, isolate);
152   }
153 
GetFunctionAtAddress(Address pc)154   const WasmFunction& GetFunctionAtAddress(Address pc) {
155     FrameInspectionScope scope(this, pc);
156     auto* module = native_module_->module();
157     return module->functions[scope.code->index()];
158   }
159 
GetExportName(ImportExportKindCode kind,uint32_t index)160   WireBytesRef GetExportName(ImportExportKindCode kind, uint32_t index) {
161     base::MutexGuard guard(&mutex_);
162     if (!export_names_) {
163       export_names_ =
164           std::make_unique<std::map<ImportExportKey, WireBytesRef>>();
165       for (auto exp : native_module_->module()->export_table) {
166         auto exp_key = std::make_pair(exp.kind, exp.index);
167         if (export_names_->find(exp_key) != export_names_->end()) continue;
168         export_names_->insert(std::make_pair(exp_key, exp.name));
169       }
170     }
171     auto it = export_names_->find(std::make_pair(kind, index));
172     if (it != export_names_->end()) return it->second;
173     return {};
174   }
175 
GetImportName(ImportExportKindCode kind,uint32_t index)176   std::pair<WireBytesRef, WireBytesRef> GetImportName(ImportExportKindCode kind,
177                                                       uint32_t index) {
178     base::MutexGuard guard(&mutex_);
179     if (!import_names_) {
180       import_names_ = std::make_unique<
181           std::map<ImportExportKey, std::pair<WireBytesRef, WireBytesRef>>>();
182       for (auto imp : native_module_->module()->import_table) {
183         import_names_->insert(
184             std::make_pair(std::make_pair(imp.kind, imp.index),
185                            std::make_pair(imp.module_name, imp.field_name)));
186       }
187     }
188     auto it = import_names_->find(std::make_pair(kind, index));
189     if (it != import_names_->end()) return it->second;
190     return {};
191   }
192 
GetTypeName(int type_index)193   WireBytesRef GetTypeName(int type_index) {
194     base::MutexGuard guard(&mutex_);
195     if (!type_names_) {
196       type_names_ = std::make_unique<NameMap>(DecodeNameMap(
197           native_module_->wire_bytes(), NameSectionKindCode::kTypeCode));
198     }
199     return type_names_->GetName(type_index);
200   }
201 
GetLocalName(int func_index,int local_index)202   WireBytesRef GetLocalName(int func_index, int local_index) {
203     base::MutexGuard guard(&mutex_);
204     if (!local_names_) {
205       local_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
206           native_module_->wire_bytes(), NameSectionKindCode::kLocalCode));
207     }
208     return local_names_->GetName(func_index, local_index);
209   }
210 
GetFieldName(int struct_index,int field_index)211   WireBytesRef GetFieldName(int struct_index, int field_index) {
212     base::MutexGuard guard(&mutex_);
213     if (!field_names_) {
214       field_names_ = std::make_unique<IndirectNameMap>(DecodeIndirectNameMap(
215           native_module_->wire_bytes(), NameSectionKindCode::kFieldCode));
216     }
217     return field_names_->GetName(struct_index, field_index);
218   }
219 
220   // If the frame position is not in the list of breakpoints, return that
221   // position. Return 0 otherwise.
222   // This is used to generate a "dead breakpoint" in Liftoff, which is necessary
223   // for OSR to find the correct return address.
DeadBreakpoint(WasmFrame * frame,base::Vector<const int> breakpoints)224   int DeadBreakpoint(WasmFrame* frame, base::Vector<const int> breakpoints) {
225     const auto& function =
226         native_module_->module()->functions[frame->function_index()];
227     int offset = frame->position() - function.code.offset();
228     if (std::binary_search(breakpoints.begin(), breakpoints.end(), offset)) {
229       return 0;
230     }
231     return offset;
232   }
233 
234   // Find the dead breakpoint (see above) for the top wasm frame, if that frame
235   // is in the function of the given index.
DeadBreakpoint(int func_index,base::Vector<const int> breakpoints,Isolate * isolate)236   int DeadBreakpoint(int func_index, base::Vector<const int> breakpoints,
237                      Isolate* isolate) {
238     StackTraceFrameIterator it(isolate);
239     if (it.done() || !it.is_wasm()) return 0;
240     auto* wasm_frame = WasmFrame::cast(it.frame());
241     if (static_cast<int>(wasm_frame->function_index()) != func_index) return 0;
242     return DeadBreakpoint(wasm_frame, breakpoints);
243   }
244 
RecompileLiftoffWithBreakpoints(int func_index,base::Vector<const int> offsets,int dead_breakpoint)245   WasmCode* RecompileLiftoffWithBreakpoints(int func_index,
246                                             base::Vector<const int> offsets,
247                                             int dead_breakpoint) {
248     DCHECK(!mutex_.TryLock());  // Mutex is held externally.
249 
250     ForDebugging for_debugging = offsets.size() == 1 && offsets[0] == 0
251                                      ? kForStepping
252                                      : kWithBreakpoints;
253 
254     // Check the cache first.
255     for (auto begin = cached_debugging_code_.begin(), it = begin,
256               end = cached_debugging_code_.end();
257          it != end; ++it) {
258       if (it->func_index == func_index &&
259           it->breakpoint_offsets.as_vector() == offsets &&
260           it->dead_breakpoint == dead_breakpoint) {
261         // Rotate the cache entry to the front (for LRU).
262         for (; it != begin; --it) std::iter_swap(it, it - 1);
263         if (for_debugging == kWithBreakpoints) {
264           // Re-install the code, in case it was replaced in the meantime.
265           native_module_->ReinstallDebugCode(it->code);
266         }
267         return it->code;
268       }
269     }
270 
271     // Recompile the function with Liftoff, setting the new breakpoints.
272     // Not thread-safe. The caller is responsible for locking {mutex_}.
273     CompilationEnv env = native_module_->CreateCompilationEnv();
274     auto* function = &native_module_->module()->functions[func_index];
275     base::Vector<const uint8_t> wire_bytes = native_module_->wire_bytes();
276     FunctionBody body{function->sig, function->code.offset(),
277                       wire_bytes.begin() + function->code.offset(),
278                       wire_bytes.begin() + function->code.end_offset()};
279     std::unique_ptr<DebugSideTable> debug_sidetable;
280 
281     // Debug side tables for stepping are generated lazily.
282     bool generate_debug_sidetable = for_debugging == kWithBreakpoints;
283     WasmCompilationResult result = ExecuteLiftoffCompilation(
284         &env, body, func_index, for_debugging,
285         LiftoffOptions{}
286             .set_breakpoints(offsets)
287             .set_dead_breakpoint(dead_breakpoint)
288             .set_debug_sidetable(generate_debug_sidetable ? &debug_sidetable
289                                                           : nullptr));
290     // Liftoff compilation failure is a FATAL error. We rely on complete Liftoff
291     // support for debugging.
292     if (!result.succeeded()) FATAL("Liftoff compilation failed");
293     DCHECK_EQ(generate_debug_sidetable, debug_sidetable != nullptr);
294 
295     WasmCode* new_code = native_module_->PublishCode(
296         native_module_->AddCompiledCode(std::move(result)));
297 
298     DCHECK(new_code->is_inspectable());
299     if (generate_debug_sidetable) {
300       base::MutexGuard lock(&debug_side_tables_mutex_);
301       DCHECK_EQ(0, debug_side_tables_.count(new_code));
302       debug_side_tables_.emplace(new_code, std::move(debug_sidetable));
303     }
304 
305     // Insert new code into the cache. Insert before existing elements for LRU.
306     cached_debugging_code_.insert(
307         cached_debugging_code_.begin(),
308         CachedDebuggingCode{func_index, base::OwnedVector<int>::Of(offsets),
309                             dead_breakpoint, new_code});
310     // Increase the ref count (for the cache entry).
311     new_code->IncRef();
312     // Remove exceeding element.
313     if (cached_debugging_code_.size() > kMaxCachedDebuggingCode) {
314       // Put the code in the surrounding CodeRefScope to delay deletion until
315       // after the mutex is released.
316       WasmCodeRefScope::AddRef(cached_debugging_code_.back().code);
317       cached_debugging_code_.back().code->DecRefOnLiveCode();
318       cached_debugging_code_.pop_back();
319     }
320     DCHECK_GE(kMaxCachedDebuggingCode, cached_debugging_code_.size());
321 
322     return new_code;
323   }
324 
SetBreakpoint(int func_index,int offset,Isolate * isolate)325   void SetBreakpoint(int func_index, int offset, Isolate* isolate) {
326     // Put the code ref scope outside of the mutex, so we don't unnecessarily
327     // hold the mutex while freeing code.
328     WasmCodeRefScope wasm_code_ref_scope;
329 
330     // Hold the mutex while modifying breakpoints, to ensure consistency when
331     // multiple isolates set/remove breakpoints at the same time.
332     base::MutexGuard guard(&mutex_);
333 
334     // offset == 0 indicates flooding and should not happen here.
335     DCHECK_NE(0, offset);
336 
337     // Get the set of previously set breakpoints, to check later whether a new
338     // breakpoint was actually added.
339     std::vector<int> all_breakpoints = FindAllBreakpoints(func_index);
340 
341     auto& isolate_data = per_isolate_data_[isolate];
342     std::vector<int>& breakpoints =
343         isolate_data.breakpoints_per_function[func_index];
344     auto insertion_point =
345         std::lower_bound(breakpoints.begin(), breakpoints.end(), offset);
346     if (insertion_point != breakpoints.end() && *insertion_point == offset) {
347       // The breakpoint is already set for this isolate.
348       return;
349     }
350     breakpoints.insert(insertion_point, offset);
351 
352     DCHECK(std::is_sorted(all_breakpoints.begin(), all_breakpoints.end()));
353     // Find the insertion position within {all_breakpoints}.
354     insertion_point = std::lower_bound(all_breakpoints.begin(),
355                                        all_breakpoints.end(), offset);
356     bool breakpoint_exists =
357         insertion_point != all_breakpoints.end() && *insertion_point == offset;
358     // If the breakpoint was already set before, then we can just reuse the old
359     // code. Otherwise, recompile it. In any case, rewrite this isolate's stack
360     // to make sure that it uses up-to-date code containing the breakpoint.
361     WasmCode* new_code;
362     if (breakpoint_exists) {
363       new_code = native_module_->GetCode(func_index);
364     } else {
365       all_breakpoints.insert(insertion_point, offset);
366       int dead_breakpoint =
367           DeadBreakpoint(func_index, base::VectorOf(all_breakpoints), isolate);
368       new_code = RecompileLiftoffWithBreakpoints(
369           func_index, base::VectorOf(all_breakpoints), dead_breakpoint);
370     }
371     UpdateReturnAddresses(isolate, new_code, isolate_data.stepping_frame);
372   }
373 
FindAllBreakpoints(int func_index)374   std::vector<int> FindAllBreakpoints(int func_index) {
375     DCHECK(!mutex_.TryLock());  // Mutex must be held externally.
376     std::set<int> breakpoints;
377     for (auto& data : per_isolate_data_) {
378       auto it = data.second.breakpoints_per_function.find(func_index);
379       if (it == data.second.breakpoints_per_function.end()) continue;
380       for (int offset : it->second) breakpoints.insert(offset);
381     }
382     return {breakpoints.begin(), breakpoints.end()};
383   }
384 
UpdateBreakpoints(int func_index,base::Vector<int> breakpoints,Isolate * isolate,StackFrameId stepping_frame,int dead_breakpoint)385   void UpdateBreakpoints(int func_index, base::Vector<int> breakpoints,
386                          Isolate* isolate, StackFrameId stepping_frame,
387                          int dead_breakpoint) {
388     DCHECK(!mutex_.TryLock());  // Mutex is held externally.
389     WasmCode* new_code = RecompileLiftoffWithBreakpoints(
390         func_index, breakpoints, dead_breakpoint);
391     UpdateReturnAddresses(isolate, new_code, stepping_frame);
392   }
393 
FloodWithBreakpoints(WasmFrame * frame,ReturnLocation return_location)394   void FloodWithBreakpoints(WasmFrame* frame, ReturnLocation return_location) {
395     // 0 is an invalid offset used to indicate flooding.
396     constexpr int kFloodingBreakpoints[] = {0};
397     DCHECK(frame->wasm_code()->is_liftoff());
398     // Generate an additional source position for the current byte offset.
399     base::MutexGuard guard(&mutex_);
400     WasmCode* new_code = RecompileLiftoffWithBreakpoints(
401         frame->function_index(), base::ArrayVector(kFloodingBreakpoints), 0);
402     UpdateReturnAddress(frame, new_code, return_location);
403 
404     per_isolate_data_[frame->isolate()].stepping_frame = frame->id();
405   }
406 
PrepareStep(WasmFrame * frame)407   bool PrepareStep(WasmFrame* frame) {
408     WasmCodeRefScope wasm_code_ref_scope;
409     wasm::WasmCode* code = frame->wasm_code();
410     if (!code->is_liftoff()) return false;  // Cannot step in TurboFan code.
411     if (IsAtReturn(frame)) return false;    // Will return after this step.
412     FloodWithBreakpoints(frame, kAfterBreakpoint);
413     return true;
414   }
415 
PrepareStepOutTo(WasmFrame * frame)416   void PrepareStepOutTo(WasmFrame* frame) {
417     WasmCodeRefScope wasm_code_ref_scope;
418     wasm::WasmCode* code = frame->wasm_code();
419     if (!code->is_liftoff()) return;  // Cannot step out to TurboFan code.
420     FloodWithBreakpoints(frame, kAfterWasmCall);
421   }
422 
ClearStepping(WasmFrame * frame)423   void ClearStepping(WasmFrame* frame) {
424     WasmCodeRefScope wasm_code_ref_scope;
425     base::MutexGuard guard(&mutex_);
426     auto* code = frame->wasm_code();
427     if (code->for_debugging() != kForStepping) return;
428     int func_index = code->index();
429     std::vector<int> breakpoints = FindAllBreakpoints(func_index);
430     int dead_breakpoint = DeadBreakpoint(frame, base::VectorOf(breakpoints));
431     WasmCode* new_code = RecompileLiftoffWithBreakpoints(
432         func_index, base::VectorOf(breakpoints), dead_breakpoint);
433     UpdateReturnAddress(frame, new_code, kAfterBreakpoint);
434   }
435 
ClearStepping(Isolate * isolate)436   void ClearStepping(Isolate* isolate) {
437     base::MutexGuard guard(&mutex_);
438     auto it = per_isolate_data_.find(isolate);
439     if (it != per_isolate_data_.end()) it->second.stepping_frame = NO_ID;
440   }
441 
IsStepping(WasmFrame * frame)442   bool IsStepping(WasmFrame* frame) {
443     Isolate* isolate = frame->wasm_instance().GetIsolate();
444     if (isolate->debug()->last_step_action() == StepInto) return true;
445     base::MutexGuard guard(&mutex_);
446     auto it = per_isolate_data_.find(isolate);
447     return it != per_isolate_data_.end() &&
448            it->second.stepping_frame == frame->id();
449   }
450 
RemoveBreakpoint(int func_index,int position,Isolate * isolate)451   void RemoveBreakpoint(int func_index, int position, Isolate* isolate) {
452     // Put the code ref scope outside of the mutex, so we don't unnecessarily
453     // hold the mutex while freeing code.
454     WasmCodeRefScope wasm_code_ref_scope;
455 
456     // Hold the mutex while modifying breakpoints, to ensure consistency when
457     // multiple isolates set/remove breakpoints at the same time.
458     base::MutexGuard guard(&mutex_);
459 
460     const auto& function = native_module_->module()->functions[func_index];
461     int offset = position - function.code.offset();
462 
463     auto& isolate_data = per_isolate_data_[isolate];
464     std::vector<int>& breakpoints =
465         isolate_data.breakpoints_per_function[func_index];
466     DCHECK_LT(0, offset);
467     auto insertion_point =
468         std::lower_bound(breakpoints.begin(), breakpoints.end(), offset);
469     if (insertion_point == breakpoints.end()) return;
470     if (*insertion_point != offset) return;
471     breakpoints.erase(insertion_point);
472 
473     std::vector<int> remaining = FindAllBreakpoints(func_index);
474     // If the breakpoint is still set in another isolate, don't remove it.
475     DCHECK(std::is_sorted(remaining.begin(), remaining.end()));
476     if (std::binary_search(remaining.begin(), remaining.end(), offset)) return;
477     int dead_breakpoint =
478         DeadBreakpoint(func_index, base::VectorOf(remaining), isolate);
479     UpdateBreakpoints(func_index, base::VectorOf(remaining), isolate,
480                       isolate_data.stepping_frame, dead_breakpoint);
481   }
482 
RemoveDebugSideTables(base::Vector<WasmCode * const> codes)483   void RemoveDebugSideTables(base::Vector<WasmCode* const> codes) {
484     base::MutexGuard guard(&debug_side_tables_mutex_);
485     for (auto* code : codes) {
486       debug_side_tables_.erase(code);
487     }
488   }
489 
GetDebugSideTableIfExists(const WasmCode * code) const490   DebugSideTable* GetDebugSideTableIfExists(const WasmCode* code) const {
491     base::MutexGuard guard(&debug_side_tables_mutex_);
492     auto it = debug_side_tables_.find(code);
493     return it == debug_side_tables_.end() ? nullptr : it->second.get();
494   }
495 
HasRemovedBreakpoints(const std::vector<int> & removed,const std::vector<int> & remaining)496   static bool HasRemovedBreakpoints(const std::vector<int>& removed,
497                                     const std::vector<int>& remaining) {
498     DCHECK(std::is_sorted(remaining.begin(), remaining.end()));
499     for (int offset : removed) {
500       // Return true if we removed a breakpoint which is not part of remaining.
501       if (!std::binary_search(remaining.begin(), remaining.end(), offset)) {
502         return true;
503       }
504     }
505     return false;
506   }
507 
RemoveIsolate(Isolate * isolate)508   void RemoveIsolate(Isolate* isolate) {
509     // Put the code ref scope outside of the mutex, so we don't unnecessarily
510     // hold the mutex while freeing code.
511     WasmCodeRefScope wasm_code_ref_scope;
512 
513     base::MutexGuard guard(&mutex_);
514     auto per_isolate_data_it = per_isolate_data_.find(isolate);
515     if (per_isolate_data_it == per_isolate_data_.end()) return;
516     std::unordered_map<int, std::vector<int>> removed_per_function =
517         std::move(per_isolate_data_it->second.breakpoints_per_function);
518     per_isolate_data_.erase(per_isolate_data_it);
519     for (auto& entry : removed_per_function) {
520       int func_index = entry.first;
521       std::vector<int>& removed = entry.second;
522       std::vector<int> remaining = FindAllBreakpoints(func_index);
523       if (HasRemovedBreakpoints(removed, remaining)) {
524         RecompileLiftoffWithBreakpoints(func_index, base::VectorOf(remaining),
525                                         0);
526       }
527     }
528   }
529 
530  private:
531   struct FrameInspectionScope {
FrameInspectionScopev8::internal::wasm::DebugInfoImpl::FrameInspectionScope532     FrameInspectionScope(DebugInfoImpl* debug_info, Address pc)
533         : code(wasm::GetWasmCodeManager()->LookupCode(pc)),
534           pc_offset(static_cast<int>(pc - code->instruction_start())),
535           debug_side_table(code->is_inspectable()
536                                ? debug_info->GetDebugSideTable(code)
537                                : nullptr),
538           debug_side_table_entry(debug_side_table
539                                      ? debug_side_table->GetEntry(pc_offset)
540                                      : nullptr) {
541       DCHECK_IMPLIES(code->is_inspectable(), debug_side_table_entry != nullptr);
542     }
543 
is_inspectablev8::internal::wasm::DebugInfoImpl::FrameInspectionScope544     bool is_inspectable() const { return debug_side_table_entry; }
545 
546     wasm::WasmCodeRefScope wasm_code_ref_scope;
547     wasm::WasmCode* code;
548     int pc_offset;
549     const DebugSideTable* debug_side_table;
550     const DebugSideTable::Entry* debug_side_table_entry;
551   };
552 
GetDebugSideTable(WasmCode * code)553   const DebugSideTable* GetDebugSideTable(WasmCode* code) {
554     DCHECK(code->is_inspectable());
555     {
556       // Only hold the mutex temporarily. We can't hold it while generating the
557       // debug side table, because compilation takes the {NativeModule} lock.
558       base::MutexGuard guard(&debug_side_tables_mutex_);
559       auto it = debug_side_tables_.find(code);
560       if (it != debug_side_tables_.end()) return it->second.get();
561     }
562 
563     // Otherwise create the debug side table now.
564     std::unique_ptr<DebugSideTable> debug_side_table =
565         GenerateLiftoffDebugSideTable(code);
566     DebugSideTable* ret = debug_side_table.get();
567 
568     // Check cache again, maybe another thread concurrently generated a debug
569     // side table already.
570     {
571       base::MutexGuard guard(&debug_side_tables_mutex_);
572       auto& slot = debug_side_tables_[code];
573       if (slot != nullptr) return slot.get();
574       slot = std::move(debug_side_table);
575     }
576 
577     // Print the code together with the debug table, if requested.
578     code->MaybePrint();
579     return ret;
580   }
581 
582   // Get the value of a local (including parameters) or stack value. Stack
583   // values follow the locals in the same index space.
GetValue(const DebugSideTable * debug_side_table,const DebugSideTable::Entry * debug_side_table_entry,int index,Address stack_frame_base,Address debug_break_fp,Isolate * isolate) const584   WasmValue GetValue(const DebugSideTable* debug_side_table,
585                      const DebugSideTable::Entry* debug_side_table_entry,
586                      int index, Address stack_frame_base,
587                      Address debug_break_fp, Isolate* isolate) const {
588     const auto* value =
589         debug_side_table->FindValue(debug_side_table_entry, index);
590     if (value->is_constant()) {
591       DCHECK(value->type == kWasmI32 || value->type == kWasmI64);
592       return value->type == kWasmI32 ? WasmValue(value->i32_const)
593                                      : WasmValue(int64_t{value->i32_const});
594     }
595 
596     if (value->is_register()) {
597       auto reg = LiftoffRegister::from_liftoff_code(value->reg_code);
598       auto gp_addr = [debug_break_fp](Register reg) {
599         return debug_break_fp +
600                WasmDebugBreakFrameConstants::GetPushedGpRegisterOffset(
601                    reg.code());
602       };
603       if (reg.is_gp_pair()) {
604         DCHECK_EQ(kWasmI64, value->type);
605         uint32_t low_word = ReadUnalignedValue<uint32_t>(gp_addr(reg.low_gp()));
606         uint32_t high_word =
607             ReadUnalignedValue<uint32_t>(gp_addr(reg.high_gp()));
608         return WasmValue((uint64_t{high_word} << 32) | low_word);
609       }
610       if (reg.is_gp()) {
611         if (value->type == kWasmI32) {
612           return WasmValue(ReadUnalignedValue<uint32_t>(gp_addr(reg.gp())));
613         } else if (value->type == kWasmI64) {
614           return WasmValue(ReadUnalignedValue<uint64_t>(gp_addr(reg.gp())));
615         } else if (value->type.is_reference()) {
616           Handle<Object> obj(
617               Object(ReadUnalignedValue<Address>(gp_addr(reg.gp()))), isolate);
618           return WasmValue(obj, value->type);
619         } else {
620           UNREACHABLE();
621         }
622       }
623       DCHECK(reg.is_fp() || reg.is_fp_pair());
624       // ifdef here to workaround unreachable code for is_fp_pair.
625 #ifdef V8_TARGET_ARCH_ARM
626       int code = reg.is_fp_pair() ? reg.low_fp().code() : reg.fp().code();
627 #else
628       int code = reg.fp().code();
629 #endif
630       Address spilled_addr =
631           debug_break_fp +
632           WasmDebugBreakFrameConstants::GetPushedFpRegisterOffset(code);
633       if (value->type == kWasmF32) {
634         return WasmValue(ReadUnalignedValue<float>(spilled_addr));
635       } else if (value->type == kWasmF64) {
636         return WasmValue(ReadUnalignedValue<double>(spilled_addr));
637       } else if (value->type == kWasmS128) {
638         return WasmValue(Simd128(ReadUnalignedValue<int16>(spilled_addr)));
639       } else {
640         // All other cases should have been handled above.
641         UNREACHABLE();
642       }
643     }
644 
645     // Otherwise load the value from the stack.
646     Address stack_address = stack_frame_base - value->stack_offset;
647     switch (value->type.kind()) {
648       case kI32:
649         return WasmValue(ReadUnalignedValue<int32_t>(stack_address));
650       case kI64:
651         return WasmValue(ReadUnalignedValue<int64_t>(stack_address));
652       case kF32:
653         return WasmValue(ReadUnalignedValue<float>(stack_address));
654       case kF64:
655         return WasmValue(ReadUnalignedValue<double>(stack_address));
656       case kS128:
657         return WasmValue(Simd128(ReadUnalignedValue<int16>(stack_address)));
658       case kRef:
659       case kOptRef:
660       case kRtt: {
661         Handle<Object> obj(Object(ReadUnalignedValue<Address>(stack_address)),
662                            isolate);
663         return WasmValue(obj, value->type);
664       }
665       case kI8:
666       case kI16:
667       case kVoid:
668       case kBottom:
669         UNREACHABLE();
670     }
671   }
672 
673   // After installing a Liftoff code object with a different set of breakpoints,
674   // update return addresses on the stack so that execution resumes in the new
675   // code. The frame layout itself should be independent of breakpoints.
UpdateReturnAddresses(Isolate * isolate,WasmCode * new_code,StackFrameId stepping_frame)676   void UpdateReturnAddresses(Isolate* isolate, WasmCode* new_code,
677                              StackFrameId stepping_frame) {
678     // The first return location is after the breakpoint, others are after wasm
679     // calls.
680     ReturnLocation return_location = kAfterBreakpoint;
681     for (StackTraceFrameIterator it(isolate); !it.done();
682          it.Advance(), return_location = kAfterWasmCall) {
683       // We still need the flooded function for stepping.
684       if (it.frame()->id() == stepping_frame) continue;
685       if (!it.is_wasm()) continue;
686       WasmFrame* frame = WasmFrame::cast(it.frame());
687       if (frame->native_module() != new_code->native_module()) continue;
688       if (frame->function_index() != new_code->index()) continue;
689       if (!frame->wasm_code()->is_liftoff()) continue;
690       UpdateReturnAddress(frame, new_code, return_location);
691     }
692   }
693 
UpdateReturnAddress(WasmFrame * frame,WasmCode * new_code,ReturnLocation return_location)694   void UpdateReturnAddress(WasmFrame* frame, WasmCode* new_code,
695                            ReturnLocation return_location) {
696     DCHECK(new_code->is_liftoff());
697     DCHECK_EQ(frame->function_index(), new_code->index());
698     DCHECK_EQ(frame->native_module(), new_code->native_module());
699     DCHECK(frame->wasm_code()->is_liftoff());
700     Address new_pc =
701         FindNewPC(frame, new_code, frame->byte_offset(), return_location);
702 #ifdef DEBUG
703     int old_position = frame->position();
704 #endif
705 #if V8_TARGET_ARCH_X64
706     if (frame->wasm_code()->for_debugging()) {
707       base::Memory<Address>(frame->fp() - kOSRTargetOffset) = new_pc;
708     }
709 #else
710     PointerAuthentication::ReplacePC(frame->pc_address(), new_pc,
711                                      kSystemPointerSize);
712 #endif
713     // The frame position should still be the same after OSR.
714     DCHECK_EQ(old_position, frame->position());
715   }
716 
IsAtReturn(WasmFrame * frame)717   bool IsAtReturn(WasmFrame* frame) {
718     DisallowGarbageCollection no_gc;
719     int position = frame->position();
720     NativeModule* native_module =
721         frame->wasm_instance().module_object().native_module();
722     uint8_t opcode = native_module->wire_bytes()[position];
723     if (opcode == kExprReturn) return true;
724     // Another implicit return is at the last kExprEnd in the function body.
725     int func_index = frame->function_index();
726     WireBytesRef code = native_module->module()->functions[func_index].code;
727     return static_cast<size_t>(position) == code.end_offset() - 1;
728   }
729 
730   // Isolate-specific data, for debugging modules that are shared by multiple
731   // isolates.
732   struct PerIsolateDebugData {
733     // Keeps track of the currently set breakpoints (by offset within that
734     // function).
735     std::unordered_map<int, std::vector<int>> breakpoints_per_function;
736 
737     // Store the frame ID when stepping, to avoid overwriting that frame when
738     // setting or removing a breakpoint.
739     StackFrameId stepping_frame = NO_ID;
740   };
741 
742   NativeModule* const native_module_;
743 
744   mutable base::Mutex debug_side_tables_mutex_;
745 
746   // DebugSideTable per code object, lazily initialized.
747   std::unordered_map<const WasmCode*, std::unique_ptr<DebugSideTable>>
748       debug_side_tables_;
749 
750   // {mutex_} protects all fields below.
751   mutable base::Mutex mutex_;
752 
753   // Cache a fixed number of WasmCode objects that were generated for debugging.
754   // This is useful especially in stepping, because stepping code is cleared on
755   // every pause and re-installed on the next step.
756   // This is a LRU cache (most recently used entries first).
757   static constexpr size_t kMaxCachedDebuggingCode = 3;
758   struct CachedDebuggingCode {
759     int func_index;
760     base::OwnedVector<const int> breakpoint_offsets;
761     int dead_breakpoint;
762     WasmCode* code;
763   };
764   std::vector<CachedDebuggingCode> cached_debugging_code_;
765 
766   // Names of exports, lazily derived from the exports table.
767   std::unique_ptr<std::map<ImportExportKey, wasm::WireBytesRef>> export_names_;
768 
769   // Names of imports, lazily derived from the imports table.
770   std::unique_ptr<std::map<ImportExportKey,
771                            std::pair<wasm::WireBytesRef, wasm::WireBytesRef>>>
772       import_names_;
773 
774   // Names of types, lazily decoded from the wire bytes.
775   std::unique_ptr<NameMap> type_names_;
776   // Names of locals, lazily decoded from the wire bytes.
777   std::unique_ptr<IndirectNameMap> local_names_;
778   // Names of struct fields, lazily decoded from the wire bytes.
779   std::unique_ptr<IndirectNameMap> field_names_;
780 
781   // Isolate-specific data.
782   std::unordered_map<Isolate*, PerIsolateDebugData> per_isolate_data_;
783 };
784 
DebugInfo(NativeModule * native_module)785 DebugInfo::DebugInfo(NativeModule* native_module)
786     : impl_(std::make_unique<DebugInfoImpl>(native_module)) {}
787 
788 DebugInfo::~DebugInfo() = default;
789 
GetNumLocals(Address pc)790 int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); }
791 
GetLocalValue(int local,Address pc,Address fp,Address debug_break_fp,Isolate * isolate)792 WasmValue DebugInfo::GetLocalValue(int local, Address pc, Address fp,
793                                    Address debug_break_fp, Isolate* isolate) {
794   return impl_->GetLocalValue(local, pc, fp, debug_break_fp, isolate);
795 }
796 
GetStackDepth(Address pc)797 int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); }
798 
GetStackValue(int index,Address pc,Address fp,Address debug_break_fp,Isolate * isolate)799 WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp,
800                                    Address debug_break_fp, Isolate* isolate) {
801   return impl_->GetStackValue(index, pc, fp, debug_break_fp, isolate);
802 }
803 
GetFunctionAtAddress(Address pc)804 const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
805   return impl_->GetFunctionAtAddress(pc);
806 }
807 
GetExportName(ImportExportKindCode code,uint32_t index)808 WireBytesRef DebugInfo::GetExportName(ImportExportKindCode code,
809                                       uint32_t index) {
810   return impl_->GetExportName(code, index);
811 }
812 
GetImportName(ImportExportKindCode code,uint32_t index)813 std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName(
814     ImportExportKindCode code, uint32_t index) {
815   return impl_->GetImportName(code, index);
816 }
817 
GetTypeName(int type_index)818 WireBytesRef DebugInfo::GetTypeName(int type_index) {
819   return impl_->GetTypeName(type_index);
820 }
821 
GetLocalName(int func_index,int local_index)822 WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) {
823   return impl_->GetLocalName(func_index, local_index);
824 }
825 
GetFieldName(int struct_index,int field_index)826 WireBytesRef DebugInfo::GetFieldName(int struct_index, int field_index) {
827   return impl_->GetFieldName(struct_index, field_index);
828 }
829 
SetBreakpoint(int func_index,int offset,Isolate * current_isolate)830 void DebugInfo::SetBreakpoint(int func_index, int offset,
831                               Isolate* current_isolate) {
832   impl_->SetBreakpoint(func_index, offset, current_isolate);
833 }
834 
PrepareStep(WasmFrame * frame)835 bool DebugInfo::PrepareStep(WasmFrame* frame) {
836   return impl_->PrepareStep(frame);
837 }
838 
PrepareStepOutTo(WasmFrame * frame)839 void DebugInfo::PrepareStepOutTo(WasmFrame* frame) {
840   impl_->PrepareStepOutTo(frame);
841 }
842 
ClearStepping(Isolate * isolate)843 void DebugInfo::ClearStepping(Isolate* isolate) {
844   impl_->ClearStepping(isolate);
845 }
846 
ClearStepping(WasmFrame * frame)847 void DebugInfo::ClearStepping(WasmFrame* frame) { impl_->ClearStepping(frame); }
848 
IsStepping(WasmFrame * frame)849 bool DebugInfo::IsStepping(WasmFrame* frame) {
850   return impl_->IsStepping(frame);
851 }
852 
RemoveBreakpoint(int func_index,int offset,Isolate * current_isolate)853 void DebugInfo::RemoveBreakpoint(int func_index, int offset,
854                                  Isolate* current_isolate) {
855   impl_->RemoveBreakpoint(func_index, offset, current_isolate);
856 }
857 
RemoveDebugSideTables(base::Vector<WasmCode * const> code)858 void DebugInfo::RemoveDebugSideTables(base::Vector<WasmCode* const> code) {
859   impl_->RemoveDebugSideTables(code);
860 }
861 
GetDebugSideTableIfExists(const WasmCode * code) const862 DebugSideTable* DebugInfo::GetDebugSideTableIfExists(
863     const WasmCode* code) const {
864   return impl_->GetDebugSideTableIfExists(code);
865 }
866 
RemoveIsolate(Isolate * isolate)867 void DebugInfo::RemoveIsolate(Isolate* isolate) {
868   return impl_->RemoveIsolate(isolate);
869 }
870 
871 }  // namespace wasm
872 
873 namespace {
874 
875 // Return the next breakable position at or after {offset_in_func} in function
876 // {func_index}, or 0 if there is none.
877 // Note that 0 is never a breakable position in wasm, since the first byte
878 // contains the locals count for the function.
FindNextBreakablePosition(wasm::NativeModule * native_module,int func_index,int offset_in_func)879 int FindNextBreakablePosition(wasm::NativeModule* native_module, int func_index,
880                               int offset_in_func) {
881   AccountingAllocator alloc;
882   Zone tmp(&alloc, ZONE_NAME);
883   wasm::BodyLocalDecls locals(&tmp);
884   const byte* module_start = native_module->wire_bytes().begin();
885   const wasm::WasmFunction& func =
886       native_module->module()->functions[func_index];
887   wasm::BytecodeIterator iterator(module_start + func.code.offset(),
888                                   module_start + func.code.end_offset(),
889                                   &locals);
890   DCHECK_LT(0, locals.encoded_size);
891   if (offset_in_func < 0) return 0;
892   for (; iterator.has_next(); iterator.next()) {
893     if (iterator.pc_offset() < static_cast<uint32_t>(offset_in_func)) continue;
894     if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue;
895     return static_cast<int>(iterator.pc_offset());
896   }
897   return 0;
898 }
899 
SetBreakOnEntryFlag(Script script,bool enabled)900 void SetBreakOnEntryFlag(Script script, bool enabled) {
901   if (script.break_on_entry() == enabled) return;
902 
903   script.set_break_on_entry(enabled);
904   // Update the "break_on_entry" flag on all live instances.
905   i::WeakArrayList weak_instance_list = script.wasm_weak_instance_list();
906   for (int i = 0; i < weak_instance_list.length(); ++i) {
907     if (weak_instance_list.Get(i)->IsCleared()) continue;
908     i::WasmInstanceObject instance =
909         i::WasmInstanceObject::cast(weak_instance_list.Get(i)->GetHeapObject());
910     instance.set_break_on_entry(enabled);
911   }
912 }
913 }  // namespace
914 
915 // static
SetBreakPoint(Handle<Script> script,int * position,Handle<BreakPoint> break_point)916 bool WasmScript::SetBreakPoint(Handle<Script> script, int* position,
917                                Handle<BreakPoint> break_point) {
918   DCHECK_NE(kOnEntryBreakpointPosition, *position);
919 
920   // Find the function for this breakpoint.
921   const wasm::WasmModule* module = script->wasm_native_module()->module();
922   int func_index = GetContainingWasmFunction(module, *position);
923   if (func_index < 0) return false;
924   const wasm::WasmFunction& func = module->functions[func_index];
925   int offset_in_func = *position - func.code.offset();
926 
927   int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(),
928                                                    func_index, offset_in_func);
929   if (breakable_offset == 0) return false;
930   *position = func.code.offset() + breakable_offset;
931 
932   return WasmScript::SetBreakPointForFunction(script, func_index,
933                                               breakable_offset, break_point);
934 }
935 
936 // static
SetInstrumentationBreakpoint(Handle<Script> script,Handle<BreakPoint> break_point)937 void WasmScript::SetInstrumentationBreakpoint(Handle<Script> script,
938                                               Handle<BreakPoint> break_point) {
939   // Special handling for on-entry breakpoints.
940   AddBreakpointToInfo(script, kOnEntryBreakpointPosition, break_point);
941 
942   // Update the "break_on_entry" flag on all live instances.
943   SetBreakOnEntryFlag(*script, true);
944 }
945 
946 // static
SetBreakPointOnFirstBreakableForFunction(Handle<Script> script,int func_index,Handle<BreakPoint> break_point)947 bool WasmScript::SetBreakPointOnFirstBreakableForFunction(
948     Handle<Script> script, int func_index, Handle<BreakPoint> break_point) {
949   if (func_index < 0) return false;
950   int offset_in_func = 0;
951 
952   int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(),
953                                                    func_index, offset_in_func);
954   if (breakable_offset == 0) return false;
955   return WasmScript::SetBreakPointForFunction(script, func_index,
956                                               breakable_offset, break_point);
957 }
958 
959 // static
SetBreakPointForFunction(Handle<Script> script,int func_index,int offset,Handle<BreakPoint> break_point)960 bool WasmScript::SetBreakPointForFunction(Handle<Script> script, int func_index,
961                                           int offset,
962                                           Handle<BreakPoint> break_point) {
963   Isolate* isolate = script->GetIsolate();
964 
965   DCHECK_LE(0, func_index);
966   DCHECK_NE(0, offset);
967 
968   // Find the function for this breakpoint.
969   wasm::NativeModule* native_module = script->wasm_native_module();
970   const wasm::WasmModule* module = native_module->module();
971   const wasm::WasmFunction& func = module->functions[func_index];
972 
973   // Insert new break point into {wasm_breakpoint_infos} of the script.
974   AddBreakpointToInfo(script, func.code.offset() + offset, break_point);
975 
976   native_module->GetDebugInfo()->SetBreakpoint(func_index, offset, isolate);
977 
978   return true;
979 }
980 
981 namespace {
982 
GetBreakpointPos(Isolate * isolate,Object break_point_info_or_undef)983 int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) {
984   if (break_point_info_or_undef.IsUndefined(isolate)) return kMaxInt;
985   return BreakPointInfo::cast(break_point_info_or_undef).source_position();
986 }
987 
FindBreakpointInfoInsertPos(Isolate * isolate,Handle<FixedArray> breakpoint_infos,int position)988 int FindBreakpointInfoInsertPos(Isolate* isolate,
989                                 Handle<FixedArray> breakpoint_infos,
990                                 int position) {
991   // Find insert location via binary search, taking care of undefined values on
992   // the right. {position} is either {kOnEntryBreakpointPosition} (which is -1),
993   // or positive.
994   DCHECK(position == WasmScript::kOnEntryBreakpointPosition || position > 0);
995 
996   int left = 0;                            // inclusive
997   int right = breakpoint_infos->length();  // exclusive
998   while (right - left > 1) {
999     int mid = left + (right - left) / 2;
1000     Object mid_obj = breakpoint_infos->get(mid);
1001     if (GetBreakpointPos(isolate, mid_obj) <= position) {
1002       left = mid;
1003     } else {
1004       right = mid;
1005     }
1006   }
1007 
1008   int left_pos = GetBreakpointPos(isolate, breakpoint_infos->get(left));
1009   return left_pos < position ? left + 1 : left;
1010 }
1011 
1012 }  // namespace
1013 
1014 // static
ClearBreakPoint(Handle<Script> script,int position,Handle<BreakPoint> break_point)1015 bool WasmScript::ClearBreakPoint(Handle<Script> script, int position,
1016                                  Handle<BreakPoint> break_point) {
1017   if (!script->has_wasm_breakpoint_infos()) return false;
1018 
1019   Isolate* isolate = script->GetIsolate();
1020   Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1021 
1022   int pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1023 
1024   // Does a BreakPointInfo object already exist for this position?
1025   if (pos == breakpoint_infos->length()) return false;
1026 
1027   Handle<BreakPointInfo> info(BreakPointInfo::cast(breakpoint_infos->get(pos)),
1028                               isolate);
1029   BreakPointInfo::ClearBreakPoint(isolate, info, break_point);
1030 
1031   // Check if there are no more breakpoints at this location.
1032   if (info->GetBreakPointCount(isolate) == 0) {
1033     // Update array by moving breakpoints up one position.
1034     for (int i = pos; i < breakpoint_infos->length() - 1; i++) {
1035       Object entry = breakpoint_infos->get(i + 1);
1036       breakpoint_infos->set(i, entry);
1037       if (entry.IsUndefined(isolate)) break;
1038     }
1039     // Make sure last array element is empty as a result.
1040     breakpoint_infos->set_undefined(breakpoint_infos->length() - 1);
1041   }
1042 
1043   if (break_point->id() == v8::internal::Debug::kInstrumentationId) {
1044     // Special handling for instrumentation breakpoints.
1045     SetBreakOnEntryFlag(*script, false);
1046   } else {
1047     // Remove the breakpoint from DebugInfo and recompile.
1048     wasm::NativeModule* native_module = script->wasm_native_module();
1049     const wasm::WasmModule* module = native_module->module();
1050     int func_index = GetContainingWasmFunction(module, position);
1051     native_module->GetDebugInfo()->RemoveBreakpoint(func_index, position,
1052                                                     isolate);
1053   }
1054 
1055   return true;
1056 }
1057 
1058 // static
ClearBreakPointById(Handle<Script> script,int breakpoint_id)1059 bool WasmScript::ClearBreakPointById(Handle<Script> script, int breakpoint_id) {
1060   if (!script->has_wasm_breakpoint_infos()) {
1061     return false;
1062   }
1063   Isolate* isolate = script->GetIsolate();
1064   Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1065   // If the array exists, it should not be empty.
1066   DCHECK_LT(0, breakpoint_infos->length());
1067 
1068   for (int i = 0, e = breakpoint_infos->length(); i < e; ++i) {
1069     Handle<Object> obj(breakpoint_infos->get(i), isolate);
1070     if (obj->IsUndefined(isolate)) {
1071       continue;
1072     }
1073     Handle<BreakPointInfo> breakpoint_info = Handle<BreakPointInfo>::cast(obj);
1074     Handle<BreakPoint> breakpoint;
1075     if (BreakPointInfo::GetBreakPointById(isolate, breakpoint_info,
1076                                           breakpoint_id)
1077             .ToHandle(&breakpoint)) {
1078       DCHECK(breakpoint->id() == breakpoint_id);
1079       return WasmScript::ClearBreakPoint(
1080           script, breakpoint_info->source_position(), breakpoint);
1081     }
1082   }
1083   return false;
1084 }
1085 
1086 // static
ClearAllBreakpoints(Script script)1087 void WasmScript::ClearAllBreakpoints(Script script) {
1088   script.set_wasm_breakpoint_infos(
1089       ReadOnlyRoots(script.GetIsolate()).empty_fixed_array());
1090   SetBreakOnEntryFlag(script, false);
1091 }
1092 
1093 // static
AddBreakpointToInfo(Handle<Script> script,int position,Handle<BreakPoint> break_point)1094 void WasmScript::AddBreakpointToInfo(Handle<Script> script, int position,
1095                                      Handle<BreakPoint> break_point) {
1096   Isolate* isolate = script->GetIsolate();
1097   Handle<FixedArray> breakpoint_infos;
1098   if (script->has_wasm_breakpoint_infos()) {
1099     breakpoint_infos = handle(script->wasm_breakpoint_infos(), isolate);
1100   } else {
1101     breakpoint_infos =
1102         isolate->factory()->NewFixedArray(4, AllocationType::kOld);
1103     script->set_wasm_breakpoint_infos(*breakpoint_infos);
1104   }
1105 
1106   int insert_pos =
1107       FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1108 
1109   // If a BreakPointInfo object already exists for this position, add the new
1110   // breakpoint object and return.
1111   if (insert_pos < breakpoint_infos->length() &&
1112       GetBreakpointPos(isolate, breakpoint_infos->get(insert_pos)) ==
1113           position) {
1114     Handle<BreakPointInfo> old_info(
1115         BreakPointInfo::cast(breakpoint_infos->get(insert_pos)), isolate);
1116     BreakPointInfo::SetBreakPoint(isolate, old_info, break_point);
1117     return;
1118   }
1119 
1120   // Enlarge break positions array if necessary.
1121   bool need_realloc = !breakpoint_infos->get(breakpoint_infos->length() - 1)
1122                            .IsUndefined(isolate);
1123   Handle<FixedArray> new_breakpoint_infos = breakpoint_infos;
1124   if (need_realloc) {
1125     new_breakpoint_infos = isolate->factory()->NewFixedArray(
1126         2 * breakpoint_infos->length(), AllocationType::kOld);
1127     script->set_wasm_breakpoint_infos(*new_breakpoint_infos);
1128     // Copy over the entries [0, insert_pos).
1129     for (int i = 0; i < insert_pos; ++i)
1130       new_breakpoint_infos->set(i, breakpoint_infos->get(i));
1131   }
1132 
1133   // Move elements [insert_pos, ...] up by one.
1134   for (int i = breakpoint_infos->length() - 1; i >= insert_pos; --i) {
1135     Object entry = breakpoint_infos->get(i);
1136     if (entry.IsUndefined(isolate)) continue;
1137     new_breakpoint_infos->set(i + 1, entry);
1138   }
1139 
1140   // Generate new BreakpointInfo.
1141   Handle<BreakPointInfo> breakpoint_info =
1142       isolate->factory()->NewBreakPointInfo(position);
1143   BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point);
1144 
1145   // Now insert new position at insert_pos.
1146   new_breakpoint_infos->set(insert_pos, *breakpoint_info);
1147 }
1148 
1149 // static
GetPossibleBreakpoints(wasm::NativeModule * native_module,const v8::debug::Location & start,const v8::debug::Location & end,std::vector<v8::debug::BreakLocation> * locations)1150 bool WasmScript::GetPossibleBreakpoints(
1151     wasm::NativeModule* native_module, const v8::debug::Location& start,
1152     const v8::debug::Location& end,
1153     std::vector<v8::debug::BreakLocation>* locations) {
1154   DisallowGarbageCollection no_gc;
1155 
1156   const wasm::WasmModule* module = native_module->module();
1157   const std::vector<wasm::WasmFunction>& functions = module->functions;
1158 
1159   if (start.GetLineNumber() != 0 || start.GetColumnNumber() < 0 ||
1160       (!end.IsEmpty() &&
1161        (end.GetLineNumber() != 0 || end.GetColumnNumber() < 0 ||
1162         end.GetColumnNumber() < start.GetColumnNumber())))
1163     return false;
1164 
1165   // start_func_index, start_offset and end_func_index is inclusive.
1166   // end_offset is exclusive.
1167   // start_offset and end_offset are module-relative byte offsets.
1168   // We set strict to false because offsets may be between functions.
1169   int start_func_index =
1170       GetNearestWasmFunction(module, start.GetColumnNumber());
1171   if (start_func_index < 0) return false;
1172   uint32_t start_offset = start.GetColumnNumber();
1173   int end_func_index;
1174   uint32_t end_offset;
1175 
1176   if (end.IsEmpty()) {
1177     // Default: everything till the end of the Script.
1178     end_func_index = static_cast<uint32_t>(functions.size() - 1);
1179     end_offset = functions[end_func_index].code.end_offset();
1180   } else {
1181     // If end is specified: Use it and check for valid input.
1182     end_offset = end.GetColumnNumber();
1183     end_func_index = GetNearestWasmFunction(module, end_offset);
1184     DCHECK_GE(end_func_index, start_func_index);
1185   }
1186 
1187   if (start_func_index == end_func_index &&
1188       start_offset > functions[end_func_index].code.end_offset())
1189     return false;
1190   AccountingAllocator alloc;
1191   Zone tmp(&alloc, ZONE_NAME);
1192   const byte* module_start = native_module->wire_bytes().begin();
1193 
1194   for (int func_idx = start_func_index; func_idx <= end_func_index;
1195        ++func_idx) {
1196     const wasm::WasmFunction& func = functions[func_idx];
1197     if (func.code.length() == 0) continue;
1198 
1199     wasm::BodyLocalDecls locals(&tmp);
1200     wasm::BytecodeIterator iterator(module_start + func.code.offset(),
1201                                     module_start + func.code.end_offset(),
1202                                     &locals);
1203     DCHECK_LT(0u, locals.encoded_size);
1204     for (; iterator.has_next(); iterator.next()) {
1205       uint32_t total_offset = func.code.offset() + iterator.pc_offset();
1206       if (total_offset >= end_offset) {
1207         DCHECK_EQ(end_func_index, func_idx);
1208         break;
1209       }
1210       if (total_offset < start_offset) continue;
1211       if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue;
1212       locations->emplace_back(0, total_offset, debug::kCommonBreakLocation);
1213     }
1214   }
1215   return true;
1216 }
1217 
1218 namespace {
1219 
CheckBreakPoint(Isolate * isolate,Handle<BreakPoint> break_point,StackFrameId frame_id)1220 bool CheckBreakPoint(Isolate* isolate, Handle<BreakPoint> break_point,
1221                      StackFrameId frame_id) {
1222   if (break_point->condition().length() == 0) return true;
1223 
1224   HandleScope scope(isolate);
1225   Handle<String> condition(break_point->condition(), isolate);
1226   Handle<Object> result;
1227   // The Wasm engine doesn't perform any sort of inlining.
1228   const int inlined_jsframe_index = 0;
1229   const bool throw_on_side_effect = false;
1230   if (!DebugEvaluate::Local(isolate, frame_id, inlined_jsframe_index, condition,
1231                             throw_on_side_effect)
1232            .ToHandle(&result)) {
1233     isolate->clear_pending_exception();
1234     return false;
1235   }
1236   return result->BooleanValue(isolate);
1237 }
1238 
1239 }  // namespace
1240 
1241 // static
CheckBreakPoints(Isolate * isolate,Handle<Script> script,int position,StackFrameId frame_id)1242 MaybeHandle<FixedArray> WasmScript::CheckBreakPoints(Isolate* isolate,
1243                                                      Handle<Script> script,
1244                                                      int position,
1245                                                      StackFrameId frame_id) {
1246   if (!script->has_wasm_breakpoint_infos()) return {};
1247 
1248   Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate);
1249   int insert_pos =
1250       FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position);
1251   if (insert_pos >= breakpoint_infos->length()) return {};
1252 
1253   Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos),
1254                                        isolate);
1255   if (maybe_breakpoint_info->IsUndefined(isolate)) return {};
1256   Handle<BreakPointInfo> breakpoint_info =
1257       Handle<BreakPointInfo>::cast(maybe_breakpoint_info);
1258   if (breakpoint_info->source_position() != position) return {};
1259 
1260   Handle<Object> break_points(breakpoint_info->break_points(), isolate);
1261   if (!break_points->IsFixedArray()) {
1262     if (!CheckBreakPoint(isolate, Handle<BreakPoint>::cast(break_points),
1263                          frame_id)) {
1264       return {};
1265     }
1266     Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1);
1267     break_points_hit->set(0, *break_points);
1268     return break_points_hit;
1269   }
1270 
1271   Handle<FixedArray> array = Handle<FixedArray>::cast(break_points);
1272   Handle<FixedArray> break_points_hit =
1273       isolate->factory()->NewFixedArray(array->length());
1274   int break_points_hit_count = 0;
1275   for (int i = 0; i < array->length(); ++i) {
1276     Handle<BreakPoint> break_point(BreakPoint::cast(array->get(i)), isolate);
1277     if (CheckBreakPoint(isolate, break_point, frame_id)) {
1278       break_points_hit->set(break_points_hit_count++, *break_point);
1279     }
1280   }
1281   if (break_points_hit_count == 0) return {};
1282   break_points_hit->Shrink(isolate, break_points_hit_count);
1283   return break_points_hit;
1284 }
1285 
1286 }  // namespace internal
1287 }  // namespace v8
1288