Beispiel #1
0
// Unwind the frame for a builtin.  Currently only used when switching
// modes for hphpd_break and fb_enable_code_coverage.
void unwindBuiltinFrame() {
  auto& stack = vmStack();
  auto& fp = vmfp();

  assert(fp->m_func->methInfo());
  assert(fp->m_func->name()->isame(s_hphpd_break.get()) ||
         fp->m_func->name()->isame(s_fb_enable_code_coverage.get()));

  // Free any values that may be on the eval stack.  We know there
  // can't be FPI regions and it can't be a generator body because
  // it's a builtin frame.
  auto const evalTop = reinterpret_cast<TypedValue*>(vmfp());
  while (stack.topTV() < evalTop) {
    stack.popTV();
  }

  // Free the locals and VarEnv if there is one
  auto rv = make_tv<KindOfNull>();
  frame_free_locals_inl(fp, fp->m_func->numLocals(), &rv);

  // Tear down the frame
  Offset pc = -1;
  ActRec* sfp = g_context->getPrevVMState(fp, &pc);
  assert(pc != -1);
  fp = sfp;
  vmpc() = fp->m_func->unit()->at(pc);
  stack.discardAR();
  stack.pushNull(); // return value
}
Beispiel #2
0
void sync_regstate(_Unwind_Context* context) {
  assertx(tl_regState == VMRegState::DIRTY);

  uintptr_t frameRbp = _Unwind_GetGR(context, Debug::RBP);
  uintptr_t frameRip = _Unwind_GetIP(context);
  FTRACE(2, "syncing regstate for rbp: {:#x} rip: {:#x}\n", frameRbp, frameRip);

  /*
   * fixupWork expects to be looking at the first frame that is out of
   * the TC.  We have RBP/RIP for the TC frame that called out here,
   * so we make a fake ActRec here to give it what it expects.
   *
   * Note: this doesn't work for IndirectFixup situations.  However,
   * currently IndirectFixup is only used for destructors, which
   * aren't allowed to throw, so this is ok.
   */
  ActRec fakeAr;
  fakeAr.m_sfp = reinterpret_cast<ActRec*>(frameRbp);
  fakeAr.m_savedRip = frameRip;

  Stats::inc(Stats::TC_SyncUnwind);
  mcg->fixupMap().fixupWork(g_context.getNoCheck(), &fakeAr);
  tl_regState = VMRegState::CLEAN;
  FTRACE(2, "synced vmfp: {} vmsp: {} vmpc: {}\n", vmfp(), vmsp(), vmpc());
}
Beispiel #3
0
Array XenonRequestLocalData::logAsyncStack() {
  VMRegAnchor _;
  Array bt;

  auto currentWaitHandle = c_ResumableWaitHandle::getRunning(vmfp());
  if (currentWaitHandle == nullptr) {
    // if we have a nullptr, then we have no async stack to store for this log
    return bt;
  }
  Array depStack = currentWaitHandle->t_getdependencystack();

  for (ArrayIter iter(depStack); iter; ++iter) {
    Array frameData;
    if (iter.secondRef().isNull()) {
      frameData.set(s_function, "<prep>", true);
    } else {
      auto wh = objToWaitableWaitHandle(iter.secondRef().toObject());
      frameData.set(s_function, wh->t_getname(), true);
      // Async function wait handles may have a source location to add.
      if (wh->getKind() == c_WaitHandle::Kind::AsyncFunction) {
        auto afwh = static_cast<c_AsyncFunctionWaitHandle*>(wh);
        if (!afwh->isRunning()) {
          frameData.set(s_file, afwh->getFileName(), true);
          frameData.set(s_line, afwh->getLineNumber(), true);
        }
      }
    }
    bt.append(frameData);
  }
  return bt;
}
Beispiel #4
0
static Array HHVM_FUNCTION(xdebug_get_declared_vars) {
  if (RuntimeOption::RepoAuthoritative) {
    raise_error("xdebug_get_declared_vars unsupported in RepoAuthoritative "
      "mode");
  }

  // Grab the callee function
  VMRegAnchor _; // Ensure consistent state for vmfp
  auto func = g_context->getPrevFunc(vmfp());
  if (!func) {
    return Array::Create();
  }

  // Add each named local to the returned array. Note that since this function
  // is supposed to return all _declared_ variables in scope, which includes
  // variables that have been unset.
  auto const numNames = func->numNamedLocals();
  PackedArrayInit vars(numNames);
  for (Id i = 0; i < numNames; ++i) {
    assert(func->lookupVarId(func->localVarName(i)) == i);
    String varname(const_cast<StringData*>(func->localVarName(i)));
    // Skip the internal closure "0Closure" variable
    if (!s_closure_varname.equal(varname)) {
      vars.append(varname);
    }
  }
  return vars.toArray();
}
Beispiel #5
0
void XDebugProfiler::collectFrameData(FrameData& frameData,
                                      const TypedValue* retVal) {
  VMRegAnchor _; // Ensure consistent state for vmfp and vmpc
  ActRec* fp = vmfp();
  bool is_func_begin = retVal == nullptr;
  frameData.is_func_begin = is_func_begin;

  // The function reference and call file/line are stored when tracing/profiling
  // on function enter
  if ((m_tracingEnabled || m_profilingEnabled) && is_func_begin) {
    frameData.func = fp->func();

    // Need the previous frame in order to get the call line. If we cannot
    // get the previous frame, default to 1
    Offset offset;
    const ActRec* prevFp = g_context->getPrevVMState(fp, &offset);
    if (prevFp != nullptr) {
      frameData.line = prevFp->unit()->getLineNumber(offset);
    } else {
      frameData.line = 1;
    }
  } else {
    frameData.func = nullptr;
    frameData.line = 1;
  }

  // Time is stored if profiling, tracing, or collect_time is enabled, but it
  // only needs to be collected on function exit if profiling or if computerized
  // tracing output is enabled
  if (m_profilingEnabled ||
      (is_func_begin && (m_collectTime || m_tracingEnabled)) ||
      (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) {
    frameData.time = Timer::GetCurrentTimeMicros();
  } else {
    frameData.time = 0;
  }

  // Memory usage is stored on function begin if tracing, or if collect_memory
  // is enabled, or on function end if computerized tracing output is enabled
  if ((is_func_begin && (m_tracingEnabled || m_collectMemory)) ||
      (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) {
    frameData.memory_usage = MM().getStats().usage;
  } else {
    frameData.memory_usage = 0;
  }

  // If tracing is enabled, we may need to collect a serialized version of
  // the arguments or the return value.
  if (m_tracingEnabled && is_func_begin && XDEBUG_GLOBAL(CollectParams) > 0) {
    // TODO(#3704) This relies on xdebug_var_dump
    throw_not_implemented("Tracing with collect_params enabled");
  } else if (m_tracingEnabled && !is_func_begin &&
             XDEBUG_GLOBAL(CollectReturn)) {
    // TODO(#3704) This relies on xdebug_var_dump
    throw_not_implemented("Tracing with collect_return enabled");
  } else {
    frameData.context_str = nullptr;
  }
}
Beispiel #6
0
void
FixupMap::fixupWorkSimulated(ExecutionContext* ec) const {
  TRACE(1, "fixup(begin):\n");

  auto isVMFrame = [] (ActRec* ar, const vixl::Simulator* sim) {
    // If this assert is failing, you may have forgotten a sync point somewhere
    assert(ar);
    bool ret =
      uintptr_t(ar) - s_stackLimit >= s_stackSize &&
      !sim->is_on_stack(ar);
    assert(!ret ||
           (ar >= vmStack().getStackLowAddress() &&
            ar < vmStack().getStackHighAddress()) ||
           ar->resumed());
    return ret;
  };

  // For each nested simulator (corresponding to nested VM invocations), look at
  // its PC to find a potential fixup key.
  //
  // Callstack walking is necessary, because we may get called from a
  // uniqueStub.
  for (int i = ec->m_activeSims.size() - 1; i >= 0; --i) {
    auto const* sim = ec->m_activeSims[i];
    auto* rbp = reinterpret_cast<ActRec*>(sim->xreg(JIT::ARM::rVmFp.code()));
    auto tca = reinterpret_cast<TCA>(sim->pc());
    TRACE(2, "considering frame %p, %p\n", rbp, tca);

    while (rbp && !isVMFrame(rbp, sim)) {
      tca = reinterpret_cast<TCA>(rbp->m_savedRip);
      rbp = rbp->m_sfp;
    }

    if (!rbp) continue;

    auto* ent = m_fixups.find(tca);
    if (!ent) {
      continue;
    }

    if (ent->isIndirect()) {
      not_implemented();
    }

    VMRegs regs;
    regsFromActRec(tca, rbp, ent->fixup, &regs);
    TRACE(2, "fixup(end): func %s fp %p sp %p pc %p\b",
          regs.m_fp->m_func->name()->data(),
          regs.m_fp, regs.m_sp, regs.m_pc);
    vmfp() = const_cast<ActRec*>(regs.m_fp);
    vmpc() = reinterpret_cast<PC>(regs.m_pc);
    vmsp() = regs.m_sp;
    return;
  }

  // This shouldn't be reached.
  always_assert(false);
}
Beispiel #7
0
UnwindAction enterUnwinder() {
  auto fault = g_context->m_faults.back();
  return unwind(
    vmfp(),    // by ref
    vmStack(), // by ref
    vmpc(),    // by ref
    fault
  );
}
Beispiel #8
0
int DebuggerProxy::getStackDepth() {
  TRACE(2, "DebuggerProxy::getStackDepth\n");
  int depth = 0;
  auto fp = vmfp();
  if (!fp) return 0;
  fp = fp->sfp();
  while (fp) {
    fp = fp->sfp();
    depth++;
  }
  return depth;
}
Variant HHVM_FUNCTION(get_class_methods, const Variant& class_or_object) {
  auto const cls = get_cls(class_or_object);
  if (!cls) return init_null();
  VMRegAnchor _;

  auto retVal = Array::attach(MixedArray::MakeReserve(cls->numMethods()));
  Class::getMethodNames(
    cls,
    arGetContextClassFromBuiltin(vmfp()),
    retVal
  );
  return HHVM_FN(array_values)(retVal).toArray();
}
Beispiel #10
0
static String HHVM_FUNCTION(xdebug_call_file) {
  // PHP5 xdebug returns the top-level file if the callee is top-level.
  auto fp = get_call_fp();
  const Func *func;
  if (fp == nullptr) {
    VMRegAnchor _;
    func = g_context->getPrevFunc(vmfp());
    assert(func);
  } else {
    func = fp->func();
  }
  return String(const_cast<StringData*>(func->filename()));
}
Beispiel #11
0
int DebuggerProxy::getRealStackDepth() {
  TRACE(2, "DebuggerProxy::getRealStackDepth\n");
  int depth = 0;
  auto const context = g_context.getNoCheck();
  auto fp = vmfp();
  if (!fp) return 0;

  while (fp != nullptr) {
    fp = context->getPrevVMState(fp, nullptr, nullptr);
    depth++;
  }
  return depth;
}
Beispiel #12
0
void enterTC(TCA start, ActRec* stashedAR) {
  if (debug) {
    fflush(stdout);
    fflush(stderr);
  }

  assertx(tc::isValidCodeAddress(start));
  assertx(((uintptr_t)vmsp() & (sizeof(Cell) - 1)) == 0);
  assertx(((uintptr_t)vmfp() & (sizeof(Cell) - 1)) == 0);

  INC_TPC(enter_tc);
  if (Trace::moduleEnabled(Trace::ringbuffer, 1)) {
    auto skData = SrcKey{liveFunc(), vmpc(), liveResumed()}.toAtomicInt();
    Trace::ringbufferEntry(Trace::RBTypeEnterTC, skData, (uint64_t)start);
  }

  tl_regState = VMRegState::DIRTY;
  enterTCImpl(start, stashedAR);
  tl_regState = VMRegState::CLEAN;
  assertx(isValidVMStackAddress(vmsp()));

  vmfp() = nullptr;
}
Beispiel #13
0
void CmdNext::onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt) {
  TRACE(2, "CmdNext::onSetup\n");
  assertx(!m_complete); // Complete cmds should not be asked to do work.
  m_stackDepth = proxy.getStackDepth();
  m_vmDepth = g_context->m_nesting;
  m_loc = interrupt.getFileLine();
  ActRec *fp = vmfp();
  if (!fp) {
    // If we have no frame just wait for the next instruction to be interpreted.
    m_needsVMInterrupt = true;
    return;
  }
  PC pc = vmpc();
  stepCurrentLine(interrupt, fp, pc);
}
TCA fcallHelper(ActRec* ar, void* sp) {
  try {
    assert(!ar->resumed());
    TCA tca =
      mcg->getFuncPrologue((Func*)ar->m_func, ar->numArgs(), ar);
    if (tca) {
      return tca;
    }
    if (!ar->m_func->isClonedClosure()) {
      /*
       * If the func is a cloned closure, then the original
       * closure has already run the prologue, and the prologues
       * array is just being used as entry points for the
       * dv funclets. Dont run the prologue again.
       */
      VMRegAnchor _(ar);
      if (g_context->doFCall(ar, vmpc())) {
        return tx->uniqueStubs.resumeHelperRet;
      }
      // We've been asked to skip the function body
      // (fb_intercept). frame, stack and pc have
      // already been fixed - flag that with a negative
      // return address.
      return (TCA)-ar->m_savedRip;
    }
    setupAfterPrologue(ar, sp);
    assert(ar == vmfp());
    return tx->uniqueStubs.resumeHelper;
  } catch (...) {
    /*
      The return address is set to __fcallHelperThunk,
      which has no unwind information. Its "logically"
      part of the tc, but the c++ unwinder wont know
      that. So point our return address at the called
      function's return address (which will be in the
      tc).
      Note that the registers really are clean - we
      cleaned them in the try above - so we just
      have to tell the unwinder that.
    */
    DECLARE_FRAME_POINTER(framePtr);
    tl_regState = VMRegState::CLEAN;
    framePtr->m_savedRip = ar->m_savedRip;
    throw;
  }
}
Beispiel #15
0
void XDebugHook::onOpcode(PC pc) {
  auto server = XDEBUG_GLOBAL(Server);
  if (server == nullptr) {
    return;
  }

  // Likely case is that there was no break command.
  auto brk = server->getAndClearBreak();
  if (LIKELY(brk == nullptr)) {
    return;
  }

  server->log("Request thread received break command");

  VMRegAnchor anchor;

  auto const unit = vmfp()->func()->unit();
  auto const line = unit->getLineNumber(unit->offsetOf(pc));
  auto const filepath = const_cast<StringData*>(unit->filepath());
  auto const transpath = File::TranslatePath(String(filepath));

  // XDebugServer::breakpoint will send the response for the command before the
  // break command, but we first need to send a response for the break command.
  auto response = xdebug_xml_node_init("response");
  server->addXmlns(*response);

  auto const& cmd_str  = brk->getCommandStr();
  auto const& trans_id = brk->getTransactionId();

  // Manually add status and reason.  XDebugServer still thinks we're running
  // because we haven't run XDebugServer::breakpoint yet.
  xdebug_xml_add_attribute(response, "status", "break");
  xdebug_xml_add_attribute(response, "reason", "ok");

  // Ditto with command, XDebugServer is tracking the command before the break.
  xdebug_xml_add_attribute_dup(response, "command", cmd_str.data());
  xdebug_xml_add_attribute_dup(response, "transaction_id", trans_id.data());

  delete brk;

  server->sendMessage(*response);
  xdebug_xml_node_dtor(response);

  // Now we can go into a command loop.
  server->breakpoint(transpath, init_null(), init_null(), line);
}
Beispiel #16
0
void HHVM_FUNCTION(set_frame_metadata, const Variant& metadata) {
  VMRegAnchor _;
  auto fp = vmfp();
  if (fp && fp->skipFrame()) fp = g_context->getPrevVMState(fp);
  if (UNLIKELY(!fp)) return;

  if (LIKELY(!(fp->func()->attrs() & AttrMayUseVV)) ||
      LIKELY(!fp->hasVarEnv())) {
    auto const local = fp->func()->lookupVarId(s_86metadata.get());
    if (LIKELY(local != kInvalidId)) {
      cellSet(*metadata.asCell(), *tvAssertCell(frame_local(fp, local)));
    } else {
      SystemLib::throwInvalidArgumentExceptionObject(
        "Unsupported dynamic call of set_frame_metadata()");
    }
  } else {
    fp->getVarEnv()->set(s_86metadata.get(), metadata.asTypedValue());
  }
}
Beispiel #17
0
// stack trace helper
static ProfileStackTrace getStackTrace() {
  ProfileStackTrace trace;

  if (g_context.isNull()) return trace;
  VMRegAnchor _;
  ActRec *fp = vmfp();
  if (!fp) return trace;
  PC pc = vmpc();

  const Func *f = fp->m_func;
  Unit *u = f->unit();
  Offset off = pc - u->entry();
  for (;;) {
    trace.push_back({ f, off, fp->resumed() });
    fp = g_context->getPrevVMStateUNSAFE(fp, &off);
    if (!fp) break;
    f = fp->m_func;
  }
  return trace;
}
Beispiel #18
0
void XDebugProfiler::enableTracing(const String& filename, int64_t opts) {
  assert(!m_tracingEnabled);

  // Attempt to open the passed filename. php5 xdebug doesn't enable tracing
  // if we cannot open the file, so we need to open it now as opposed to when we
  // actually do the writing in order to ensure we handle this case. We keep the
  // file handle open in order to ensure we can still write on tracing stop
  FILE* file;
  if (opts & k_XDEBUG_TRACE_APPEND) {
    file = fopen(filename.data(), "a");
  } else {
    file = fopen(filename.data(), "w");
  }

  // If file is null, opening the passed filename failed. php5 xdebug doesn't
  // do anything in this case, but we should probably notify the user
  if (file == nullptr) {
    raise_warning("xdebug profiler failed to open tracing file %s for writing.",
                  filename.data());
    return;
  }

  m_tracingEnabled = true;
  m_tracingStartIdx = m_nextFrameIdx;
  m_tracingFilename = filename;
  m_tracingFile = file;
  m_tracingOpts = opts;

  // If we're not at the top level, need to grab the call sites for each frame
  // on the stack.
  VMRegAnchor _;
  Offset offset;
  ActRec* fp = vmfp();
  while ((fp = g_context->getPrevVMState(fp, &offset)) != nullptr) {
    FrameData frame;
    frame.func = fp->func();
    frame.line = fp->unit()->getLineNumber(offset);
    m_tracingStartFrameData.push_back(frame);
  }
}
Beispiel #19
0
void throwable_init(ObjectData* throwable) {
  assertx(is_throwable(throwable));
  assertx(throwable_has_expected_props());

  auto trace = HHVM_FN(debug_backtrace)(exception_get_trace_options());
  cellMove(
    make_tv<KindOfArray>(trace.detach()), throwable->propVec()[s_traceIdx]);

  VMRegAnchor _;
  auto const fp = vmfp();
  if (UNLIKELY(!fp)) return;
  if (UNLIKELY(fp->func()->isBuiltin())) {
    throwable_init_file_and_line_from_builtin(throwable);
  } else {
    assertx(throwable->propVec()[s_fileIdx].m_type == KindOfNull);
    assertx(throwable->propVec()[s_lineIdx].m_type == KindOfNull);
    auto const unit = fp->func()->unit();
    auto const file = const_cast<StringData*>(unit->filepath());
    auto const line = unit->getLineNumber(unit->offsetOf(vmpc()));
    cellDup(make_tv<KindOfString>(file), throwable->propVec()[s_fileIdx]);
    cellDup(make_tv<KindOfInt64>(line), throwable->propVec()[s_lineIdx]);
  }
}
// throws on context depth level overflows and cross-context cycles
void c_WaitableWaitHandle::join() {
  EagerVMRegAnchor _;
  auto const savedFP = vmfp();

  assert(!isFinished());

  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnJoinCallback())) {
    session->onJoin(this);
  }

  // enter new asio context and set up guard that will exit once we are done
  session->enterContext(savedFP);
  auto exit_guard = folly::makeGuard([&] { session->exitContext(); });

  // import this wait handle to the newly created context
  // throws if cross-context cycle found
  enterContext(session->getCurrentContextIdx());

  // run queues until we are finished
  session->getCurrentContext()->runUntil(this);
  assert(isFinished());
}
Beispiel #21
0
static Variant eval_for_assert(ActRec* const curFP, const String& codeStr) {
  String prefixedCode = concat3("<?php return ", codeStr, ";");

  auto const oldErrorLevel =
    s_option_data->assertQuietEval ? HHVM_FN(error_reporting)(Variant(0)) : 0;
  SCOPE_EXIT {
    if (s_option_data->assertQuietEval) HHVM_FN(error_reporting)(oldErrorLevel);
  };

  auto const unit = g_context->compileEvalString(prefixedCode.get());
  if (unit == nullptr) {
    raise_recoverable_error("Syntax error in assert()");
    // Failure to compile the eval string doesn't count as an
    // assertion failure.
    return Variant(true);
  }

  if (!(curFP->func()->attrs() & AttrMayUseVV)) {
    throw_not_supported("assert()",
                        "assert called from non-varenv function");
  }

  if (!curFP->hasVarEnv()) {
    curFP->setVarEnv(VarEnv::createLocal(curFP));
  }
  auto varEnv = curFP->getVarEnv();

  if (curFP != vmfp()) {
    // If we aren't using FCallBuiltin, the stack frame of the call to assert
    // will be in middle of the code we are about to eval and our caller, whose
    // varEnv we want to use. The invokeFunc below will get very confused if
    // this is the case, since it will be using a varEnv that belongs to the
    // wrong function on the stack. So, we rebind it here, to match what
    // invokeFunc will expect.
    assert(!vmfp()->hasVarEnv());
    vmfp()->setVarEnv(varEnv);
    varEnv->enterFP(curFP, vmfp());
  }

  ObjectData* thiz = nullptr;
  Class* cls = nullptr;
  Class* ctx = curFP->func()->cls();
  if (ctx) {
    if (curFP->hasThis()) {
      thiz = curFP->getThis();
      cls = thiz->getVMClass();
    } else {
      cls = curFP->getClass();
    }
  }
  auto const func = unit->getMain(ctx);
  return Variant::attach(
    g_context->invokeFunc(
      func,
      init_null_variant,
      thiz,
      cls,
      varEnv,
      nullptr,
      ExecutionContext::InvokePseudoMain
    )
  );
}
Beispiel #22
0
Object HHVM_FUNCTION(asio_get_running) {
  VMRegAnchor _;
  return Object{c_ResumableWaitHandle::getRunning(vmfp())};
}
Beispiel #23
0
void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) {
  TRACE(2, "CmdNext::onBeginInterrupt\n");
  assertx(!m_complete); // Complete cmds should not be asked to do work.

  ActRec *fp = vmfp();
  if (!fp) {
    // If we have no frame just wait for the next instruction to be interpreted.
    m_needsVMInterrupt = true;
    return;
  }
  PC pc = vmpc();
  Unit* unit = fp->m_func->unit();
  Offset offset = unit->offsetOf(pc);
  TRACE(2, "CmdNext: pc %p, opcode %s at '%s' offset %d\n",
        pc,
        opcodeToName(peek_op(pc)),
        fp->m_func->fullName()->data(),
        offset);

  int currentVMDepth = g_context->m_nesting;
  int currentStackDepth = proxy.getStackDepth();

  TRACE(2, "CmdNext: original depth %d:%d, current depth %d:%d\n",
        m_vmDepth, m_stackDepth, currentVMDepth, currentStackDepth);

  // Where are we on the stack now vs. when we started? Breaking the answer down
  // into distinct variables helps the clarity of the algorithm below.
  bool deeper = false;
  bool originalDepth = false;
  if ((currentVMDepth == m_vmDepth) && (currentStackDepth == m_stackDepth)) {
    originalDepth = true;
  } else if ((currentVMDepth > m_vmDepth) ||
             ((currentVMDepth == m_vmDepth) &&
              (currentStackDepth > m_stackDepth))) {
    deeper = true;
  }

  m_needsVMInterrupt = false; // Will be set again below if still needed.

  // First consider if we've got internal breakpoints setup. These are used when
  // we can make an accurate prediction of where execution should flow,
  // eventually, and when we want to let the program run normally until we get
  // there.
  if (hasStepOuts() || hasStepResumable()) {
    TRACE(2, "CmdNext: checking internal breakpoint(s)\n");
    if (atStepOutOffset(unit, offset)) {
      if (deeper) return; // Recursion
      TRACE(2, "CmdNext: hit step-out\n");
    } else if (atStepResumableOffset(unit, offset)) {
      if (m_stepResumableId != getResumableId(fp)) return;
      TRACE(2, "CmdNext: hit step-cont\n");
      // We're in the resumable we expect. This may be at a
      // different stack depth, though, especially if we've moved from
      // the original function to the resumable. Update the depth
      // accordingly.
      if (!originalDepth) {
        m_vmDepth = currentVMDepth;
        m_stackDepth = currentStackDepth;
        deeper = false;
        originalDepth = true;
      }
    } else if (interrupt.getInterruptType() == ExceptionHandler) {
      // Entering an exception handler may take us someplace we weren't
      // expecting. Adjust internal breakpoints accordingly. First case is easy.
      if (deeper) {
        TRACE(2, "CmdNext: exception handler, deeper\n");
        return;
      }
      // For step-conts, we ignore handlers at the original level if we're not
      // in the original resumable. We don't care about exception handlers
      // in resumables being driven at the same level.
      if (hasStepResumable() && originalDepth &&
          (m_stepResumableId != getResumableId(fp))) {
        TRACE(2, "CmdNext: exception handler, original depth, wrong cont\n");
        return;
      }
      // Sometimes we have handlers in generated code, i.e., Continuation::next.
      // These just help propagate exceptions so ignore those.
      if (fp->m_func->line1() == 0) {
        TRACE(2, "CmdNext: exception handler, ignoring func with no source\n");
        return;
      }
      if (fp->m_func->isBuiltin()) {
        TRACE(2, "CmdNext: exception handler, ignoring builtin functions\n");
        return;
      }
      TRACE(2, "CmdNext: exception handler altering expected flow\n");
    } else {
      // We have internal breakpoints setup, but we haven't hit one yet. Keep
      // running until we reach one.
      TRACE(2, "CmdNext: waiting to hit internal breakpoint...\n");
      return;
    }
    // We've hit one internal breakpoint at a useful place, or decided we don't,
    // need them, so we can remove them all now.
    cleanupStepOuts();
    cleanupStepResumable();
  }

  if (interrupt.getInterruptType() == ExceptionHandler) {
    // If we're about to enter an exception handler we turn interrupts on to
    // ensure we stop when control reaches the handler. The normal logic below
    // will decide if we're done at that point or not.
    TRACE(2, "CmdNext: exception handler\n");
    removeLocationFilter();
    m_needsVMInterrupt = true;
    return;
  }

  if (m_skippingAwait) {
    m_skippingAwait = false;
    stepAfterAwait();
    return;
  }

  if (deeper) {
    TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n");
    setupStepOuts();
    // We can nuke the entire location filter here since we'll re-install it
    // when we get back to the old level. Keeping it installed may be more
    // efficient if we were on a large line, but there is a penalty for every
    // opcode executed while it's installed and that's bad if there's a lot of
    // code called from that line.
    removeLocationFilter();
    return;
  }

  if (originalDepth && (m_loc == interrupt.getFileLine())) {
    TRACE(2, "CmdNext: not complete, still on same line\n");
    stepCurrentLine(interrupt, fp, pc);
    return;
  }

  TRACE(2, "CmdNext: operation complete.\n");
  m_complete = (decCount() == 0);
  if (!m_complete) {
    TRACE(2, "CmdNext: repeat count > 0, start fresh.\n");
    onSetup(proxy, interrupt);
  }
}
Beispiel #24
0
Array createBacktrace(const BacktraceArgs& btArgs) {
  Array bt = Array::Create();

  // If there is a parser frame, put it at the beginning of
  // the backtrace
  if (btArgs.m_parserFrame) {
    bt.append(
      make_map_array(
        s_file, btArgs.m_parserFrame->filename,
        s_line, btArgs.m_parserFrame->lineNumber
      )
    );
  }

  VMRegAnchor _;
  if (!vmfp()) {
    // If there are no VM frames, we're done
    return bt;
  }

  int depth = 0;
  ActRec* fp = nullptr;
  Offset pc = 0;

  // Get the fp and pc of the top frame (possibly skipping one frame)
  {
    if (btArgs.m_skipTop) {
      fp = g_context->getPrevVMState(vmfp(), &pc);
      if (!fp) {
        // We skipped over the only VM frame, we're done
        return bt;
      }
    } else {
      fp = vmfp();
      Unit *unit = vmfp()->m_func->unit();
      assert(unit);
      pc = unit->offsetOf(vmpc());
    }

    // Handle the top frame
    if (btArgs.m_withSelf) {
      // Builtins don't have a file and line number
      if (!fp->m_func->isBuiltin()) {
        Unit* unit = fp->m_func->unit();
        assert(unit);
        const char* filename = fp->m_func->filename()->data();
        Offset off = pc;

        ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{});
        frame.set(s_file, filename);
        frame.set(s_line, unit->getLineNumber(off));
        if (btArgs.m_parserFrame) {
          frame.set(s_function, s_include);
          frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename));
        }
        bt.append(frame.toVariant());
        depth++;
      }
    }
  }
  // Handle the subsequent VM frames
  Offset prevPc = 0;
  for (ActRec* prevFp = g_context->getPrevVMState(fp, &prevPc);
       fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit);
       fp = prevFp, pc = prevPc,
         prevFp = g_context->getPrevVMState(fp, &prevPc)) {
    // do not capture frame for HPHP only functions
    if (fp->m_func->isNoInjection()) {
      continue;
    }

    ArrayInit frame(7, ArrayInit::Map{});

    auto const curUnit = fp->m_func->unit();
    auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc));
    auto const isReturning =
      curOp == Op::RetC || curOp == Op::RetV ||
      curOp == Op::CreateCont || curOp == Op::Await ||
      fp->localsDecRefd();

    // Builtins and generators don't have a file and line number
    if (prevFp && !prevFp->m_func->isBuiltin() && !fp->resumed()) {
      auto const prevUnit = prevFp->m_func->unit();
      auto prevFile = prevUnit->filepath();
      if (prevFp->m_func->originalFilename()) {
        prevFile = prevFp->m_func->originalFilename();
      }
      assert(prevFile);
      frame.set(s_file, const_cast<StringData*>(prevFile));

      // In the normal method case, the "saved pc" for line number printing is
      // pointing at the cell conversion (Unbox/Pop) instruction, not the call
      // itself. For multi-line calls, this instruction is associated with the
      // subsequent line which results in an off-by-n. We're subtracting one
      // in order to look up the line associated with the FCall/FCallArray
      // instruction. Exception handling and the other opcodes (ex. BoxR)
      // already do the right thing. The emitter associates object access with
      // the subsequent expression and this would be difficult to modify.
      auto const opAtPrevPc =
        *reinterpret_cast<const Op*>(prevUnit->at(prevPc));
      Offset pcAdjust = 0;
      if (opAtPrevPc == OpPopR || opAtPrevPc == OpUnboxR) {
        pcAdjust = 1;
      }
      frame.set(s_line,
                prevFp->m_func->unit()->getLineNumber(prevPc - pcAdjust));
    }

    // check for include
    String funcname = const_cast<StringData*>(fp->m_func->name());
    if (fp->m_func->isClosureBody()) {
      static StringData* s_closure_label =
        makeStaticString("{closure}");
      funcname = s_closure_label;
    }

    // check for pseudomain
    if (funcname.empty()) {
      if (!prevFp) continue;
      funcname = s_include;
    }

    frame.set(s_function, funcname);

    if (!funcname.same(s_include)) {
      // Closures have an m_this but they aren't in object context
      Class* ctx = arGetContextClass(fp);
      if (ctx != nullptr && !fp->m_func->isClosureBody()) {
        frame.set(s_class, ctx->name()->data());
        if (fp->hasThis() && !isReturning) {
          if (btArgs.m_withThis) {
            frame.set(s_object, Object(fp->getThis()));
          }
          frame.set(s_type, "->");
        } else {
          frame.set(s_type, "::");
        }
      }
    }

    Array args = Array::Create();
    if (btArgs.m_ignoreArgs) {
      // do nothing
    } else if (funcname.same(s_include)) {
      if (depth) {
        args.append(const_cast<StringData*>(curUnit->filepath()));
        frame.set(s_args, args);
      }
    } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) {
      // Provide an empty 'args' array to be consistent with hphpc
      frame.set(s_args, args);
    } else {
      const int nparams = fp->m_func->numNonVariadicParams();
      int nargs = fp->numArgs();
      int nformals = std::min(nparams, nargs);

      if (UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) {
        // VarEnv is attached to eval or debugger frame, other than the current
        // frame. Access locals thru VarEnv.
        auto varEnv = fp->getVarEnv();
        auto func = fp->func();
        for (int i = 0; i < nformals; i++) {
          TypedValue *arg = varEnv->lookup(func->localVarName(i));
          args.append(tvAsVariant(arg));
        }
      } else {
        for (int i = 0; i < nformals; i++) {
          TypedValue *arg = frame_local(fp, i);
          args.append(tvAsVariant(arg));
        }
      }

      /* builtin extra args are not stored in varenv */
      if (nargs > nparams && fp->hasExtraArgs()) {
        for (int i = nparams; i < nargs; i++) {
          TypedValue *arg = fp->getExtraArg(i - nparams);
          args.append(tvAsVariant(arg));
        }
      }
      frame.set(s_args, args);
    }

    bt.append(frame.toVariant());
    depth++;
  }
  return bt;

}
Beispiel #25
0
Array createBacktrace(const BacktraceArgs& btArgs) {
  auto bt = Array::Create();

  // If there is a parser frame, put it at the beginning of the backtrace.
  if (btArgs.m_parserFrame) {
    bt.append(
      make_map_array(
        s_file, btArgs.m_parserFrame->filename,
        s_line, btArgs.m_parserFrame->lineNumber
      )
    );
  }

  VMRegAnchor _;
  // If there are no VM frames, we're done.
  if (!rds::header() || !vmfp()) return bt;

  int depth = 0;
  ActRec* fp = nullptr;
  Offset pc = 0;

  // Get the fp and pc of the top frame (possibly skipping one frame).

  if (btArgs.m_skipTop) {
    fp = getPrevActRec(vmfp(), &pc);
    // We skipped over the only VM frame, we're done.
    if (!fp) return bt;
  } else {
    fp = vmfp();
    auto const unit = fp->func()->unit();
    assert(unit);
    pc = unit->offsetOf(vmpc());
  }

  // Handle the top frame.
  if (btArgs.m_withSelf) {
    // Builtins don't have a file and line number.
    if (!fp->func()->isBuiltin()) {
      auto const unit = fp->func()->unit();
      assert(unit);
      auto const filename = fp->func()->filename();

      ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{});
      frame.set(s_file, Variant{const_cast<StringData*>(filename)});
      frame.set(s_line, unit->getLineNumber(pc));
      if (btArgs.m_parserFrame) {
        frame.set(s_function, s_include);
        frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename));
      }
      bt.append(frame.toVariant());
      depth++;
    }
  }

  // Handle the subsequent VM frames.
  Offset prevPc = 0;
  for (auto prevFp = getPrevActRec(fp, &prevPc);
       fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit);
       fp = prevFp, pc = prevPc,
         prevFp = getPrevActRec(fp, &prevPc)) {
    // Do not capture frame for HPHP only functions.
    if (fp->func()->isNoInjection()) continue;

    ArrayInit frame(7, ArrayInit::Map{});

    auto const curUnit = fp->func()->unit();
    auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc));
    auto const isReturning =
      curOp == Op::RetC || curOp == Op::RetV ||
      curOp == Op::CreateCont || curOp == Op::Await ||
      fp->localsDecRefd();

    // Builtins and generators don't have a file and line number
    if (prevFp && !prevFp->func()->isBuiltin()) {
      auto const prevUnit = prevFp->func()->unit();
      auto prevFile = prevUnit->filepath();
      if (prevFp->func()->originalFilename()) {
        prevFile = prevFp->func()->originalFilename();
      }
      assert(prevFile);
      frame.set(s_file, Variant{const_cast<StringData*>(prevFile)});

      // In the normal method case, the "saved pc" for line number printing is
      // pointing at the cell conversion (Unbox/Pop) instruction, not the call
      // itself. For multi-line calls, this instruction is associated with the
      // subsequent line which results in an off-by-n. We're subtracting one
      // in order to look up the line associated with the FCall/FCallArray
      // instruction. Exception handling and the other opcodes (ex. BoxR)
      // already do the right thing. The emitter associates object access with
      // the subsequent expression and this would be difficult to modify.
      auto const opAtPrevPc =
        *reinterpret_cast<const Op*>(prevUnit->at(prevPc));
      Offset pcAdjust = 0;
      if (opAtPrevPc == Op::PopR ||
          opAtPrevPc == Op::UnboxR ||
          opAtPrevPc == Op::UnboxRNop) {
        pcAdjust = 1;
      }
      frame.set(s_line,
                prevFp->func()->unit()->getLineNumber(prevPc - pcAdjust));
    }

    // Check for include.
    String funcname{const_cast<StringData*>(fp->func()->name())};
    if (fp->func()->isClosureBody()) {
      // Strip the file hash from the closure name.
      String fullName{const_cast<StringData*>(fp->func()->baseCls()->name())};
      funcname = fullName.substr(0, fullName.find(';'));
    }

    // Check for pseudomain.
    if (funcname.empty()) {
      if (!prevFp && !btArgs.m_withPseudoMain) continue;
      else if (!prevFp) funcname = s_main;
      else funcname = s_include;
    }

    frame.set(s_function, funcname);

    if (!funcname.same(s_include)) {
      // Closures have an m_this but they aren't in object context.
      auto ctx = arGetContextClass(fp);
      if (ctx != nullptr && !fp->func()->isClosureBody()) {
        frame.set(s_class, Variant{const_cast<StringData*>(ctx->name())});
        if (fp->hasThis() && !isReturning) {
          if (btArgs.m_withThis) {
            frame.set(s_object, Object(fp->getThis()));
          }
          frame.set(s_type, s_arrow);
        } else {
          frame.set(s_type, s_double_colon);
        }
      }
    }

    bool const mayUseVV = fp->func()->attrs() & AttrMayUseVV;

    auto const withNames = btArgs.m_withArgNames;
    auto const withValues = btArgs.m_withArgValues;
    if (!btArgs.m_withArgNames && !btArgs.m_withArgValues) {
      // do nothing
    } else if (funcname.same(s_include)) {
      if (depth != 0) {
        auto filepath = const_cast<StringData*>(curUnit->filepath());
        frame.set(s_args, make_packed_array(filepath));
      }
    } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) {
      // Provide an empty 'args' array to be consistent with hphpc.
      frame.set(s_args, empty_array());
    } else {
      auto args = Array::Create();
      auto const nparams = fp->func()->numNonVariadicParams();
      auto const nargs = fp->numArgs();
      auto const nformals = std::min<int>(nparams, nargs);

      if (UNLIKELY(mayUseVV) &&
          UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) {
        // VarEnv is attached to eval or debugger frame, other than the current
        // frame. Access locals thru VarEnv.
        auto varEnv = fp->getVarEnv();
        auto func = fp->func();
        for (int i = 0; i < nformals; i++) {
          auto const argname = func->localVarName(i);
          auto const tv = varEnv->lookup(argname);

          Variant val;
          if (tv != nullptr) { // the variable hasn't been unset
            val = withValues ? tvAsVariant(tv) : "";
          }

          if (withNames) {
            args.set(String(const_cast<StringData*>(argname)), val);
          } else {
            args.append(val);
          }
        }
      } else {
        for (int i = 0; i < nformals; i++) {
          Variant val = withValues ? tvAsVariant(frame_local(fp, i)) : "";

          if (withNames) {
            auto const argname = fp->func()->localVarName(i);
            args.set(String(const_cast<StringData*>(argname)), val);
          } else {
            args.append(val);
          }
        }
      }

      // Builtin extra args are not stored in varenv.
      if (UNLIKELY(mayUseVV) && nargs > nparams && fp->hasExtraArgs()) {
        for (int i = nparams; i < nargs; i++) {
          auto arg = fp->getExtraArg(i - nparams);
          args.append(tvAsVariant(arg));
        }
      }
      frame.set(s_args, args);
    }

    if (btArgs.m_withMetadata && !isReturning) {
      if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv())) {
        auto tv = fp->getVarEnv()->lookup(s_86metadata.get());
        if (tv != nullptr && tv->m_type != KindOfUninit) {
          frame.set(s_metadata, tvAsVariant(tv));
        }
      } else {
        auto local = fp->func()->lookupVarId(s_86metadata.get());
        if (local != kInvalidId) {
          auto tv = frame_local(fp, local);
          if (tv->m_type != KindOfUninit) {
            frame.set(s_metadata, tvAsVariant(tv));
          }
        }
      }
    }

    bt.append(frame.toVariant());
    depth++;
  }

  return bt;
}
Beispiel #26
0
bool EventHook::RunInterceptHandler(ActRec* ar) {
  const Func* func = ar->func();
  if (LIKELY(func->maybeIntercepted() == 0)) return true;

  // Intercept only original generator / async function calls, not resumption.
  if (ar->resumed()) return true;

  Variant* h = get_intercept_handler(func->fullNameStr(),
                                     &func->maybeIntercepted());
  if (!h) return true;

  /*
   * In production mode, only functions that we have assumed can be
   * intercepted during static analysis should actually be
   * intercepted.
   */
  if (RuntimeOption::RepoAuthoritative &&
      !RuntimeOption::EvalJitEnableRenameFunction) {
    if (!(func->attrs() & AttrInterceptable)) {
      raise_error("fb_intercept was used on a non-interceptable function (%s) "
                  "in RepoAuthoritative mode", func->fullName()->data());
    }
  }

  VMRegAnchor _;

  PC savePc = vmpc();

  Variant doneFlag = true;
  Variant called_on;

  if (ar->hasThis()) {
    called_on = Variant(ar->getThis());
  } else if (ar->hasClass()) {
    // For static methods, give handler the name of called class
    called_on = Variant(const_cast<StringData*>(ar->getClass()->name()));
  }
  Variant intArgs =
    PackedArrayInit(5)
      .append(VarNR(ar->func()->fullName()))
      .append(called_on)
      .append(get_frame_args_with_ref(ar))
      .append(h->asCArrRef()[1])
      .appendRef(doneFlag)
      .toArray();

  Variant ret = vm_call_user_func(h->asCArrRef()[0], intArgs);
  if (doneFlag.toBoolean()) {
    Offset pcOff;
    ActRec* outer = g_context->getPrevVMState(ar, &pcOff);

    frame_free_locals_inl_no_hook<true>(ar, ar->func()->numLocals());
    Stack& stack = vmStack();
    stack.top() = (Cell*)(ar + 1);
    cellDup(*ret.asCell(), *stack.allocTV());

    vmfp() = outer;
    vmpc() = outer ? outer->func()->unit()->at(pcOff) : nullptr;

    return false;
  }
  vmfp() = ar;
  vmpc() = savePc;

  return true;
}
Beispiel #27
0
void HttpServer::runOrExitProcess() {
  if (StaticContentCache::TheFileCache &&
      StructuredLog::enabled() &&
      StructuredLog::coinflip(RuntimeOption::EvalStaticContentsLogRate)) {
    CacheManager::setLogger([](bool existsCheck, const std::string& name) {
        auto record = StructuredLogEntry{};
        record.setInt("existsCheck", existsCheck);
        record.setStr("file", name);
        bool needsCppStack = true;
        if (!g_context.isNull()) {
          VMRegAnchor _;
          if (vmfp()) {
            auto const bt =
              createBacktrace(BacktraceArgs().withArgValues(false));
            std::vector<std::string> frameStrings;
            std::vector<folly::StringPiece> frames;
            for (int i = 0; i < bt.size(); i++) {
              auto f = bt.rvalAt(i).toArray();
              if (f.exists(s_file)) {
                std::string s = f.rvalAt(s_file).toString().toCppString();
                if (f.exists(s_line)) {
                  s += folly::sformat(":{}", f.rvalAt(s_line).toInt64());
                }
                frameStrings.emplace_back(std::move(s));
                frames.push_back(frameStrings.back());
              }
            }
            record.setVec("stack", frames);
            needsCppStack = false;
          }
        }
        if (needsCppStack) {
          record.setStackTrace("stack", StackTrace{StackTrace::Force{}});
        }
        StructuredLog::log("hhvm_file_cache", record);
      });
  }
  auto startupFailure = [] (const std::string& msg) {
    Logger::Error(msg);
    Logger::Error("Shutting down due to failure(s) to bind in "
                  "HttpServer::runAndExitProcess");
    // Logger flushes itself---we don't need to run any atexit handlers
    // (historically we've mostly just SEGV'd while trying) ...
    _Exit(1);
  };

  if (!RuntimeOption::InstanceId.empty()) {
    std::string msg = "Starting instance " + RuntimeOption::InstanceId;
    if (!RuntimeOption::DeploymentId.empty()) {
      msg += " from deployment " + RuntimeOption::DeploymentId;
    }
    Logger::Info(msg);
  }

  m_watchDog.start();

  if (RuntimeOption::ServerPort) {
    if (!startServer(true)) {
      startupFailure("Unable to start page server");
      not_reached();
    }
    Logger::Info("page server started");
  }

  StartTime = time(nullptr);

  if (RuntimeOption::AdminServerPort) {
    if (!startServer(false)) {
      startupFailure("Unable to start admin server");
      not_reached();
    }
    Logger::Info("admin server started");
  }

  for (unsigned int i = 0; i < m_satellites.size(); i++) {
    std::string name = m_satellites[i]->getName();
    try {
      m_satellites[i]->start();
      Logger::Info("satellite server %s started", name.c_str());
    } catch (Exception &e) {
      startupFailure(
        folly::format("Unable to start satellite server {}: {}",
                      name, e.getMessage()).str()
      );
      not_reached();
    }
  }

  if (!Eval::Debugger::StartServer()) {
    startupFailure("Unable to start debugger server");
    not_reached();
  } else if (RuntimeOption::EnableDebuggerServer) {
    Logger::Info("debugger server started");
  }

  try {
    InitFiniNode::ServerInit();
  } catch (std::exception &e) {
    startupFailure(
      folly::sformat("Exception in InitFiniNode::ServerInit(): {}",
                     e.what()));
  }

  {
    BootStats::mark("servers started");
    Logger::Info("all servers started");
    createPid();
    Lock lock(this);
    BootStats::done();
    // continously running until /stop is received on admin server, or
    // takeover is requested.
    while (!m_stopped) {
      wait();
    }
    if (m_stopReason) {
      Logger::Warning("Server stopping with reason: %s\n", m_stopReason);
    }
    // if we were killed, bail out immediately
    if (m_killed) {
      Logger::Info("page server killed");
      return;
    }
  }

  if (RuntimeOption::ServerPort) {
    Logger::Info("stopping page server");
    m_pageServer->stop();
  }
  onServerShutdown();

  EvictFileCache();

  waitForServers();
  m_watchDog.waitForEnd();
  playShutdownRequest(RuntimeOption::ServerCleanupRequest);
  hphp_process_exit();
  Logger::Info("all servers stopped");
}