Bug Summary

File:out/../deps/v8/src/wasm/wasm-debug.cc
Warning:line 589, column 9
Called C++ object pointer is null

Annotated Source Code

Press '?' to see keyboard shortcuts

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