1 // Copyright 2020 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/debug/wasm/gdb-server/wasm-module-debug.h"
6
7 #include "src/api/api-inl.h"
8 #include "src/api/api.h"
9 #include "src/base/platform/wrappers.h"
10 #include "src/execution/frames-inl.h"
11 #include "src/execution/frames.h"
12 #include "src/objects/script.h"
13 #include "src/wasm/module-instantiate.h"
14 #include "src/wasm/wasm-debug.h"
15 #include "src/wasm/wasm-value.h"
16
17 namespace v8 {
18 namespace internal {
19 namespace wasm {
20 namespace gdb_server {
21
WasmModuleDebug(v8::Isolate * isolate,Local<debug::WasmScript> wasm_script)22 WasmModuleDebug::WasmModuleDebug(v8::Isolate* isolate,
23 Local<debug::WasmScript> wasm_script) {
24 DCHECK_EQ(Script::TYPE_WASM, Utils::OpenHandle(*wasm_script)->type());
25
26 isolate_ = isolate;
27 wasm_script_ = Global<debug::WasmScript>(isolate, wasm_script);
28 }
29
GetModuleName() const30 std::string WasmModuleDebug::GetModuleName() const {
31 v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
32 v8::Local<v8::String> name;
33 std::string module_name;
34 if (wasm_script->Name().ToLocal(&name)) {
35 module_name = *(v8::String::Utf8Value(isolate_, name));
36 }
37 return module_name;
38 }
39
GetFirstWasmInstance()40 Handle<WasmInstanceObject> WasmModuleDebug::GetFirstWasmInstance() {
41 v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
42 Handle<Script> script = Utils::OpenHandle(*wasm_script);
43
44 Handle<WeakArrayList> weak_instance_list(script->wasm_weak_instance_list(),
45 GetIsolate());
46 if (weak_instance_list->length() > 0) {
47 MaybeObject maybe_instance = weak_instance_list->Get(0);
48 if (maybe_instance->IsWeak()) {
49 Handle<WasmInstanceObject> instance(
50 WasmInstanceObject::cast(maybe_instance->GetHeapObjectAssumeWeak()),
51 GetIsolate());
52 return instance;
53 }
54 }
55 return Handle<WasmInstanceObject>::null();
56 }
57
GetLEB128Size(base::Vector<const uint8_t> module_bytes,int offset)58 int GetLEB128Size(base::Vector<const uint8_t> module_bytes, int offset) {
59 int index = offset;
60 while (module_bytes[index] & 0x80) index++;
61 return index + 1 - offset;
62 }
63
ReturnPc(const NativeModule * native_module,int pc)64 int ReturnPc(const NativeModule* native_module, int pc) {
65 base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
66 uint8_t opcode = wire_bytes[pc];
67 switch (opcode) {
68 case kExprCallFunction: {
69 // skip opcode
70 pc++;
71 // skip function index
72 return pc + GetLEB128Size(wire_bytes, pc);
73 }
74 case kExprCallIndirect: {
75 // skip opcode
76 pc++;
77 // skip signature index
78 pc += GetLEB128Size(wire_bytes, pc);
79 // skip table index
80 return pc + GetLEB128Size(wire_bytes, pc);
81 }
82 default:
83 UNREACHABLE();
84 }
85 }
86
87 // static
GetCallStack(uint32_t debug_context_id,Isolate * isolate)88 std::vector<wasm_addr_t> WasmModuleDebug::GetCallStack(
89 uint32_t debug_context_id, Isolate* isolate) {
90 std::vector<wasm_addr_t> call_stack;
91 for (StackFrameIterator frame_it(isolate); !frame_it.done();
92 frame_it.Advance()) {
93 StackFrame* const frame = frame_it.frame();
94 switch (frame->type()) {
95 case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
96 case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
97 case StackFrame::OPTIMIZED:
98 case StackFrame::INTERPRETED:
99 case StackFrame::BASELINE:
100 case StackFrame::BUILTIN:
101 case StackFrame::WASM: {
102 // A standard frame may include many summarized frames, due to inlining.
103 std::vector<FrameSummary> frames;
104 CommonFrame::cast(frame)->Summarize(&frames);
105 for (size_t i = frames.size(); i-- != 0;) {
106 int offset = 0;
107 Handle<Script> script;
108
109 auto& summary = frames[i];
110 if (summary.IsJavaScript()) {
111 FrameSummary::JavaScriptFrameSummary const& java_script =
112 summary.AsJavaScript();
113 offset = java_script.code_offset();
114 script = Handle<Script>::cast(java_script.script());
115 } else if (summary.IsWasm()) {
116 FrameSummary::WasmFrameSummary const& wasm = summary.AsWasm();
117 offset = GetWasmFunctionOffset(wasm.wasm_instance()->module(),
118 wasm.function_index()) +
119 wasm.byte_offset();
120 script = wasm.script();
121
122 bool zeroth_frame = call_stack.empty();
123 if (!zeroth_frame) {
124 const NativeModule* native_module =
125 wasm.wasm_instance()->module_object().native_module();
126 offset = ReturnPc(native_module, offset);
127 }
128 }
129
130 if (offset > 0) {
131 call_stack.push_back(
132 {debug_context_id << 16 | script->id(), uint32_t(offset)});
133 }
134 }
135 break;
136 }
137
138 case StackFrame::BUILTIN_EXIT:
139 default:
140 // ignore the frame.
141 break;
142 }
143 }
144 if (call_stack.empty()) call_stack.push_back({1, 0});
145 return call_stack;
146 }
147
148 // static
FindWasmFrame(StackTraceFrameIterator * frame_it,uint32_t * frame_index)149 std::vector<FrameSummary> WasmModuleDebug::FindWasmFrame(
150 StackTraceFrameIterator* frame_it, uint32_t* frame_index) {
151 while (!frame_it->done()) {
152 StackFrame* const frame = frame_it->frame();
153 switch (frame->type()) {
154 case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION:
155 case StackFrame::JAVA_SCRIPT_BUILTIN_CONTINUATION_WITH_CATCH:
156 case StackFrame::OPTIMIZED:
157 case StackFrame::INTERPRETED:
158 case StackFrame::BASELINE:
159 case StackFrame::BUILTIN:
160 case StackFrame::WASM: {
161 // A standard frame may include many summarized frames, due to inlining.
162 std::vector<FrameSummary> frames;
163 CommonFrame::cast(frame)->Summarize(&frames);
164 const size_t frame_count = frames.size();
165 DCHECK_GT(frame_count, 0);
166
167 if (frame_count > *frame_index) {
168 if (frame_it->is_wasm())
169 return frames;
170 else
171 return {};
172 } else {
173 *frame_index -= frame_count;
174 frame_it->Advance();
175 }
176 break;
177 }
178
179 case StackFrame::BUILTIN_EXIT:
180 default:
181 // ignore the frame.
182 break;
183 }
184 }
185 return {};
186 }
187
188 // static
GetWasmInstance(Isolate * isolate,uint32_t frame_index)189 Handle<WasmInstanceObject> WasmModuleDebug::GetWasmInstance(
190 Isolate* isolate, uint32_t frame_index) {
191 StackTraceFrameIterator frame_it(isolate);
192 std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
193 if (frames.empty()) {
194 return Handle<WasmInstanceObject>::null();
195 }
196
197 int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
198 const FrameSummary::WasmFrameSummary& summary =
199 frames[reversed_index].AsWasm();
200 return summary.wasm_instance();
201 }
202
203 // static
GetWasmGlobal(Isolate * isolate,uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)204 bool WasmModuleDebug::GetWasmGlobal(Isolate* isolate, uint32_t frame_index,
205 uint32_t index, uint8_t* buffer,
206 uint32_t buffer_size, uint32_t* size) {
207 HandleScope handles(isolate);
208
209 Handle<WasmInstanceObject> instance = GetWasmInstance(isolate, frame_index);
210 if (!instance.is_null()) {
211 Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
212 const wasm::WasmModule* module = module_object->module();
213 if (index < module->globals.size()) {
214 wasm::WasmValue wasm_value =
215 WasmInstanceObject::GetGlobalValue(instance, module->globals[index]);
216 return GetWasmValue(wasm_value, buffer, buffer_size, size);
217 }
218 }
219 return false;
220 }
221
222 // static
GetWasmLocal(Isolate * isolate,uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)223 bool WasmModuleDebug::GetWasmLocal(Isolate* isolate, uint32_t frame_index,
224 uint32_t index, uint8_t* buffer,
225 uint32_t buffer_size, uint32_t* size) {
226 HandleScope handles(isolate);
227
228 StackTraceFrameIterator frame_it(isolate);
229 std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
230 if (frames.empty()) {
231 return false;
232 }
233
234 int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
235 const FrameSummary& summary = frames[reversed_index];
236 if (summary.IsWasm()) {
237 Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
238 if (!instance.is_null()) {
239 Handle<WasmModuleObject> module_object(instance->module_object(),
240 isolate);
241 wasm::NativeModule* native_module = module_object->native_module();
242 DebugInfo* debug_info = native_module->GetDebugInfo();
243 if (static_cast<uint32_t>(
244 debug_info->GetNumLocals(frame_it.frame()->pc())) > index) {
245 wasm::WasmValue wasm_value = debug_info->GetLocalValue(
246 index, frame_it.frame()->pc(), frame_it.frame()->fp(),
247 frame_it.frame()->callee_fp());
248 return GetWasmValue(wasm_value, buffer, buffer_size, size);
249 }
250 }
251 }
252 return false;
253 }
254
255 // static
GetWasmStackValue(Isolate * isolate,uint32_t frame_index,uint32_t index,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)256 bool WasmModuleDebug::GetWasmStackValue(Isolate* isolate, uint32_t frame_index,
257 uint32_t index, uint8_t* buffer,
258 uint32_t buffer_size, uint32_t* size) {
259 HandleScope handles(isolate);
260
261 StackTraceFrameIterator frame_it(isolate);
262 std::vector<FrameSummary> frames = FindWasmFrame(&frame_it, &frame_index);
263 if (frames.empty()) {
264 return false;
265 }
266
267 int reversed_index = static_cast<int>(frames.size() - 1 - frame_index);
268 const FrameSummary& summary = frames[reversed_index];
269 if (summary.IsWasm()) {
270 Handle<WasmInstanceObject> instance = summary.AsWasm().wasm_instance();
271 if (!instance.is_null()) {
272 Handle<WasmModuleObject> module_object(instance->module_object(),
273 isolate);
274 wasm::NativeModule* native_module = module_object->native_module();
275 DebugInfo* debug_info = native_module->GetDebugInfo();
276 if (static_cast<uint32_t>(
277 debug_info->GetStackDepth(frame_it.frame()->pc())) > index) {
278 WasmValue wasm_value = debug_info->GetStackValue(
279 index, frame_it.frame()->pc(), frame_it.frame()->fp(),
280 frame_it.frame()->callee_fp());
281 return GetWasmValue(wasm_value, buffer, buffer_size, size);
282 }
283 }
284 }
285 return false;
286 }
287
GetWasmMemory(Isolate * isolate,uint32_t offset,uint8_t * buffer,uint32_t size)288 uint32_t WasmModuleDebug::GetWasmMemory(Isolate* isolate, uint32_t offset,
289 uint8_t* buffer, uint32_t size) {
290 HandleScope handles(isolate);
291
292 uint32_t bytes_read = 0;
293 Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
294 if (!instance.is_null()) {
295 uint8_t* mem_start = instance->memory_start();
296 size_t mem_size = instance->memory_size();
297 if (static_cast<uint64_t>(offset) + size <= mem_size) {
298 memcpy(buffer, mem_start + offset, size);
299 bytes_read = size;
300 } else if (offset < mem_size) {
301 bytes_read = static_cast<uint32_t>(mem_size) - offset;
302 memcpy(buffer, mem_start + offset, bytes_read);
303 }
304 }
305 return bytes_read;
306 }
307
GetWasmData(Isolate * isolate,uint32_t offset,uint8_t * buffer,uint32_t size)308 uint32_t WasmModuleDebug::GetWasmData(Isolate* isolate, uint32_t offset,
309 uint8_t* buffer, uint32_t size) {
310 HandleScope handles(isolate);
311
312 uint32_t bytes_read = 0;
313 Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
314 if (!instance.is_null()) {
315 Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
316 const wasm::WasmModule* module = module_object->module();
317 if (!module->data_segments.empty()) {
318 const WasmDataSegment& segment = module->data_segments[0];
319 uint32_t data_offset = EvalUint32InitExpr(instance, segment.dest_addr);
320 offset += data_offset;
321
322 uint8_t* mem_start = instance->memory_start();
323 size_t mem_size = instance->memory_size();
324 if (static_cast<uint64_t>(offset) + size <= mem_size) {
325 memcpy(buffer, mem_start + offset, size);
326 bytes_read = size;
327 } else if (offset < mem_size) {
328 bytes_read = static_cast<uint32_t>(mem_size) - offset;
329 memcpy(buffer, mem_start + offset, bytes_read);
330 }
331 }
332 }
333 return bytes_read;
334 }
335
GetWasmModuleBytes(wasm_addr_t wasm_addr,uint8_t * buffer,uint32_t size)336 uint32_t WasmModuleDebug::GetWasmModuleBytes(wasm_addr_t wasm_addr,
337 uint8_t* buffer, uint32_t size) {
338 uint32_t bytes_read = 0;
339 // Any instance will work.
340 Handle<WasmInstanceObject> instance = GetFirstWasmInstance();
341 if (!instance.is_null()) {
342 Handle<WasmModuleObject> module_object(instance->module_object(),
343 GetIsolate());
344 wasm::NativeModule* native_module = module_object->native_module();
345 const wasm::ModuleWireBytes wire_bytes(native_module->wire_bytes());
346 uint32_t offset = wasm_addr.Offset();
347 if (offset < wire_bytes.length()) {
348 uint32_t module_size = static_cast<uint32_t>(wire_bytes.length());
349 bytes_read = module_size - offset >= size ? size : module_size - offset;
350 memcpy(buffer, wire_bytes.start() + offset, bytes_read);
351 }
352 }
353 return bytes_read;
354 }
355
AddBreakpoint(uint32_t offset,int * breakpoint_id)356 bool WasmModuleDebug::AddBreakpoint(uint32_t offset, int* breakpoint_id) {
357 v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
358 Handle<Script> script = Utils::OpenHandle(*wasm_script);
359 Handle<String> condition = GetIsolate()->factory()->empty_string();
360 int breakpoint_address = static_cast<int>(offset);
361 return GetIsolate()->debug()->SetBreakPointForScript(
362 script, condition, &breakpoint_address, breakpoint_id);
363 }
364
RemoveBreakpoint(uint32_t offset,int breakpoint_id)365 void WasmModuleDebug::RemoveBreakpoint(uint32_t offset, int breakpoint_id) {
366 v8::Local<debug::WasmScript> wasm_script = wasm_script_.Get(isolate_);
367 Handle<Script> script = Utils::OpenHandle(*wasm_script);
368 GetIsolate()->debug()->RemoveBreakpointForWasmScript(script, breakpoint_id);
369 }
370
PrepareStep()371 void WasmModuleDebug::PrepareStep() {
372 i::Isolate* isolate = GetIsolate();
373 DebugScope debug_scope(isolate->debug());
374 debug::PrepareStep(reinterpret_cast<v8::Isolate*>(isolate),
375 debug::StepAction::StepInto);
376 }
377
378 template <typename T>
StoreValue(const T & value,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)379 bool StoreValue(const T& value, uint8_t* buffer, uint32_t buffer_size,
380 uint32_t* size) {
381 *size = sizeof(value);
382 if (*size > buffer_size) return false;
383 memcpy(buffer, &value, *size);
384 return true;
385 }
386
387 // static
GetWasmValue(const wasm::WasmValue & wasm_value,uint8_t * buffer,uint32_t buffer_size,uint32_t * size)388 bool WasmModuleDebug::GetWasmValue(const wasm::WasmValue& wasm_value,
389 uint8_t* buffer, uint32_t buffer_size,
390 uint32_t* size) {
391 switch (wasm_value.type().kind()) {
392 case wasm::kI32:
393 return StoreValue(wasm_value.to_i32(), buffer, buffer_size, size);
394 case wasm::kI64:
395 return StoreValue(wasm_value.to_i64(), buffer, buffer_size, size);
396 case wasm::kF32:
397 return StoreValue(wasm_value.to_f32(), buffer, buffer_size, size);
398 case wasm::kF64:
399 return StoreValue(wasm_value.to_f64(), buffer, buffer_size, size);
400 case wasm::kS128:
401 return StoreValue(wasm_value.to_s128(), buffer, buffer_size, size);
402 case wasm::kRef:
403 case wasm::kOptRef:
404 case wasm::kRtt:
405 case wasm::kVoid:
406 case wasm::kBottom:
407 // Not supported
408 return false;
409 }
410 }
411
412 } // namespace gdb_server
413 } // namespace wasm
414 } // namespace internal
415 } // namespace v8
416