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