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