tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }