1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 *
4 * Copyright 2016 Mozilla Foundation
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "wasm/WasmDebug.h"
20
21 #include "mozilla/BinarySearch.h"
22
23 #include "debugger/Debugger.h"
24 #include "ds/Sort.h"
25 #include "jit/ExecutableAllocator.h"
26 #include "jit/MacroAssembler.h"
27 #include "wasm/WasmInstance.h"
28 #include "wasm/WasmStubs.h"
29 #include "wasm/WasmValidate.h"
30
31 #include "gc/FreeOp-inl.h"
32
33 using namespace js;
34 using namespace js::jit;
35 using namespace js::wasm;
36
37 using mozilla::BinarySearchIf;
38
DebugState(const Code & code,const Module & module)39 DebugState::DebugState(const Code& code, const Module& module)
40 : code_(&code),
41 module_(&module),
42 enterFrameTrapsEnabled_(false),
43 enterAndLeaveFrameTrapsCounter_(0) {
44 MOZ_ASSERT(code.metadata().debugEnabled);
45 }
46
trace(JSTracer * trc)47 void DebugState::trace(JSTracer* trc) {
48 for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
49 WasmBreakpointSite* site = iter.get().value();
50 site->trace(trc);
51 }
52 }
53
finalize(JSFreeOp * fop)54 void DebugState::finalize(JSFreeOp* fop) {
55 for (auto iter = breakpointSites_.iter(); !iter.done(); iter.next()) {
56 WasmBreakpointSite* site = iter.get().value();
57 site->delete_(fop);
58 }
59 }
60
61 static const uint32_t DefaultBinarySourceColumnNumber = 1;
62
SlowCallSiteSearchByOffset(const MetadataTier & metadata,uint32_t offset)63 static const CallSite* SlowCallSiteSearchByOffset(const MetadataTier& metadata,
64 uint32_t offset) {
65 for (const CallSite& callSite : metadata.callSites) {
66 if (callSite.lineOrBytecode() == offset &&
67 callSite.kind() == CallSiteDesc::Breakpoint) {
68 return &callSite;
69 }
70 }
71 return nullptr;
72 }
73
getLineOffsets(size_t lineno,Vector<uint32_t> * offsets)74 bool DebugState::getLineOffsets(size_t lineno, Vector<uint32_t>* offsets) {
75 const CallSite* callsite =
76 SlowCallSiteSearchByOffset(metadata(Tier::Debug), lineno);
77 if (callsite && !offsets->append(lineno)) {
78 return false;
79 }
80 return true;
81 }
82
getAllColumnOffsets(Vector<ExprLoc> * offsets)83 bool DebugState::getAllColumnOffsets(Vector<ExprLoc>* offsets) {
84 for (const CallSite& callSite : metadata(Tier::Debug).callSites) {
85 if (callSite.kind() != CallSite::Breakpoint) {
86 continue;
87 }
88 uint32_t offset = callSite.lineOrBytecode();
89 if (!offsets->emplaceBack(offset, DefaultBinarySourceColumnNumber,
90 offset)) {
91 return false;
92 }
93 }
94 return true;
95 }
96
getOffsetLocation(uint32_t offset,size_t * lineno,size_t * column)97 bool DebugState::getOffsetLocation(uint32_t offset, size_t* lineno,
98 size_t* column) {
99 if (!SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset)) {
100 return false;
101 }
102 *lineno = offset;
103 *column = DefaultBinarySourceColumnNumber;
104 return true;
105 }
106
stepModeEnabled(uint32_t funcIndex) const107 bool DebugState::stepModeEnabled(uint32_t funcIndex) const {
108 return stepperCounters_.lookup(funcIndex).found();
109 }
110
incrementStepperCount(JSContext * cx,uint32_t funcIndex)111 bool DebugState::incrementStepperCount(JSContext* cx, uint32_t funcIndex) {
112 const CodeRange& codeRange =
113 codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
114 MOZ_ASSERT(codeRange.isFunction());
115
116 StepperCounters::AddPtr p = stepperCounters_.lookupForAdd(funcIndex);
117 if (p) {
118 MOZ_ASSERT(p->value() > 0);
119 p->value()++;
120 return true;
121 }
122 if (!stepperCounters_.add(p, funcIndex, 1)) {
123 ReportOutOfMemory(cx);
124 return false;
125 }
126
127 AutoWritableJitCode awjc(
128 cx->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
129 codeRange.end() - codeRange.begin());
130
131 for (const CallSite& callSite : callSites(Tier::Debug)) {
132 if (callSite.kind() != CallSite::Breakpoint) {
133 continue;
134 }
135 uint32_t offset = callSite.returnAddressOffset();
136 if (codeRange.begin() <= offset && offset <= codeRange.end()) {
137 toggleDebugTrap(offset, true);
138 }
139 }
140 return true;
141 }
142
decrementStepperCount(JSFreeOp * fop,uint32_t funcIndex)143 void DebugState::decrementStepperCount(JSFreeOp* fop, uint32_t funcIndex) {
144 const CodeRange& codeRange =
145 codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
146 MOZ_ASSERT(codeRange.isFunction());
147
148 MOZ_ASSERT(!stepperCounters_.empty());
149 StepperCounters::Ptr p = stepperCounters_.lookup(funcIndex);
150 MOZ_ASSERT(p);
151 if (--p->value()) {
152 return;
153 }
154
155 stepperCounters_.remove(p);
156
157 AutoWritableJitCode awjc(
158 fop->runtime(), code_->segment(Tier::Debug).base() + codeRange.begin(),
159 codeRange.end() - codeRange.begin());
160
161 for (const CallSite& callSite : callSites(Tier::Debug)) {
162 if (callSite.kind() != CallSite::Breakpoint) {
163 continue;
164 }
165 uint32_t offset = callSite.returnAddressOffset();
166 if (codeRange.begin() <= offset && offset <= codeRange.end()) {
167 bool enabled = breakpointSites_.has(offset);
168 toggleDebugTrap(offset, enabled);
169 }
170 }
171 }
172
hasBreakpointTrapAtOffset(uint32_t offset)173 bool DebugState::hasBreakpointTrapAtOffset(uint32_t offset) {
174 return SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
175 }
176
toggleBreakpointTrap(JSRuntime * rt,uint32_t offset,bool enabled)177 void DebugState::toggleBreakpointTrap(JSRuntime* rt, uint32_t offset,
178 bool enabled) {
179 const CallSite* callSite =
180 SlowCallSiteSearchByOffset(metadata(Tier::Debug), offset);
181 if (!callSite) {
182 return;
183 }
184 size_t debugTrapOffset = callSite->returnAddressOffset();
185
186 const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
187 const CodeRange* codeRange =
188 code_->lookupFuncRange(codeSegment.base() + debugTrapOffset);
189 MOZ_ASSERT(codeRange);
190
191 if (stepperCounters_.lookup(codeRange->funcIndex())) {
192 return; // no need to toggle when step mode is enabled
193 }
194
195 AutoWritableJitCode awjc(rt, codeSegment.base(), codeSegment.length());
196 toggleDebugTrap(debugTrapOffset, enabled);
197 }
198
getBreakpointSite(uint32_t offset) const199 WasmBreakpointSite* DebugState::getBreakpointSite(uint32_t offset) const {
200 WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
201 if (!p) {
202 return nullptr;
203 }
204
205 return p->value();
206 }
207
getOrCreateBreakpointSite(JSContext * cx,Instance * instance,uint32_t offset)208 WasmBreakpointSite* DebugState::getOrCreateBreakpointSite(JSContext* cx,
209 Instance* instance,
210 uint32_t offset) {
211 WasmBreakpointSite* site;
212
213 WasmBreakpointSiteMap::AddPtr p = breakpointSites_.lookupForAdd(offset);
214 if (!p) {
215 site = cx->new_<WasmBreakpointSite>(instance->object(), offset);
216 if (!site) {
217 return nullptr;
218 }
219
220 if (!breakpointSites_.add(p, offset, site)) {
221 js_delete(site);
222 ReportOutOfMemory(cx);
223 return nullptr;
224 }
225
226 AddCellMemory(instance->object(), sizeof(WasmBreakpointSite),
227 MemoryUse::BreakpointSite);
228
229 toggleBreakpointTrap(cx->runtime(), offset, true);
230 } else {
231 site = p->value();
232 }
233 return site;
234 }
235
hasBreakpointSite(uint32_t offset)236 bool DebugState::hasBreakpointSite(uint32_t offset) {
237 return breakpointSites_.has(offset);
238 }
239
destroyBreakpointSite(JSFreeOp * fop,Instance * instance,uint32_t offset)240 void DebugState::destroyBreakpointSite(JSFreeOp* fop, Instance* instance,
241 uint32_t offset) {
242 WasmBreakpointSiteMap::Ptr p = breakpointSites_.lookup(offset);
243 MOZ_ASSERT(p);
244 fop->delete_(instance->objectUnbarriered(), p->value(),
245 MemoryUse::BreakpointSite);
246 breakpointSites_.remove(p);
247 toggleBreakpointTrap(fop->runtime(), offset, false);
248 }
249
clearBreakpointsIn(JSFreeOp * fop,WasmInstanceObject * instance,js::Debugger * dbg,JSObject * handler)250 void DebugState::clearBreakpointsIn(JSFreeOp* fop, WasmInstanceObject* instance,
251 js::Debugger* dbg, JSObject* handler) {
252 MOZ_ASSERT(instance);
253
254 // Breakpoints hold wrappers in the instance's compartment for the handler.
255 // Make sure we don't try to search for the unwrapped handler.
256 MOZ_ASSERT_IF(handler, instance->compartment() == handler->compartment());
257
258 if (breakpointSites_.empty()) {
259 return;
260 }
261 for (WasmBreakpointSiteMap::Enum e(breakpointSites_); !e.empty();
262 e.popFront()) {
263 WasmBreakpointSite* site = e.front().value();
264 MOZ_ASSERT(site->instanceObject == instance);
265
266 Breakpoint* nextbp;
267 for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
268 nextbp = bp->nextInSite();
269 MOZ_ASSERT(bp->site == site);
270 if ((!dbg || bp->debugger == dbg) &&
271 (!handler || bp->getHandler() == handler)) {
272 bp->delete_(fop);
273 }
274 }
275 if (site->isEmpty()) {
276 fop->delete_(instance, site, MemoryUse::BreakpointSite);
277 e.removeFront();
278 }
279 }
280 }
281
clearAllBreakpoints(JSFreeOp * fop,WasmInstanceObject * instance)282 void DebugState::clearAllBreakpoints(JSFreeOp* fop,
283 WasmInstanceObject* instance) {
284 clearBreakpointsIn(fop, instance, nullptr, nullptr);
285 }
286
toggleDebugTrap(uint32_t offset,bool enabled)287 void DebugState::toggleDebugTrap(uint32_t offset, bool enabled) {
288 MOZ_ASSERT(offset);
289 uint8_t* trap = code_->segment(Tier::Debug).base() + offset;
290 const Uint32Vector& farJumpOffsets =
291 metadata(Tier::Debug).debugTrapFarJumpOffsets;
292 if (enabled) {
293 MOZ_ASSERT(farJumpOffsets.length() > 0);
294 size_t i = 0;
295 while (i < farJumpOffsets.length() && offset < farJumpOffsets[i]) {
296 i++;
297 }
298 if (i >= farJumpOffsets.length() ||
299 (i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
300 i--;
301 uint8_t* farJump = code_->segment(Tier::Debug).base() + farJumpOffsets[i];
302 MacroAssembler::patchNopToCall(trap, farJump);
303 } else {
304 MacroAssembler::patchCallToNop(trap);
305 }
306 }
307
adjustEnterAndLeaveFrameTrapsState(JSContext * cx,bool enabled)308 void DebugState::adjustEnterAndLeaveFrameTrapsState(JSContext* cx,
309 bool enabled) {
310 MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
311
312 bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
313 if (enabled) {
314 ++enterAndLeaveFrameTrapsCounter_;
315 } else {
316 --enterAndLeaveFrameTrapsCounter_;
317 }
318 bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
319 if (wasEnabled == stillEnabled) {
320 return;
321 }
322
323 const ModuleSegment& codeSegment = code_->segment(Tier::Debug);
324 AutoWritableJitCode awjc(cx->runtime(), codeSegment.base(),
325 codeSegment.length());
326 for (const CallSite& callSite : callSites(Tier::Debug)) {
327 if (callSite.kind() != CallSite::EnterFrame &&
328 callSite.kind() != CallSite::LeaveFrame) {
329 continue;
330 }
331 toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
332 }
333 }
334
ensureEnterFrameTrapsState(JSContext * cx,bool enabled)335 void DebugState::ensureEnterFrameTrapsState(JSContext* cx, bool enabled) {
336 if (enterFrameTrapsEnabled_ == enabled) {
337 return;
338 }
339
340 adjustEnterAndLeaveFrameTrapsState(cx, enabled);
341
342 enterFrameTrapsEnabled_ = enabled;
343 }
344
debugGetLocalTypes(uint32_t funcIndex,ValTypeVector * locals,size_t * argsLength,StackResults * stackResults)345 bool DebugState::debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals,
346 size_t* argsLength,
347 StackResults* stackResults) {
348 const ValTypeVector& args = metadata().debugFuncArgTypes[funcIndex];
349 const ValTypeVector& results = metadata().debugFuncReturnTypes[funcIndex];
350 ResultType resultType(ResultType::Vector(results));
351 *argsLength = args.length();
352 *stackResults = ABIResultIter::HasStackResults(resultType)
353 ? StackResults::HasStackResults
354 : StackResults::NoStackResults;
355 if (!locals->appendAll(args)) {
356 return false;
357 }
358
359 // Decode local var types from wasm binary function body.
360 const CodeRange& range =
361 codeRanges(Tier::Debug)[funcToCodeRangeIndex(funcIndex)];
362 // In wasm, the Code points to the function start via funcLineOrBytecode.
363 size_t offsetInModule = range.funcLineOrBytecode();
364 Decoder d(bytecode().begin() + offsetInModule, bytecode().end(),
365 offsetInModule,
366 /* error = */ nullptr);
367 return DecodeValidatedLocalEntries(d, locals);
368 }
369
getGlobal(Instance & instance,uint32_t globalIndex,MutableHandleValue vp)370 bool DebugState::getGlobal(Instance& instance, uint32_t globalIndex,
371 MutableHandleValue vp) {
372 const GlobalDesc& global = metadata().globals[globalIndex];
373
374 if (global.isConstant()) {
375 LitVal value = global.constantValue();
376 switch (value.type().kind()) {
377 case ValType::I32:
378 vp.set(Int32Value(value.i32()));
379 break;
380 case ValType::I64:
381 // Just display as a Number; it's ok if we lose some precision
382 vp.set(NumberValue((double)value.i64()));
383 break;
384 case ValType::F32:
385 vp.set(NumberValue(JS::CanonicalizeNaN(value.f32())));
386 break;
387 case ValType::F64:
388 vp.set(NumberValue(JS::CanonicalizeNaN(value.f64())));
389 break;
390 case ValType::Ref:
391 // It's possible to do better. We could try some kind of hashing
392 // scheme, to make the pointer recognizable without revealing it.
393 vp.set(MagicValue(JS_OPTIMIZED_OUT));
394 break;
395 case ValType::V128:
396 // Debugger must be updated to handle this, and should be updated to
397 // handle i64 in any case.
398 vp.set(MagicValue(JS_OPTIMIZED_OUT));
399 break;
400 default:
401 MOZ_CRASH("Global constant type");
402 }
403 return true;
404 }
405
406 uint8_t* globalData = instance.globalData();
407 void* dataPtr = globalData + global.offset();
408 if (global.isIndirect()) {
409 dataPtr = *static_cast<void**>(dataPtr);
410 }
411 switch (global.type().kind()) {
412 case ValType::I32: {
413 vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
414 break;
415 }
416 case ValType::I64: {
417 // Just display as a Number; it's ok if we lose some precision
418 vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
419 break;
420 }
421 case ValType::F32: {
422 vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
423 break;
424 }
425 case ValType::F64: {
426 vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
427 break;
428 }
429 case ValType::Ref: {
430 // Just hide it. See above.
431 vp.set(MagicValue(JS_OPTIMIZED_OUT));
432 break;
433 }
434 case ValType::V128: {
435 // Just hide it. See above.
436 vp.set(MagicValue(JS_OPTIMIZED_OUT));
437 break;
438 }
439 default: {
440 MOZ_CRASH("Global variable type");
441 break;
442 }
443 }
444 return true;
445 }
446
getSourceMappingURL(JSContext * cx,MutableHandleString result) const447 bool DebugState::getSourceMappingURL(JSContext* cx,
448 MutableHandleString result) const {
449 result.set(nullptr);
450
451 for (const CustomSection& customSection : module_->customSections()) {
452 const Bytes& sectionName = customSection.name;
453 if (strlen(SourceMappingURLSectionName) != sectionName.length() ||
454 memcmp(SourceMappingURLSectionName, sectionName.begin(),
455 sectionName.length()) != 0) {
456 continue;
457 }
458
459 // Parse found "SourceMappingURL" custom section.
460 Decoder d(customSection.payload->begin(), customSection.payload->end(), 0,
461 /* error = */ nullptr);
462 uint32_t nchars;
463 if (!d.readVarU32(&nchars)) {
464 return true; // ignoring invalid section data
465 }
466 const uint8_t* chars;
467 if (!d.readBytes(nchars, &chars) || d.currentPosition() != d.end()) {
468 return true; // ignoring invalid section data
469 }
470
471 UTF8Chars utf8Chars(reinterpret_cast<const char*>(chars), nchars);
472 JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
473 if (!str) {
474 return false;
475 }
476 result.set(str);
477 return true;
478 }
479
480 // Check presence of "SourceMap:" HTTP response header.
481 char* sourceMapURL = metadata().sourceMapURL.get();
482 if (sourceMapURL && strlen(sourceMapURL)) {
483 UTF8Chars utf8Chars(sourceMapURL, strlen(sourceMapURL));
484 JSString* str = JS_NewStringCopyUTF8N(cx, utf8Chars);
485 if (!str) {
486 return false;
487 }
488 result.set(str);
489 }
490 return true;
491 }
492
addSizeOfMisc(MallocSizeOf mallocSizeOf,Metadata::SeenSet * seenMetadata,Code::SeenSet * seenCode,size_t * code,size_t * data) const493 void DebugState::addSizeOfMisc(MallocSizeOf mallocSizeOf,
494 Metadata::SeenSet* seenMetadata,
495 Code::SeenSet* seenCode, size_t* code,
496 size_t* data) const {
497 code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code,
498 data);
499 module_->addSizeOfMisc(mallocSizeOf, seenMetadata, seenCode, code, data);
500 }
501