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