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