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); |
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); |
| 2 | | Calling constructor for 'FrameInspectionScope' | |
|
| 6 | | Returning from constructor for 'FrameInspectionScope' | |
|
147 | int num_locals = scope.debug_side_table->num_locals(); |
| 7 | | Called C++ object pointer is null |
|
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); |
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); |
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); |
| 1 | Calling 'DebugInfoImpl::GetStackValue' | |
|
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 | } |