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