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