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