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 32namespace v8 { 33namespace internal { 34namespace wasm { 35 36namespace { 37 38using ImportExportKey = std::pair<ImportExportKindCode, uint32_t>; 39 40enum ReturnLocation { kAfterBreakpoint, kAfterWasmCall }; 41 42Address 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 88void 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 95void 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 115class DebugInfoImpl { 116 public: 117 explicit DebugInfoImpl(NativeModule* native_module) 118 : native_module_(native_module) {} 119 120 DebugInfoImpl(const DebugInfoImpl&) = delete; 121 DebugInfoImpl& operator=(const DebugInfoImpl&) = delete; 122 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 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 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 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 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 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 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 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 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 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. 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. 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 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 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 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 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 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 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 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 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 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 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 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 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 490 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 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 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 { 532 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 544 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 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. 584 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. 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 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 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 785DebugInfo::DebugInfo(NativeModule* native_module) 786 : impl_(std::make_unique<DebugInfoImpl>(native_module)) {} 787 788DebugInfo::~DebugInfo() = default; 789 790int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); } 791 792WasmValue 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 797int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); } 798 799WasmValue 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 804const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) { 805 return impl_->GetFunctionAtAddress(pc); 806} 807 808WireBytesRef DebugInfo::GetExportName(ImportExportKindCode code, 809 uint32_t index) { 810 return impl_->GetExportName(code, index); 811} 812 813std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName( 814 ImportExportKindCode code, uint32_t index) { 815 return impl_->GetImportName(code, index); 816} 817 818WireBytesRef DebugInfo::GetTypeName(int type_index) { 819 return impl_->GetTypeName(type_index); 820} 821 822WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) { 823 return impl_->GetLocalName(func_index, local_index); 824} 825 826WireBytesRef DebugInfo::GetFieldName(int struct_index, int field_index) { 827 return impl_->GetFieldName(struct_index, field_index); 828} 829 830void DebugInfo::SetBreakpoint(int func_index, int offset, 831 Isolate* current_isolate) { 832 impl_->SetBreakpoint(func_index, offset, current_isolate); 833} 834 835bool DebugInfo::PrepareStep(WasmFrame* frame) { 836 return impl_->PrepareStep(frame); 837} 838 839void DebugInfo::PrepareStepOutTo(WasmFrame* frame) { 840 impl_->PrepareStepOutTo(frame); 841} 842 843void DebugInfo::ClearStepping(Isolate* isolate) { 844 impl_->ClearStepping(isolate); 845} 846 847void DebugInfo::ClearStepping(WasmFrame* frame) { impl_->ClearStepping(frame); } 848 849bool DebugInfo::IsStepping(WasmFrame* frame) { 850 return impl_->IsStepping(frame); 851} 852 853void DebugInfo::RemoveBreakpoint(int func_index, int offset, 854 Isolate* current_isolate) { 855 impl_->RemoveBreakpoint(func_index, offset, current_isolate); 856} 857 858void DebugInfo::RemoveDebugSideTables(base::Vector<WasmCode* const> code) { 859 impl_->RemoveDebugSideTables(code); 860} 861 862DebugSideTable* DebugInfo::GetDebugSideTableIfExists( 863 const WasmCode* code) const { 864 return impl_->GetDebugSideTableIfExists(code); 865} 866 867void DebugInfo::RemoveIsolate(Isolate* isolate) { 868 return impl_->RemoveIsolate(isolate); 869} 870 871} // namespace wasm 872 873namespace { 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. 879int 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 900void 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 916bool 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 937void 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 947bool 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 960bool 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 981namespace { 982 983int 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 988int 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 1015bool 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 1059bool 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 1087void 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 1094void 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 1150bool 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 1218namespace { 1219 1220bool 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 1242MaybeHandle<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