blob: bbd75f6b188f11e286725b66505226c1fccbe9d5 [file] [log] [blame]
Kaido Kertf309f9a2021-04-30 12:09:15 -07001// 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"
Kaido Kertf309f9a2021-04-30 12:09:15 -070011#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
23namespace v8 {
24namespace internal {
25namespace wasm {
26namespace {
27
28static Handle<String> V8String(Isolate* isolate, const char* str) {
29 return isolate->factory()->NewStringFromAsciiChecked(str);
30}
31
32static 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
68static 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
82class DebugEvaluatorProxy {
83 public:
84 explicit DebugEvaluatorProxy(Isolate* isolate, CommonFrame* frame)
85 : isolate_(isolate), frame_(frame) {}
86
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);
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);
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
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);
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
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
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
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
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
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
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
215 void SetInstances(Handle<WasmInstanceObject> evaluator,
216 Handle<WasmInstanceObject> debuggee) {
217 evaluator_ = evaluator;
218 debuggee_ = debuggee;
219 }
220
221 private:
222 template <typename T>
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 }
Kaido Kert6f3fc442021-06-25 11:58:59 -0700231 memcpy(&evaluator_->memory_start()[result_offset], &val, sizeof(T));
Kaido Kertf309f9a2021-04-30 12:09:15 -0700232 }
233
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
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
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>
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
291static 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
357Maybe<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
434MaybeHandle<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