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