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