clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name wasm-debug.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -ffunction-sections -fdata-sections -fcoverage-compilation-dir=/home/maurizio/node-v18.6.0/out -resource-dir /usr/local/lib/clang/16.0.0 -D _GLIBCXX_USE_CXX11_ABI=1 -D NODE_OPENSSL_CONF_NAME=nodejs_conf -D NODE_OPENSSL_HAS_QUIC -D V8_GYP_BUILD -D V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP=64 -D __STDC_FORMAT_MACROS -D OPENSSL_NO_PINSHARED -D OPENSSL_THREADS -D V8_TARGET_ARCH_X64 -D V8_HAVE_TARGET_OS -D V8_TARGET_OS_LINUX -D V8_EMBEDDER_STRING="-node.8" -D ENABLE_DISASSEMBLER -D V8_PROMISE_INTERNAL_FIELD_COUNT=1 -D V8_SHORT_BUILTIN_CALLS -D OBJECT_PRINT -D V8_INTL_SUPPORT -D V8_ATOMIC_OBJECT_FIELD_WRITES -D V8_ENABLE_LAZY_SOURCE_POSITIONS -D V8_USE_SIPHASH -D V8_SHARED_RO_HEAP -D V8_WIN64_UNWINDING_INFO -D V8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH -D V8_SNAPSHOT_COMPRESSION -D V8_ENABLE_WEBASSEMBLY -D V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS -D V8_ALLOCATION_FOLDING -D V8_ALLOCATION_SITE_TRACKING -D V8_SCRIPTORMODULE_LEGACY_LIFETIME -D V8_ADVANCED_BIGINT_ALGORITHMS -D ICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_STATIC -D UCONFIG_NO_SERVICE=1 -D U_ENABLE_DYLOAD=0 -D U_STATIC_IMPLEMENTATION=1 -D U_HAVE_STD_STRING=1 -D UCONFIG_NO_BREAK_ITERATION=0 -I ../deps/v8 -I ../deps/v8/include -I /home/maurizio/node-v18.6.0/out/Release/obj/gen/inspector-generated-output-root -I ../deps/v8/third_party/inspector_protocol -I /home/maurizio/node-v18.6.0/out/Release/obj/gen -I /home/maurizio/node-v18.6.0/out/Release/obj/gen/generate-bytecode-output-root -I ../deps/icu-small/source/i18n -I ../deps/icu-small/source/common -I ../deps/v8/third_party/zlib -I ../deps/v8/third_party/zlib/google -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8 -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/x86_64-redhat-linux -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/backward -internal-isystem /usr/local/lib/clang/16.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-unused-parameter -Wno-return-type -std=gnu++17 -fdeprecated-macro -fdebug-compilation-dir=/home/maurizio/node-v18.6.0/out -ferror-limit 19 -fno-rtti -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-08-22-142216-507842-1 -x c++ ../deps/v8/src/wasm/wasm-debug.cc
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | #include "src/wasm/wasm-debug.h" |
| 6 | |
| 7 | #include <iomanip> |
| 8 | #include <unordered_map> |
| 9 | |
| 10 | #include "src/base/optional.h" |
| 11 | #include "src/base/platform/wrappers.h" |
| 12 | #include "src/codegen/assembler-inl.h" |
| 13 | #include "src/common/assert-scope.h" |
| 14 | #include "src/compiler/wasm-compiler.h" |
| 15 | #include "src/debug/debug-evaluate.h" |
| 16 | #include "src/execution/frames-inl.h" |
| 17 | #include "src/heap/factory.h" |
| 18 | #include "src/wasm/baseline/liftoff-compiler.h" |
| 19 | #include "src/wasm/baseline/liftoff-register.h" |
| 20 | #include "src/wasm/module-decoder.h" |
| 21 | #include "src/wasm/value-type.h" |
| 22 | #include "src/wasm/wasm-code-manager.h" |
| 23 | #include "src/wasm/wasm-engine.h" |
| 24 | #include "src/wasm/wasm-limits.h" |
| 25 | #include "src/wasm/wasm-module.h" |
| 26 | #include "src/wasm/wasm-objects-inl.h" |
| 27 | #include "src/wasm/wasm-opcodes-inl.h" |
| 28 | #include "src/wasm/wasm-subtyping.h" |
| 29 | #include "src/wasm/wasm-value.h" |
| 30 | #include "src/zone/accounting-allocator.h" |
| 31 | |
| 32 | namespace v8 { |
| 33 | namespace internal { |
| 34 | namespace wasm { |
| 35 | |
| 36 | namespace { |
| 37 | |
| 38 | using ImportExportKey = std::pair<ImportExportKindCode, uint32_t>; |
| 39 | |
| 40 | enum ReturnLocation { kAfterBreakpoint, kAfterWasmCall }; |
| 41 | |
| 42 | Address FindNewPC(WasmFrame* frame, WasmCode* wasm_code, int byte_offset, |
| 43 | ReturnLocation return_location) { |
| 44 | base::Vector<const uint8_t> new_pos_table = wasm_code->source_positions(); |
| 45 | |
| 46 | DCHECK_LE(0, byte_offset); |
| 47 | |
| 48 | |
| 49 | |
| 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 | |
| 63 | |
| 64 | |
| 65 | |
| 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 | } |
| 87 | |
| 88 | void DebugSideTable::Print(std::ostream& os) const { |
| 89 | os << "Debug side table (" << num_locals_ << " locals, " << entries_.size() |
| 90 | << " entries):\n"; |
| 91 | for (auto& entry : entries_) entry.Print(os); |
| 92 | os << "\n"; |
| 93 | } |
| 94 | |
| 95 | void DebugSideTable::Entry::Print(std::ostream& os) const { |
| 96 | os << std::setw(6) << std::hex << pc_offset_ << std::dec << " stack height " |
| 97 | << stack_height_ << " ["; |
| 98 | for (auto& value : changed_values_) { |
| 99 | os << " " << value.type.name() << ":"; |
| 100 | switch (value.storage) { |
| 101 | case kConstant: |
| 102 | os << "const#" << value.i32_const; |
| 103 | break; |
| 104 | case kRegister: |
| 105 | os << "reg#" << value.reg_code; |
| 106 | break; |
| 107 | case kStack: |
| 108 | os << "stack#" << value.stack_offset; |
| 109 | break; |
| 110 | } |
| 111 | } |
| 112 | os << " ]\n"; |
| 113 | } |
| 114 | |
| 115 | class DebugInfoImpl { |
| 116 | public: |
| 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); |
| 2 | | Calling constructor for 'FrameInspectionScope' | |
|
| 6 | | Returning from constructor for 'FrameInspectionScope' | |
|
| 132 | return GetValue(scope.debug_side_table, scope.debug_side_table_entry, local, |
| 7 | | Passing null pointer value via 1st parameter 'debug_side_table' | |
|
| 8 | | Calling 'DebugInfoImpl::GetValue' | |
|
| 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 | |
| 221 | |
| 222 | |
| 223 | |
| 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 | |
| 235 | |
| 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()); |
| 249 | |
| 250 | ForDebugging for_debugging = offsets.size() == 1 && offsets[0] == 0 |
| 251 | ? kForStepping |
| 252 | : kWithBreakpoints; |
| 253 | |
| 254 | |
| 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 | |
| 262 | for (; it != begin; --it) std::iter_swap(it, it - 1); |
| 263 | if (for_debugging == kWithBreakpoints) { |
| 264 | |
| 265 | native_module_->ReinstallDebugCode(it->code); |
| 266 | } |
| 267 | return it->code; |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | |
| 272 | |
| 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 | |
| 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 | |
| 291 | |
| 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 | |
| 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 | |
| 311 | new_code->IncRef(); |
| 312 | |
| 313 | if (cached_debugging_code_.size() > kMaxCachedDebuggingCode) { |
| 314 | |
| 315 | |
| 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 | |
| 327 | |
| 328 | WasmCodeRefScope wasm_code_ref_scope; |
| 329 | |
| 330 | |
| 331 | |
| 332 | base::MutexGuard guard(&mutex_); |
| 333 | |
| 334 | |
| 335 | DCHECK_NE(0, offset); |
| 336 | |
| 337 | |
| 338 | |
| 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 | |
| 348 | return; |
| 349 | } |
| 350 | breakpoints.insert(insertion_point, offset); |
| 351 | |
| 352 | DCHECK(std::is_sorted(all_breakpoints.begin(), all_breakpoints.end())); |
| 353 | |
| 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 | |
| 359 | |
| 360 | |
| 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()); |
| 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()); |
| 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 | |
| 396 | constexpr int kFloodingBreakpoints[] = {0}; |
| 397 | DCHECK(frame->wasm_code()->is_liftoff()); |
| 398 | |
| 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; |
| 411 | if (IsAtReturn(frame)) return false; |
| 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; |
| 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 | |
| 453 | |
| 454 | WasmCodeRefScope wasm_code_ref_scope; |
| 455 | |
| 456 | |
| 457 | |
| 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 | |
| 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 | |
| 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 | |
| 510 | |
| 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() |
| |
| 4 | | Null pointer value stored to 'scope.debug_side_table' | |
|
| 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 | |
| 557 | |
| 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 | |
| 564 | std::unique_ptr<DebugSideTable> debug_side_table = |
| 565 | GenerateLiftoffDebugSideTable(code); |
| 566 | DebugSideTable* ret = debug_side_table.get(); |
| 567 | |
| 568 | |
| 569 | |
| 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 | |
| 578 | code->MaybePrint(); |
| 579 | return ret; |
| 580 | } |
| 581 | |
| 582 | |
| 583 | |
| 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); |
| 9 | | Called C++ object pointer is null |
|
| 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 | |
| 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 | |
| 641 | UNREACHABLE(); |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | |
| 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 | |
| 674 | |
| 675 | |
| 676 | void UpdateReturnAddresses(Isolate* isolate, WasmCode* new_code, |
| 677 | StackFrameId stepping_frame) { |
| 678 | |
| 679 | |
| 680 | ReturnLocation return_location = kAfterBreakpoint; |
| 681 | for (StackTraceFrameIterator it(isolate); !it.done(); |
| 682 | it.Advance(), return_location = kAfterWasmCall) { |
| 683 | |
| 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 | |
| 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 | |
| 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 | |
| 731 | |
| 732 | struct PerIsolateDebugData { |
| 733 | |
| 734 | |
| 735 | std::unordered_map<int, std::vector<int>> breakpoints_per_function; |
| 736 | |
| 737 | |
| 738 | |
| 739 | StackFrameId stepping_frame = NO_ID; |
| 740 | }; |
| 741 | |
| 742 | NativeModule* const native_module_; |
| 743 | |
| 744 | mutable base::Mutex debug_side_tables_mutex_; |
| 745 | |
| 746 | |
| 747 | std::unordered_map<const WasmCode*, std::unique_ptr<DebugSideTable>> |
| 748 | debug_side_tables_; |
| 749 | |
| 750 | |
| 751 | mutable base::Mutex mutex_; |
| 752 | |
| 753 | |
| 754 | |
| 755 | |
| 756 | |
| 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 | |
| 767 | std::unique_ptr<std::map<ImportExportKey, wasm::WireBytesRef>> export_names_; |
| 768 | |
| 769 | |
| 770 | std::unique_ptr<std::map<ImportExportKey, |
| 771 | std::pair<wasm::WireBytesRef, wasm::WireBytesRef>>> |
| 772 | import_names_; |
| 773 | |
| 774 | |
| 775 | std::unique_ptr<NameMap> type_names_; |
| 776 | |
| 777 | std::unique_ptr<IndirectNameMap> local_names_; |
| 778 | |
| 779 | std::unique_ptr<IndirectNameMap> field_names_; |
| 780 | |
| 781 | |
| 782 | std::unordered_map<Isolate*, PerIsolateDebugData> per_isolate_data_; |
| 783 | }; |
| 784 | |
| 785 | DebugInfo::DebugInfo(NativeModule* native_module) |
| 786 | : impl_(std::make_unique<DebugInfoImpl>(native_module)) {} |
| 787 | |
| 788 | DebugInfo::~DebugInfo() = default; |
| 789 | |
| 790 | int DebugInfo::GetNumLocals(Address pc) { return impl_->GetNumLocals(pc); } |
| 791 | |
| 792 | WasmValue DebugInfo::GetLocalValue(int local, Address pc, Address fp, |
| 793 | Address debug_break_fp, Isolate* isolate) { |
| 794 | return impl_->GetLocalValue(local, pc, fp, debug_break_fp, isolate); |
| 1 | Calling 'DebugInfoImpl::GetLocalValue' | |
|
| 795 | } |
| 796 | |
| 797 | int DebugInfo::GetStackDepth(Address pc) { return impl_->GetStackDepth(pc); } |
| 798 | |
| 799 | WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp, |
| 800 | Address debug_break_fp, Isolate* isolate) { |
| 801 | return impl_->GetStackValue(index, pc, fp, debug_break_fp, isolate); |
| 802 | } |
| 803 | |
| 804 | const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) { |
| 805 | return impl_->GetFunctionAtAddress(pc); |
| 806 | } |
| 807 | |
| 808 | WireBytesRef DebugInfo::GetExportName(ImportExportKindCode code, |
| 809 | uint32_t index) { |
| 810 | return impl_->GetExportName(code, index); |
| 811 | } |
| 812 | |
| 813 | std::pair<WireBytesRef, WireBytesRef> DebugInfo::GetImportName( |
| 814 | ImportExportKindCode code, uint32_t index) { |
| 815 | return impl_->GetImportName(code, index); |
| 816 | } |
| 817 | |
| 818 | WireBytesRef DebugInfo::GetTypeName(int type_index) { |
| 819 | return impl_->GetTypeName(type_index); |
| 820 | } |
| 821 | |
| 822 | WireBytesRef DebugInfo::GetLocalName(int func_index, int local_index) { |
| 823 | return impl_->GetLocalName(func_index, local_index); |
| 824 | } |
| 825 | |
| 826 | WireBytesRef DebugInfo::GetFieldName(int struct_index, int field_index) { |
| 827 | return impl_->GetFieldName(struct_index, field_index); |
| 828 | } |
| 829 | |
| 830 | void DebugInfo::SetBreakpoint(int func_index, int offset, |
| 831 | Isolate* current_isolate) { |
| 832 | impl_->SetBreakpoint(func_index, offset, current_isolate); |
| 833 | } |
| 834 | |
| 835 | bool DebugInfo::PrepareStep(WasmFrame* frame) { |
| 836 | return impl_->PrepareStep(frame); |
| 837 | } |
| 838 | |
| 839 | void DebugInfo::PrepareStepOutTo(WasmFrame* frame) { |
| 840 | impl_->PrepareStepOutTo(frame); |
| 841 | } |
| 842 | |
| 843 | void DebugInfo::ClearStepping(Isolate* isolate) { |
| 844 | impl_->ClearStepping(isolate); |
| 845 | } |
| 846 | |
| 847 | void DebugInfo::ClearStepping(WasmFrame* frame) { impl_->ClearStepping(frame); } |
| 848 | |
| 849 | bool DebugInfo::IsStepping(WasmFrame* frame) { |
| 850 | return impl_->IsStepping(frame); |
| 851 | } |
| 852 | |
| 853 | void DebugInfo::RemoveBreakpoint(int func_index, int offset, |
| 854 | Isolate* current_isolate) { |
| 855 | impl_->RemoveBreakpoint(func_index, offset, current_isolate); |
| 856 | } |
| 857 | |
| 858 | void DebugInfo::RemoveDebugSideTables(base::Vector<WasmCode* const> code) { |
| 859 | impl_->RemoveDebugSideTables(code); |
| 860 | } |
| 861 | |
| 862 | DebugSideTable* DebugInfo::GetDebugSideTableIfExists( |
| 863 | const WasmCode* code) const { |
| 864 | return impl_->GetDebugSideTableIfExists(code); |
| 865 | } |
| 866 | |
| 867 | void DebugInfo::RemoveIsolate(Isolate* isolate) { |
| 868 | return impl_->RemoveIsolate(isolate); |
| 869 | } |
| 870 | |
| 871 | } |
| 872 | |
| 873 | namespace { |
| 874 | |
| 875 | |
| 876 | |
| 877 | |
| 878 | |
| 879 | int FindNextBreakablePosition(wasm::NativeModule* native_module, int func_index, |
| 880 | int offset_in_func) { |
| 881 | AccountingAllocator alloc; |
| 882 | Zone tmp(&alloc, ZONE_NAME); |
| 883 | wasm::BodyLocalDecls locals(&tmp); |
| 884 | const byte* module_start = native_module->wire_bytes().begin(); |
| 885 | const wasm::WasmFunction& func = |
| 886 | native_module->module()->functions[func_index]; |
| 887 | wasm::BytecodeIterator iterator(module_start + func.code.offset(), |
| 888 | module_start + func.code.end_offset(), |
| 889 | &locals); |
| 890 | DCHECK_LT(0, locals.encoded_size); |
| 891 | if (offset_in_func < 0) return 0; |
| 892 | for (; iterator.has_next(); iterator.next()) { |
| 893 | if (iterator.pc_offset() < static_cast<uint32_t>(offset_in_func)) continue; |
| 894 | if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue; |
| 895 | return static_cast<int>(iterator.pc_offset()); |
| 896 | } |
| 897 | return 0; |
| 898 | } |
| 899 | |
| 900 | void SetBreakOnEntryFlag(Script script, bool enabled) { |
| 901 | if (script.break_on_entry() == enabled) return; |
| 902 | |
| 903 | script.set_break_on_entry(enabled); |
| 904 | |
| 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 | } |
| 914 | |
| 915 | |
| 916 | bool WasmScript::SetBreakPoint(Handle<Script> script, int* position, |
| 917 | Handle<BreakPoint> break_point) { |
| 918 | DCHECK_NE(kOnEntryBreakpointPosition, *position); |
| 919 | |
| 920 | |
| 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 | |
| 937 | void WasmScript::SetInstrumentationBreakpoint(Handle<Script> script, |
| 938 | Handle<BreakPoint> break_point) { |
| 939 | |
| 940 | AddBreakpointToInfo(script, kOnEntryBreakpointPosition, break_point); |
| 941 | |
| 942 | |
| 943 | SetBreakOnEntryFlag(*script, true); |
| 944 | } |
| 945 | |
| 946 | |
| 947 | bool WasmScript::SetBreakPointOnFirstBreakableForFunction( |
| 948 | Handle<Script> script, int func_index, Handle<BreakPoint> break_point) { |
| 949 | if (func_index < 0) return false; |
| 950 | int offset_in_func = 0; |
| 951 | |
| 952 | int breakable_offset = FindNextBreakablePosition(script->wasm_native_module(), |
| 953 | func_index, offset_in_func); |
| 954 | if (breakable_offset == 0) return false; |
| 955 | return WasmScript::SetBreakPointForFunction(script, func_index, |
| 956 | breakable_offset, break_point); |
| 957 | } |
| 958 | |
| 959 | |
| 960 | bool WasmScript::SetBreakPointForFunction(Handle<Script> script, int func_index, |
| 961 | int offset, |
| 962 | Handle<BreakPoint> break_point) { |
| 963 | Isolate* isolate = script->GetIsolate(); |
| 964 | |
| 965 | DCHECK_LE(0, func_index); |
| 966 | DCHECK_NE(0, offset); |
| 967 | |
| 968 | |
| 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 | |
| 974 | AddBreakpointToInfo(script, func.code.offset() + offset, break_point); |
| 975 | |
| 976 | native_module->GetDebugInfo()->SetBreakpoint(func_index, offset, isolate); |
| 977 | |
| 978 | return true; |
| 979 | } |
| 980 | |
| 981 | namespace { |
| 982 | |
| 983 | int GetBreakpointPos(Isolate* isolate, Object break_point_info_or_undef) { |
| 984 | if (break_point_info_or_undef.IsUndefined(isolate)) return kMaxInt; |
| 985 | return BreakPointInfo::cast(break_point_info_or_undef).source_position(); |
| 986 | } |
| 987 | |
| 988 | int FindBreakpointInfoInsertPos(Isolate* isolate, |
| 989 | Handle<FixedArray> breakpoint_infos, |
| 990 | int position) { |
| 991 | |
| 992 | |
| 993 | |
| 994 | DCHECK(position == WasmScript::kOnEntryBreakpointPosition || position > 0); |
| 995 | |
| 996 | int left = 0; |
| 997 | int right = breakpoint_infos->length(); |
| 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 | } |
| 1013 | |
| 1014 | |
| 1015 | bool WasmScript::ClearBreakPoint(Handle<Script> script, int position, |
| 1016 | Handle<BreakPoint> break_point) { |
| 1017 | if (!script->has_wasm_breakpoint_infos()) return false; |
| 1018 | |
| 1019 | Isolate* isolate = script->GetIsolate(); |
| 1020 | Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); |
| 1021 | |
| 1022 | int pos = FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); |
| 1023 | |
| 1024 | |
| 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 | |
| 1032 | if (info->GetBreakPointCount(isolate) == 0) { |
| 1033 | |
| 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 | |
| 1040 | breakpoint_infos->set_undefined(breakpoint_infos->length() - 1); |
| 1041 | } |
| 1042 | |
| 1043 | if (break_point->id() == v8::internal::Debug::kInstrumentationId) { |
| 1044 | |
| 1045 | SetBreakOnEntryFlag(*script, false); |
| 1046 | } else { |
| 1047 | |
| 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 | |
| 1059 | bool WasmScript::ClearBreakPointById(Handle<Script> script, int breakpoint_id) { |
| 1060 | if (!script->has_wasm_breakpoint_infos()) { |
| 1061 | return false; |
| 1062 | } |
| 1063 | Isolate* isolate = script->GetIsolate(); |
| 1064 | Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); |
| 1065 | |
| 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 | |
| 1087 | void WasmScript::ClearAllBreakpoints(Script script) { |
| 1088 | script.set_wasm_breakpoint_infos( |
| 1089 | ReadOnlyRoots(script.GetIsolate()).empty_fixed_array()); |
| 1090 | SetBreakOnEntryFlag(script, false); |
| 1091 | } |
| 1092 | |
| 1093 | |
| 1094 | void WasmScript::AddBreakpointToInfo(Handle<Script> script, int position, |
| 1095 | Handle<BreakPoint> break_point) { |
| 1096 | Isolate* isolate = script->GetIsolate(); |
| 1097 | Handle<FixedArray> breakpoint_infos; |
| 1098 | if (script->has_wasm_breakpoint_infos()) { |
| 1099 | breakpoint_infos = handle(script->wasm_breakpoint_infos(), isolate); |
| 1100 | } else { |
| 1101 | breakpoint_infos = |
| 1102 | isolate->factory()->NewFixedArray(4, AllocationType::kOld); |
| 1103 | script->set_wasm_breakpoint_infos(*breakpoint_infos); |
| 1104 | } |
| 1105 | |
| 1106 | int insert_pos = |
| 1107 | FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); |
| 1108 | |
| 1109 | |
| 1110 | |
| 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 | |
| 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 | |
| 1129 | for (int i = 0; i < insert_pos; ++i) |
| 1130 | new_breakpoint_infos->set(i, breakpoint_infos->get(i)); |
| 1131 | } |
| 1132 | |
| 1133 | |
| 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 | |
| 1141 | Handle<BreakPointInfo> breakpoint_info = |
| 1142 | isolate->factory()->NewBreakPointInfo(position); |
| 1143 | BreakPointInfo::SetBreakPoint(isolate, breakpoint_info, break_point); |
| 1144 | |
| 1145 | |
| 1146 | new_breakpoint_infos->set(insert_pos, *breakpoint_info); |
| 1147 | } |
| 1148 | |
| 1149 | |
| 1150 | bool WasmScript::GetPossibleBreakpoints( |
| 1151 | wasm::NativeModule* native_module, const v8::debug::Location& start, |
| 1152 | const v8::debug::Location& end, |
| 1153 | std::vector<v8::debug::BreakLocation>* locations) { |
| 1154 | DisallowGarbageCollection no_gc; |
| 1155 | |
| 1156 | const wasm::WasmModule* module = native_module->module(); |
| 1157 | const std::vector<wasm::WasmFunction>& functions = module->functions; |
| 1158 | |
| 1159 | if (start.GetLineNumber() != 0 || start.GetColumnNumber() < 0 || |
| 1160 | (!end.IsEmpty() && |
| 1161 | (end.GetLineNumber() != 0 || end.GetColumnNumber() < 0 || |
| 1162 | end.GetColumnNumber() < start.GetColumnNumber()))) |
| 1163 | return false; |
| 1164 | |
| 1165 | |
| 1166 | |
| 1167 | |
| 1168 | |
| 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 | |
| 1178 | end_func_index = static_cast<uint32_t>(functions.size() - 1); |
| 1179 | end_offset = functions[end_func_index].code.end_offset(); |
| 1180 | } else { |
| 1181 | |
| 1182 | end_offset = end.GetColumnNumber(); |
| 1183 | end_func_index = GetNearestWasmFunction(module, end_offset); |
| 1184 | DCHECK_GE(end_func_index, start_func_index); |
| 1185 | } |
| 1186 | |
| 1187 | if (start_func_index == end_func_index && |
| 1188 | start_offset > functions[end_func_index].code.end_offset()) |
| 1189 | return false; |
| 1190 | AccountingAllocator alloc; |
| 1191 | Zone tmp(&alloc, ZONE_NAME); |
| 1192 | const byte* module_start = native_module->wire_bytes().begin(); |
| 1193 | |
| 1194 | for (int func_idx = start_func_index; func_idx <= end_func_index; |
| 1195 | ++func_idx) { |
| 1196 | const wasm::WasmFunction& func = functions[func_idx]; |
| 1197 | if (func.code.length() == 0) continue; |
| 1198 | |
| 1199 | wasm::BodyLocalDecls locals(&tmp); |
| 1200 | wasm::BytecodeIterator iterator(module_start + func.code.offset(), |
| 1201 | module_start + func.code.end_offset(), |
| 1202 | &locals); |
| 1203 | DCHECK_LT(0u, locals.encoded_size); |
| 1204 | for (; iterator.has_next(); iterator.next()) { |
| 1205 | uint32_t total_offset = func.code.offset() + iterator.pc_offset(); |
| 1206 | if (total_offset >= end_offset) { |
| 1207 | DCHECK_EQ(end_func_index, func_idx); |
| 1208 | break; |
| 1209 | } |
| 1210 | if (total_offset < start_offset) continue; |
| 1211 | if (!wasm::WasmOpcodes::IsBreakable(iterator.current())) continue; |
| 1212 | locations->emplace_back(0, total_offset, debug::kCommonBreakLocation); |
| 1213 | } |
| 1214 | } |
| 1215 | return true; |
| 1216 | } |
| 1217 | |
| 1218 | namespace { |
| 1219 | |
| 1220 | bool CheckBreakPoint(Isolate* isolate, Handle<BreakPoint> break_point, |
| 1221 | StackFrameId frame_id) { |
| 1222 | if (break_point->condition().length() == 0) return true; |
| 1223 | |
| 1224 | HandleScope scope(isolate); |
| 1225 | Handle<String> condition(break_point->condition(), isolate); |
| 1226 | Handle<Object> result; |
| 1227 | |
| 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 | } |
| 1240 | |
| 1241 | |
| 1242 | MaybeHandle<FixedArray> WasmScript::CheckBreakPoints(Isolate* isolate, |
| 1243 | Handle<Script> script, |
| 1244 | int position, |
| 1245 | StackFrameId frame_id) { |
| 1246 | if (!script->has_wasm_breakpoint_infos()) return {}; |
| 1247 | |
| 1248 | Handle<FixedArray> breakpoint_infos(script->wasm_breakpoint_infos(), isolate); |
| 1249 | int insert_pos = |
| 1250 | FindBreakpointInfoInsertPos(isolate, breakpoint_infos, position); |
| 1251 | if (insert_pos >= breakpoint_infos->length()) return {}; |
| 1252 | |
| 1253 | Handle<Object> maybe_breakpoint_info(breakpoint_infos->get(insert_pos), |
| 1254 | isolate); |
| 1255 | if (maybe_breakpoint_info->IsUndefined(isolate)) return {}; |
| 1256 | Handle<BreakPointInfo> breakpoint_info = |
| 1257 | Handle<BreakPointInfo>::cast(maybe_breakpoint_info); |
| 1258 | if (breakpoint_info->source_position() != position) return {}; |
| 1259 | |
| 1260 | Handle<Object> break_points(breakpoint_info->break_points(), isolate); |
| 1261 | if (!break_points->IsFixedArray()) { |
| 1262 | if (!CheckBreakPoint(isolate, Handle<BreakPoint>::cast(break_points), |
| 1263 | frame_id)) { |
| 1264 | return {}; |
| 1265 | } |
| 1266 | Handle<FixedArray> break_points_hit = isolate->factory()->NewFixedArray(1); |
| 1267 | break_points_hit->set(0, *break_points); |
| 1268 | return break_points_hit; |
| 1269 | } |
| 1270 | |
| 1271 | Handle<FixedArray> array = Handle<FixedArray>::cast(break_points); |
| 1272 | Handle<FixedArray> break_points_hit = |
| 1273 | isolate->factory()->NewFixedArray(array->length()); |
| 1274 | int break_points_hit_count = 0; |
| 1275 | for (int i = 0; i < array->length(); ++i) { |
| 1276 | Handle<BreakPoint> break_point(BreakPoint::cast(array->get(i)), isolate); |
| 1277 | if (CheckBreakPoint(isolate, break_point, frame_id)) { |
| 1278 | break_points_hit->set(break_points_hit_count++, *break_point); |
| 1279 | } |
| 1280 | } |
| 1281 | if (break_points_hit_count == 0) return {}; |
| 1282 | break_points_hit->Shrink(isolate, break_points_hit_count); |
| 1283 | return break_points_hit; |
| 1284 | } |
| 1285 | |
| 1286 | } |
| 1287 | } |