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/wasm/wasm-debug-evaluate.h"
6 
7 #include <algorithm>
8 #include <limits>
9 
10 #include "src/api/api-inl.h"
11 #include "src/codegen/machine-type.h"
12 #include "src/compiler/wasm-compiler.h"
13 #include "src/execution/frames-inl.h"
14 #include "src/wasm/value-type.h"
15 #include "src/wasm/wasm-arguments.h"
16 #include "src/wasm/wasm-constants.h"
17 #include "src/wasm/wasm-debug.h"
18 #include "src/wasm/wasm-module.h"
19 #include "src/wasm/wasm-objects.h"
20 #include "src/wasm/wasm-result.h"
21 #include "src/wasm/wasm-value.h"
22 
23 namespace v8 {
24 namespace internal {
25 namespace wasm {
26 namespace {
27 
V8String(Isolate * isolate,const char * str)28 static Handle<String> V8String(Isolate* isolate, const char* str) {
29   return isolate->factory()->NewStringFromAsciiChecked(str);
30 }
31 
CheckSignature(ValueType return_type,std::initializer_list<ValueType> argument_types,const FunctionSig * sig,ErrorThrower * thrower)32 static bool CheckSignature(ValueType return_type,
33                            std::initializer_list<ValueType> argument_types,
34                            const FunctionSig* sig, ErrorThrower* thrower) {
35   if (sig->return_count() != 1 && return_type != kWasmBottom) {
36     thrower->CompileError("Invalid return type. Got none, expected %s",
37                           return_type.name().c_str());
38     return false;
39   }
40 
41   if (sig->return_count() == 1) {
42     if (sig->GetReturn(0) != return_type) {
43       thrower->CompileError("Invalid return type. Got %s, expected %s",
44                             sig->GetReturn(0).name().c_str(),
45                             return_type.name().c_str());
46       return false;
47     }
48   }
49 
50   if (sig->parameter_count() != argument_types.size()) {
51     thrower->CompileError("Invalid number of arguments. Expected %zu, got %zu",
52                           sig->parameter_count(), argument_types.size());
53     return false;
54   }
55   size_t p = 0;
56   for (ValueType argument_type : argument_types) {
57     if (sig->GetParam(p) != argument_type) {
58       thrower->CompileError(
59           "Invalid argument type for argument %zu. Got %s, expected %s", p,
60           sig->GetParam(p).name().c_str(), argument_type.name().c_str());
61       return false;
62     }
63     ++p;
64   }
65   return true;
66 }
67 
CheckRangeOutOfBounds(uint32_t offset,uint32_t size,size_t allocation_size,wasm::ErrorThrower * thrower)68 static bool CheckRangeOutOfBounds(uint32_t offset, uint32_t size,
69                                   size_t allocation_size,
70                                   wasm::ErrorThrower* thrower) {
71   if (size > std::numeric_limits<uint32_t>::max() - offset) {
72     thrower->RuntimeError("Overflowing memory range\n");
73     return true;
74   }
75   if (offset + size > allocation_size) {
76     thrower->RuntimeError("Illegal access to out-of-bounds memory");
77     return true;
78   }
79   return false;
80 }
81 
82 class DebugEvaluatorProxy {
83  public:
DebugEvaluatorProxy(Isolate * isolate,CommonFrame * frame)84   explicit DebugEvaluatorProxy(Isolate* isolate, CommonFrame* frame)
85       : isolate_(isolate), frame_(frame) {}
86 
GetMemoryTrampoline(const v8::FunctionCallbackInfo<v8::Value> & args)87   static void GetMemoryTrampoline(
88       const v8::FunctionCallbackInfo<v8::Value>& args) {
89     DebugEvaluatorProxy& proxy = GetProxy(args);
90 
91     uint32_t offset = proxy.GetArgAsUInt32(args, 0);
92     uint32_t size = proxy.GetArgAsUInt32(args, 1);
93     uint32_t result = proxy.GetArgAsUInt32(args, 2);
94 
95     proxy.GetMemory(offset, size, result);
96   }
97 
98   // void __getMemory(uint32_t offset, uint32_t size, void* result);
GetMemory(uint32_t offset,uint32_t size,uint32_t result)99   void GetMemory(uint32_t offset, uint32_t size, uint32_t result) {
100     wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy");
101     // Check all overflows.
102     if (CheckRangeOutOfBounds(offset, size, debuggee_->memory_size(),
103                               &thrower) ||
104         CheckRangeOutOfBounds(result, size, evaluator_->memory_size(),
105                               &thrower)) {
106       return;
107     }
108 
109     std::memcpy(&evaluator_->memory_start()[result],
110                 &debuggee_->memory_start()[offset], size);
111   }
112 
113   // void* __sbrk(intptr_t increment);
Sbrk(uint32_t increment)114   uint32_t Sbrk(uint32_t increment) {
115     if (increment > 0 && evaluator_->memory_size() <=
116                              std::numeric_limits<uint32_t>::max() - increment) {
117       Handle<WasmMemoryObject> memory(evaluator_->memory_object(), isolate_);
118       uint32_t new_pages =
119           (increment - 1 + wasm::kWasmPageSize) / wasm::kWasmPageSize;
120       WasmMemoryObject::Grow(isolate_, memory, new_pages);
121     }
122     return static_cast<uint32_t>(evaluator_->memory_size());
123   }
124 
SbrkTrampoline(const v8::FunctionCallbackInfo<v8::Value> & args)125   static void SbrkTrampoline(const v8::FunctionCallbackInfo<v8::Value>& args) {
126     DebugEvaluatorProxy& proxy = GetProxy(args);
127     uint32_t size = proxy.GetArgAsUInt32(args, 0);
128 
129     uint32_t result = proxy.Sbrk(size);
130     args.GetReturnValue().Set(result);
131   }
132 
133   // void __getLocal(uint32_t local,  void* result);
GetLocal(uint32_t local,uint32_t result_offset)134   void GetLocal(uint32_t local, uint32_t result_offset) {
135     DCHECK(frame_->is_wasm());
136     wasm::DebugInfo* debug_info =
137         WasmFrame::cast(frame_)->native_module()->GetDebugInfo();
138     WasmValue result = debug_info->GetLocalValue(
139         local, frame_->pc(), frame_->fp(), frame_->callee_fp());
140     WriteResult(result, result_offset);
141   }
142 
GetGlobal(uint32_t global,uint32_t result_offset)143   void GetGlobal(uint32_t global, uint32_t result_offset) {
144     DCHECK(frame_->is_wasm());
145 
146     const WasmGlobal& global_variable =
147         WasmFrame::cast(frame_)->native_module()->module()->globals.at(global);
148 
149     Handle<WasmInstanceObject> instance(
150         WasmFrame::cast(frame_)->wasm_instance(), isolate_);
151     WasmValue result =
152         WasmInstanceObject::GetGlobalValue(instance, global_variable);
153     WriteResult(result, result_offset);
154   }
155 
GetOperand(uint32_t operand,uint32_t result_offset)156   void GetOperand(uint32_t operand, uint32_t result_offset) {
157     DCHECK(frame_->is_wasm());
158     wasm::DebugInfo* debug_info =
159         WasmFrame::cast(frame_)->native_module()->GetDebugInfo();
160     WasmValue result = debug_info->GetStackValue(
161         operand, frame_->pc(), frame_->fp(), frame_->callee_fp());
162 
163     WriteResult(result, result_offset);
164   }
165 
GetLocalTrampoline(const v8::FunctionCallbackInfo<v8::Value> & args)166   static void GetLocalTrampoline(
167       const v8::FunctionCallbackInfo<v8::Value>& args) {
168     DebugEvaluatorProxy& proxy = GetProxy(args);
169     uint32_t local = proxy.GetArgAsUInt32(args, 0);
170     uint32_t result = proxy.GetArgAsUInt32(args, 1);
171 
172     proxy.GetLocal(local, result);
173   }
174 
GetGlobalTrampoline(const v8::FunctionCallbackInfo<v8::Value> & args)175   static void GetGlobalTrampoline(
176       const v8::FunctionCallbackInfo<v8::Value>& args) {
177     DebugEvaluatorProxy& proxy = GetProxy(args);
178     uint32_t global = proxy.GetArgAsUInt32(args, 0);
179     uint32_t result = proxy.GetArgAsUInt32(args, 1);
180 
181     proxy.GetGlobal(global, result);
182   }
183 
GetOperandTrampoline(const v8::FunctionCallbackInfo<v8::Value> & args)184   static void GetOperandTrampoline(
185       const v8::FunctionCallbackInfo<v8::Value>& args) {
186     DebugEvaluatorProxy& proxy = GetProxy(args);
187     uint32_t operand = proxy.GetArgAsUInt32(args, 0);
188     uint32_t result = proxy.GetArgAsUInt32(args, 1);
189 
190     proxy.GetOperand(operand, result);
191   }
192 
CreateImports()193   Handle<JSObject> CreateImports() {
194     Handle<JSObject> imports_obj =
195         isolate_->factory()->NewJSObject(isolate_->object_function());
196     Handle<JSObject> import_module_obj =
197         isolate_->factory()->NewJSObject(isolate_->object_function());
198     Object::SetProperty(isolate_, imports_obj, V8String(isolate_, "env"),
199                         import_module_obj)
200         .Assert();
201 
202     AddImport(import_module_obj, "__getOperand",
203               DebugEvaluatorProxy::GetOperandTrampoline);
204     AddImport(import_module_obj, "__getGlobal",
205               DebugEvaluatorProxy::GetGlobalTrampoline);
206     AddImport(import_module_obj, "__getLocal",
207               DebugEvaluatorProxy::GetLocalTrampoline);
208     AddImport(import_module_obj, "__getMemory",
209               DebugEvaluatorProxy::GetMemoryTrampoline);
210     AddImport(import_module_obj, "__sbrk", DebugEvaluatorProxy::SbrkTrampoline);
211 
212     return imports_obj;
213   }
214 
SetInstances(Handle<WasmInstanceObject> evaluator,Handle<WasmInstanceObject> debuggee)215   void SetInstances(Handle<WasmInstanceObject> evaluator,
216                     Handle<WasmInstanceObject> debuggee) {
217     evaluator_ = evaluator;
218     debuggee_ = debuggee;
219   }
220 
221  private:
222   template <typename T>
WriteResultImpl(const WasmValue & result,uint32_t result_offset)223   void WriteResultImpl(const WasmValue& result, uint32_t result_offset) {
224     wasm::ScheduledErrorThrower thrower(isolate_, "debug evaluate proxy");
225     T val = result.to<T>();
226     STATIC_ASSERT(static_cast<uint32_t>(sizeof(T)) == sizeof(T));
227     if (CheckRangeOutOfBounds(result_offset, sizeof(T),
228                               evaluator_->memory_size(), &thrower)) {
229       return;
230     }
231     memcpy(&evaluator_->memory_start()[result_offset], &val, sizeof(T));
232   }
233 
WriteResult(const WasmValue & result,uint32_t result_offset)234   void WriteResult(const WasmValue& result, uint32_t result_offset) {
235     switch (result.type().kind()) {
236       case ValueType::kI32:
237         WriteResultImpl<uint32_t>(result, result_offset);
238         break;
239       case ValueType::kI64:
240         WriteResultImpl<int64_t>(result, result_offset);
241         break;
242       case ValueType::kF32:
243         WriteResultImpl<float>(result, result_offset);
244         break;
245       case ValueType::kF64:
246         WriteResultImpl<double>(result, result_offset);
247         break;
248       default:
249         UNIMPLEMENTED();
250     }
251   }
252 
GetArgAsUInt32(const v8::FunctionCallbackInfo<v8::Value> & args,int index)253   uint32_t GetArgAsUInt32(const v8::FunctionCallbackInfo<v8::Value>& args,
254                           int index) {
255     // No type/range checks needed on his because this is only called for {args}
256     // where we have performed a signature check via {VerifyEvaluatorInterface}
257     double number = Utils::OpenHandle(*args[index])->Number();
258     return static_cast<uint32_t>(number);
259   }
260 
GetProxy(const v8::FunctionCallbackInfo<v8::Value> & args)261   static DebugEvaluatorProxy& GetProxy(
262       const v8::FunctionCallbackInfo<v8::Value>& args) {
263     return *reinterpret_cast<DebugEvaluatorProxy*>(
264         args.Data().As<v8::External>()->Value());
265   }
266 
267   template <typename CallableT>
AddImport(Handle<JSObject> import_module_obj,const char * function_name,CallableT callback)268   void AddImport(Handle<JSObject> import_module_obj, const char* function_name,
269                  CallableT callback) {
270     v8::Isolate* api_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
271     v8::Local<v8::Context> context = api_isolate->GetCurrentContext();
272     std::string data;
273     v8::Local<v8::Function> v8_function =
274         v8::Function::New(context, callback,
275                           v8::External::New(api_isolate, this))
276             .ToLocalChecked();
277 
278     Handle<JSReceiver> wrapped_function = Utils::OpenHandle(*v8_function);
279 
280     Object::SetProperty(isolate_, import_module_obj,
281                         V8String(isolate_, function_name), wrapped_function)
282         .Assert();
283   }
284 
285   Isolate* isolate_;
286   CommonFrame* frame_;
287   Handle<WasmInstanceObject> evaluator_;
288   Handle<WasmInstanceObject> debuggee_;
289 };
290 
VerifyEvaluatorInterface(const WasmModule * raw_module,const ModuleWireBytes & bytes,ErrorThrower * thrower)291 static bool VerifyEvaluatorInterface(const WasmModule* raw_module,
292                                      const ModuleWireBytes& bytes,
293                                      ErrorThrower* thrower) {
294   for (const WasmImport imported : raw_module->import_table) {
295     if (imported.kind != ImportExportKindCode::kExternalFunction) continue;
296     const WasmFunction& F = raw_module->functions.at(imported.index);
297     std::string module_name(bytes.start() + imported.module_name.offset(),
298                             bytes.start() + imported.module_name.end_offset());
299     std::string field_name(bytes.start() + imported.field_name.offset(),
300                            bytes.start() + imported.field_name.end_offset());
301 
302     if (module_name == "env") {
303       if (field_name == "__getMemory") {
304         // void __getMemory(uint32_t offset, uint32_t size, void* result);
305         if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32, kWasmI32}, F.sig,
306                            thrower)) {
307           continue;
308         }
309       } else if (field_name == "__getOperand") {
310         // void __getOperand(uint32_t local,  void* result)
311         if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
312           continue;
313         }
314       } else if (field_name == "__getGlobal") {
315         // void __getGlobal(uint32_t local,  void* result)
316         if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
317           continue;
318         }
319       } else if (field_name == "__getLocal") {
320         // void __getLocal(uint32_t local,  void* result)
321         if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
322           continue;
323         }
324       } else if (field_name == "__debug") {
325         // void __debug(uint32_t flag, uint32_t value)
326         if (CheckSignature(kWasmBottom, {kWasmI32, kWasmI32}, F.sig, thrower)) {
327           continue;
328         }
329       } else if (field_name == "__sbrk") {
330         // uint32_t __sbrk(uint32_t increment)
331         if (CheckSignature(kWasmI32, {kWasmI32}, F.sig, thrower)) {
332           continue;
333         }
334       }
335     }
336 
337     if (!thrower->error()) {
338       thrower->LinkError("Unknown import \"%s\" \"%s\"", module_name.c_str(),
339                          field_name.c_str());
340     }
341 
342     return false;
343   }
344   for (const WasmExport& exported : raw_module->export_table) {
345     if (exported.kind != ImportExportKindCode::kExternalFunction) continue;
346     const WasmFunction& F = raw_module->functions.at(exported.index);
347     std::string field_name(bytes.start() + exported.name.offset(),
348                            bytes.start() + exported.name.end_offset());
349     if (field_name == "wasm_format") {
350       if (!CheckSignature(kWasmI32, {}, F.sig, thrower)) return false;
351     }
352   }
353   return true;
354 }
355 }  // namespace
356 
DebugEvaluateImpl(Vector<const byte> snippet,Handle<WasmInstanceObject> debuggee_instance,CommonFrame * frame)357 Maybe<std::string> DebugEvaluateImpl(
358     Vector<const byte> snippet, Handle<WasmInstanceObject> debuggee_instance,
359     CommonFrame* frame) {
360   Isolate* isolate = debuggee_instance->GetIsolate();
361   HandleScope handle_scope(isolate);
362   WasmEngine* engine = isolate->wasm_engine();
363   wasm::ErrorThrower thrower(isolate, "wasm debug evaluate");
364 
365   // Create module object.
366   wasm::ModuleWireBytes bytes(snippet);
367   wasm::WasmFeatures features = wasm::WasmFeatures::FromIsolate(isolate);
368   Handle<WasmModuleObject> evaluator_module;
369   if (!engine->SyncCompile(isolate, features, &thrower, bytes)
370            .ToHandle(&evaluator_module)) {
371     return Nothing<std::string>();
372   }
373 
374   // Verify interface.
375   const WasmModule* raw_module = evaluator_module->module();
376   if (!VerifyEvaluatorInterface(raw_module, bytes, &thrower)) {
377     return Nothing<std::string>();
378   }
379 
380   // Set up imports.
381   DebugEvaluatorProxy proxy(isolate, frame);
382   Handle<JSObject> imports = proxy.CreateImports();
383 
384   // Instantiate Module.
385   Handle<WasmInstanceObject> evaluator_instance;
386   if (!engine->SyncInstantiate(isolate, &thrower, evaluator_module, imports, {})
387            .ToHandle(&evaluator_instance)) {
388     return Nothing<std::string>();
389   }
390 
391   proxy.SetInstances(evaluator_instance, debuggee_instance);
392 
393   Handle<JSObject> exports_obj(evaluator_instance->exports_object(), isolate);
394   Handle<Object> entry_point_obj;
395   bool get_property_success =
396       Object::GetProperty(isolate, exports_obj,
397                           V8String(isolate, "wasm_format"))
398           .ToHandle(&entry_point_obj);
399   if (!get_property_success ||
400       !WasmExportedFunction::IsWasmExportedFunction(*entry_point_obj)) {
401     thrower.LinkError("Missing export: \"wasm_format\"");
402     return Nothing<std::string>();
403   }
404   Handle<WasmExportedFunction> entry_point =
405       Handle<WasmExportedFunction>::cast(entry_point_obj);
406 
407   // TODO(wasm): Cache this code.
408   Handle<Code> wasm_entry = compiler::CompileCWasmEntry(
409       isolate, entry_point->sig(), debuggee_instance->module());
410 
411   CWasmArgumentsPacker packer(4 /* uint32_t return value, no parameters. */);
412   Execution::CallWasm(isolate, wasm_entry, entry_point->GetWasmCallTarget(),
413                       evaluator_instance, packer.argv());
414   if (isolate->has_pending_exception()) return Nothing<std::string>();
415 
416   uint32_t offset = packer.Pop<uint32_t>();
417   if (CheckRangeOutOfBounds(offset, 0, evaluator_instance->memory_size(),
418                             &thrower)) {
419     return Nothing<std::string>();
420   }
421 
422   // Copy the zero-terminated string result but don't overflow.
423   std::string result;
424   byte* heap = evaluator_instance->memory_start() + offset;
425   for (; offset < evaluator_instance->memory_size(); ++offset, ++heap) {
426     if (*heap == 0) return Just(result);
427     result.push_back(*heap);
428   }
429 
430   thrower.RuntimeError("The evaluation returned an invalid result");
431   return Nothing<std::string>();
432 }
433 
DebugEvaluate(Vector<const byte> snippet,Handle<WasmInstanceObject> debuggee_instance,CommonFrame * frame)434 MaybeHandle<String> DebugEvaluate(Vector<const byte> snippet,
435                                   Handle<WasmInstanceObject> debuggee_instance,
436                                   CommonFrame* frame) {
437   Maybe<std::string> result =
438       DebugEvaluateImpl(snippet, debuggee_instance, frame);
439   if (result.IsNothing()) return {};
440   std::string result_str = result.ToChecked();
441   return V8String(debuggee_instance->GetIsolate(), result_str.c_str());
442 }
443 
444 }  // namespace wasm
445 }  // namespace internal
446 }  // namespace v8
447