Пример #1
0
UnwindAction checkHandlers(const EHEnt* eh,
                           const ActRec* const fp,
                           PC& pc,
                           Fault& fault) {
  auto const func = fp->m_func;
  FTRACE(1, "checkHandlers: func {} ({})\n",
         func->fullName()->data(),
         func->unit()->filepath()->data());

  // Always blindly propagate on fatal exception since those are
  // unrecoverable anyway.
  if (fault.m_faultType == Fault::Type::CppException) {
    return UnwindAction::Propagate;
  }

  for (int i = 0;; ++i) {
    // Skip the initial m_handledCount - 1 handlers that were
    // considered before.
    if (fault.m_handledCount <= i) {
      fault.m_handledCount++;
      switch (eh->m_type) {
      case EHEnt::Type::Fault:
        FTRACE(1, "checkHandlers: entering fault at {}: save {}\n",
               eh->m_fault,
               func->unit()->offsetOf(pc));
        pc = func->unit()->entry() + eh->m_fault;
        DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
        return UnwindAction::ResumeVM;
      case EHEnt::Type::Catch:
        // Note: we skip catch clauses if we have a pending C++ exception
        // as part of our efforts to avoid running more PHP code in the
        // face of such exceptions.
        if (fault.m_faultType == Fault::Type::UserException &&
            ThreadInfo::s_threadInfo->m_pendingException == nullptr) {
          auto const obj = fault.m_userException;
          for (auto& idOff : eh->m_catches) {
            FTRACE(1, "checkHandlers: catch candidate {}\n", idOff.second);
            auto handler = func->unit()->at(idOff.second);
            auto const cls = Unit::lookupClass(
              func->unit()->lookupNamedEntityId(idOff.first)
            );
            if (!cls || !obj->instanceof(cls)) continue;

            FTRACE(1, "checkHandlers: entering catch at {}\n", idOff.second);
            pc = handler;
            DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
            return UnwindAction::ResumeVM;
          }
        }
        break;
      }
    }
    if (eh->m_parentIndex != -1) {
      eh = &func->ehtab()[eh->m_parentIndex];
    } else {
      break;
    }
  }
  return UnwindAction::Propagate;
}
Пример #2
0
void throw_exception(const Object& e) {
  if (!e.instanceof(SystemLib::s_ExceptionClass)) {
    raise_error("Exceptions must be valid objects derived from the "
                "Exception base class");
  }
  DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get()));
  throw e;
}
Пример #3
0
void EventHook::onFunctionExit(const ActRec* ar, const TypedValue* retval,
                               const Fault* fault, ssize_t flags) {
  // Xenon
  if (flags & XenonSignalFlag) {
    Xenon::getInstance().log(Xenon::ExitSample);
  }

  // Run IntervalTimer callbacks only if it's safe to do so, i.e., not when
  // there's a pending exception or we're unwinding from a C++ exception.
  if (flags & IntervalTimerFlag
      && ThreadInfo::s_threadInfo->m_pendingException == nullptr
      && (!fault || fault->m_faultType == Fault::Type::UserException)) {
    IntervalTimer::RunCallbacks(IntervalTimer::ExitSample);
  }

  // Inlined calls normally skip the function enter and exit events. If we
  // side exit in an inlined callee, we short-circuit here in order to skip
  // exit events that could unbalance the call stack.
  if (RuntimeOption::EvalJit &&
      ((jit::TCA) ar->m_savedRip == jit::mcg->tx().uniqueStubs.retInlHelper)) {
    return;
  }

  // User profiler
  if (flags & EventHookFlag) {
    auto profiler = ThreadInfo::s_threadInfo->m_profiler;
    if (profiler != nullptr &&
        !(profiler->shouldSkipBuiltins() && ar->func()->isBuiltin())) {
      // NB: we don't have a function type flag to match what we got in
      // onFunctionEnter. That's okay, though... we tolerate this in
      // TraceProfiler.
      end_profiler_frame(profiler,
                         retval,
                         GetFunctionNameForProfiler(ar->func(), NormalFunc));
    }

    if (shouldRunUserProfiler(ar->func())) {
      if (ThreadInfo::s_threadInfo->m_pendingException != nullptr) {
        // Avoid running PHP code when exception from destructor is pending.
        // TODO(#2329497) will not happen once CheckSurprise is used
      } else if (!fault) {
        runUserProfilerOnFunctionExit(ar, retval, nullptr);
      } else if (fault->m_faultType == Fault::Type::UserException) {
        runUserProfilerOnFunctionExit(ar, retval, fault->m_userException);
      } else {
        // Avoid running PHP code when unwinding C++ exception.
      }
    }
  }

  // Debugger hook
  if (flags & DebuggerHookFlag) {
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncExitHook(ar));
  }
}
Пример #4
0
UnwindAction checkHandlers(const EHEnt* eh,
                           const ActRec* const fp,
                           PC& pc,
                           Fault& fault) {
  auto const func = fp->m_func;
  ITRACE(1, "checkHandlers: func {} ({})\n",
         func->fullName()->data(),
         func->unit()->filepath()->data());

  for (int i = 0;; ++i) {
    // Skip the initial m_handledCount - 1 handlers that were
    // considered before.
    if (fault.m_handledCount <= i) {
      fault.m_handledCount++;
      switch (eh->m_type) {
      case EHEnt::Type::Fault:
        ITRACE(1, "checkHandlers: entering fault at {}: save {}\n",
               eh->m_handler,
               func->unit()->offsetOf(pc));
        pc = func->unit()->at(eh->m_handler);
        DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
        return UnwindAction::ResumeVM;
      case EHEnt::Type::Catch:
        ITRACE(1, "checkHandlers: entering catch at {}\n", eh->m_handler);
        pc = func->unit()->at(eh->m_handler);
        DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
        return UnwindAction::ResumeVM;
      }
    }
    if (eh->m_parentIndex != -1) {
      eh = &func->ehtab()[eh->m_parentIndex];
    } else {
      break;
    }
  }
  return UnwindAction::Propagate;
}
Пример #5
0
Unit* lookupUnit(StringData* path, const char* currentDir, bool* initial_opt) {
  bool init;
  bool& initial = initial_opt ? *initial_opt : init;
  initial = true;

  /*
   * NB: the m_evaledFiles map is only for the debugger, and could be omitted
   * in RepoAuthoritative mode, but currently isn't.
   */

  struct stat s;
  auto const spath = resolveVmInclude(path, currentDir, &s);
  if (spath.isNull()) return nullptr;

  auto const eContext = g_context.getNoCheck();

  // Check if this file has already been included.
  auto it = eContext->m_evaledFiles.find(spath.get());
  if (it != end(eContext->m_evaledFiles)) {
    initial = false;
    return it->second;
  }

  // This file hasn't been included yet, so we need to parse the file
  auto const cunit = checkoutFile(spath.get(), s);
  if (cunit.unit && initial_opt) {
    // if initial_opt is not set, this shouldn't be recorded as a
    // per request fetch of the file.
    if (rds::testAndSetBit(cunit.rdsBitId)) {
      initial = false;
    }
    // if parsing was successful, update the mappings for spath and
    // rpath (if it exists).
    eContext->m_evaledFilesOrder.push_back(cunit.unit->filepath());
    eContext->m_evaledFiles[spath.get()] = cunit.unit;
    spath.get()->incRefCount();
    if (!cunit.unit->filepath()->same(spath.get())) {
      eContext->m_evaledFiles[cunit.unit->filepath()] = cunit.unit;
    }
    if (g_system_profiler) {
      g_system_profiler->fileLoadCallBack(path->toCppString());
    }
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit.unit));
  }

  return cunit.unit;
}
Пример #6
0
void EventHook::onFunctionExit(const ActRec* ar, const TypedValue* retval,
                               const Fault* fault, ssize_t flags) {
  // Xenon
  if (flags & RequestInjectionData::XenonSignalFlag) {
    Xenon::getInstance().log(Xenon::ExitSample);
  }

  // Inlined calls normally skip the function enter and exit events. If we
  // side exit in an inlined callee, we short-circuit here in order to skip
  // exit events that could unbalance the call stack.
  if ((JIT::TCA) ar->m_savedRip == JIT::mcg->tx().uniqueStubs.retInlHelper) {
    return;
  }

  // User profiler
  if (flags & RequestInjectionData::EventHookFlag) {
    Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
    if (profiler != nullptr) {
      // NB: we don't have a function type flag to match what we got in
      // onFunctionEnter. That's okay, though... we tolerate this in
      // TraceProfiler.
      end_profiler_frame(profiler,
                         retval,
                         GetFunctionNameForProfiler(ar->func(), NormalFunc));
    }

    if (shouldRunUserProfiler(ar->func())) {
      if (ThreadInfo::s_threadInfo->m_pendingException != nullptr) {
        // Avoid running PHP code when exception from destructor is pending.
        // TODO(#2329497) will not happen once CheckSurprise is used
      } else if (!fault) {
        runUserProfilerOnFunctionExit(ar, retval, nullptr);
      } else if (fault->m_faultType == Fault::Type::UserException) {
        runUserProfilerOnFunctionExit(ar, retval, fault->m_userException);
      } else {
        // Avoid running PHP code when unwinding C++ exception.
      }
    }
  }

  // Debugger hook
  if (flags & RequestInjectionData::DebuggerHookFlag) {
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncExitHook(ar));
  }
}
Пример #7
0
void EventHook::onFunctionEnter(const ActRec* ar, int funcType, ssize_t flags) {
  // User profiler
  if (flags & EventHookFlag) {
    if (shouldRunUserProfiler(ar->func())) {
      runUserProfilerOnFunctionEnter(ar);
    }
    auto profiler = ThreadInfo::s_threadInfo->m_profiler;
    if (profiler != nullptr &&
        !(profiler->shouldSkipBuiltins() && ar->func()->isBuiltin())) {
      begin_profiler_frame(profiler,
                           GetFunctionNameForProfiler(ar->func(), funcType));
    }
  }

  // Debugger hook
  if (flags & DebuggerHookFlag) {
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncEntryHook(ar));
  }
}
Пример #8
0
void EventHook::onFunctionEnter(const ActRec* ar, int funcType, ssize_t flags) {
  // Xenon
  if (flags & RequestInjectionData::XenonSignalFlag) {
    Xenon::getInstance().log(Xenon::EnterSample);
  }

  // User profiler
  if (flags & RequestInjectionData::EventHookFlag) {
    if (shouldRunUserProfiler(ar->func())) {
      runUserProfilerOnFunctionEnter(ar);
    }
    Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
    if (profiler != nullptr) {
      begin_profiler_frame(profiler,
                           GetFunctionNameForProfiler(ar->func(), funcType));
    }
  }

  // Debugger hook
  if (flags & RequestInjectionData::DebuggerHookFlag) {
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncEntryHook(ar));
  }
}
Пример #9
0
Class* Unit::defClass(PreClass* preClass,
                      bool failIsFatal /* = true */) {
  Class* const* clsList = preClass->namedEntity()->clsList();
  Class* top = *clsList;
  if (top) {
    Class *cls = top->getCached();
    if (cls) {
      // Raise a fatal unless the existing class definition is identical to the
      // one this invocation would create.
      if (cls->preClass() != preClass) {
        if (failIsFatal) {
          raise_error("Class already declared: %s", preClass->name()->data());
        }
        return NULL;
      }
      return cls;
    }
  }
  // Get a compatible Class, and add it to the list of defined classes.

  Class* parent = NULL;
  for (;;) {
    // Search for a compatible extant class.  Searching from most to least
    // recently created may have better locality than alternative search orders.
    // In addition, its the only simple way to make this work lock free...
    for (Class* class_ = top; class_ != NULL; class_ = class_->m_nextClass) {
      if (class_->preClass() != preClass) continue;

      Class::Avail avail = class_->avail(parent, failIsFatal /*tryAutoload*/);
      if (LIKELY(avail == Class::AvailTrue)) {
        class_->setCached();
        DEBUGGER_ATTACHED_ONLY(phpDefClassHook(class_));
        return class_;
      }
      if (avail == Class::AvailFail) {
        if (failIsFatal) {
          raise_error("unknown class %s", parent->name()->data());
        }
        return NULL;
      }
      ASSERT(avail == Class::AvailFalse);
    }

    // Create a new class.
    if (!parent && preClass->parent()->size() != 0) {
      parent = Unit::getClass(preClass->parent(), failIsFatal);
      if (parent == NULL) {
        if (failIsFatal) {
          raise_error("unknown class %s", preClass->parent()->data());
        }
        return NULL;
      }
    }

    VMExecutionContext* ec = g_vmContext;
    ActRec* fp = ec->getFP();
    PC pc = ec->getPC();

    bool needsFrame = ec->m_stack.top() &&
      (!fp || fp->m_func->unit() != preClass->unit());

    if (needsFrame) {
      /*
        we can be called from Unit::merge, which hasnt yet setup
        the frame (because often it doesnt need to).
        Set up a fake frame here, in case of errors.
        But note that mergeUnit is called for systemlib etc before the
        stack has been setup. So dont do anything if m_stack.top()
        is NULL
      */
      ActRec &tmp = *ec->m_stack.allocA();
      tmp.m_savedRbp = (uint64_t)fp;
      tmp.m_savedRip = 0;
      tmp.m_func = preClass->unit()->getMain();
      tmp.m_soff = preClass->getOffset() - tmp.m_func->base();
      tmp.setThis(NULL);
      tmp.m_varEnv = 0;
      tmp.initNumArgs(0);
      ec->m_fp = &tmp;
      ec->m_pc = preClass->unit()->at(preClass->getOffset());
      ec->pushLocalsAndIterators(tmp.m_func);
    }
    ClassPtr newClass(Class::newClass(preClass, parent));
    if (needsFrame) {
      ec->m_stack.top() = (Cell*)(ec->m_fp+1);
      ec->m_fp = fp;
      ec->m_pc = pc;
    }
    Lock l(Unit::s_classesMutex);
    /*
      We could re-enter via Unit::getClass() or class_->avail(), so
      no need for *clsList to be volatile
    */
    if (UNLIKELY(top != *clsList)) {
      top = *clsList;
      continue;
    }
    if (top) {
      newClass->m_cachedOffset = top->m_cachedOffset;
    } else {
      newClass->m_cachedOffset =
        Transl::TargetCache::allocKnownClass(preClass->name());
    }
    newClass->m_nextClass = top;
    Util::compiler_membar();
    *const_cast<Class**>(clsList) = newClass.get();
    newClass.get()->incAtomicCount();
    newClass.get()->setCached();
    DEBUGGER_ATTACHED_ONLY(phpDefClassHook(newClass.get()));
    return newClass.get();
  }
}
Пример #10
0
UnwindAction checkHandlers(const EHEnt* eh,
                           const ActRec* const fp,
                           PC& pc,
                           Fault& fault) {
  auto const func = fp->m_func;

  FTRACE(1, "checkHandlers: func {} ({})\n",
         func->fullName()->data(),
         func->unit()->filepath()->data());

  /*
   * This code is repeatedly called with the same offset when an
   * exception is raised and rethrown by fault handlers.  The
   * `faultNest' iterator is here to skip the EHEnt handlers that have
   * already been run for this in-flight exception.
   */
  int faultNest = 0;
  for (;;) {
    assert(faultNest <= fault.m_handledCount);
    if (faultNest == fault.m_handledCount) {
      ++fault.m_handledCount;

      switch (eh->m_type) {
      case EHEnt::Type::Fault:
        FTRACE(1, "checkHandlers: entering fault at {}: save {}\n",
               eh->m_fault,
               func->unit()->offsetOf(pc));
        fault.m_savedRaiseOffset = func->unit()->offsetOf(pc);
        pc = func->unit()->entry() + eh->m_fault;
        DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
        return UnwindAction::ResumeVM;
      case EHEnt::Type::Catch:
        // Note: we skip catch clauses if we have a pending C++ exception
        // as part of our efforts to avoid running more PHP code in the
        // face of such exceptions.
        if (fault.m_faultType == Fault::Type::UserException &&
            ThreadInfo::s_threadInfo->m_pendingException == nullptr) {
          auto const obj = fault.m_userException;
          for (auto& idOff : eh->m_catches) {
            FTRACE(1, "checkHandlers: catch candidate {}\n", idOff.second);
            auto handler = func->unit()->at(idOff.second);
            auto const cls = Unit::lookupClass(
              func->unit()->lookupNamedEntityId(idOff.first)
            );
            if (!cls || !obj->instanceof(cls)) continue;

            FTRACE(1, "checkHandlers: entering catch at {}\n", idOff.second);
            pc = handler;
            DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
            return UnwindAction::ResumeVM;
          }
        }
        break;
      }
    }

    if (eh->m_parentIndex != -1) {
      eh = &func->ehtab()[eh->m_parentIndex];
    } else {
      break;
    }
    ++faultNest;
  }

  return UnwindAction::Propagate;
}
Пример #11
0
Unit* lookupUnit(StringData* path, const char* currentDir, bool* initial_opt) {
  bool init;
  bool& initial = initial_opt ? *initial_opt : init;
  initial = true;

  /*
   * NB: the m_evaledFiles map is only for the debugger, and could be omitted
   * in RepoAuthoritative mode, but currently isn't.
   */

  struct stat s;
  auto const spath = resolveVmInclude(path, currentDir, &s);
  if (spath.isNull()) return nullptr;

  auto const eContext = g_context.getNoCheck();

  // Check if this file has already been included.
  auto it = eContext->m_evaledFiles.find(spath.get());
  if (it != end(eContext->m_evaledFiles)) {
    initial = false;
    return it->second;
  }

  // We didn't find it, so try the realpath.
  auto const alreadyResolved =
    RuntimeOption::RepoAuthoritative ||
    (!RuntimeOption::CheckSymLink && (spath[0] == '/'));
  bool hasRealpath = false;
  String rpath;
  if (!alreadyResolved) {
    std::string rp = StatCache::realpath(spath.data());
    if (rp.size() != 0) {
      rpath = StringData::Make(rp.data(), rp.size(), CopyString);
      if (!rpath.same(spath)) {
        hasRealpath = true;
        it = eContext->m_evaledFiles.find(rpath.get());
        if (it != eContext->m_evaledFiles.end()) {
          // We found it! Update the mapping for spath and return the
          // unit.
          auto const unit = it->second;
          spath.get()->incRefCount();
          eContext->m_evaledFiles[spath.get()] = unit;
          initial = false;
          return unit;
        }
      }
    }
  }

  // This file hasn't been included yet, so we need to parse the file
  auto const cunit = checkoutFile(hasRealpath ? rpath.get() : spath.get(), s);
  if (cunit.unit && initial_opt) {
    // if initial_opt is not set, this shouldn't be recorded as a
    // per request fetch of the file.
    if (RDS::testAndSetBit(cunit.rdsBitId)) {
      initial = false;
    }
    // if parsing was successful, update the mappings for spath and
    // rpath (if it exists).
    eContext->m_evaledFilesOrder.push_back(cunit.unit->filepath());
    eContext->m_evaledFiles[spath.get()] = cunit.unit;
    spath.get()->incRefCount();
    if (hasRealpath) {
      eContext->m_evaledFiles[rpath.get()] = cunit.unit;
      rpath.get()->incRefCount();
    }
    DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit.unit));
  }

  return cunit.unit;
}