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
17namespace v8 {
18namespace internal {
19namespace wasm {
20namespace gdb_server {
21
22WasmModuleDebug::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
30std::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
40Handle<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
58int 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
64int 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
88std::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
149std::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
189Handle<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
204bool 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
223bool 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
256bool 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
288uint32_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
308uint32_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
336uint32_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
356bool 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
365void 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
371void 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
378template <typename T>
379bool 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
388bool 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