hphp/runtime/base/program-functions.cpp (2,586 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/program-functions.h"
#include "hphp/runtime/base/apc-typed-value.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/backtrace.h"
#include "hphp/runtime/base/bespoke-array.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/code-coverage.h"
#include "hphp/runtime/base/config.h"
#include "hphp/runtime/base/exceptions.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/extended-logger.h"
#include "hphp/runtime/base/file-util.h"
#include "hphp/runtime/base/file-util-defs.h"
#include "hphp/runtime/base/hhprof.h"
#include "hphp/runtime/base/implicit-context.h"
#include "hphp/runtime/base/ini-setting.h"
#include "hphp/runtime/base/init-fini-node.h"
#include "hphp/runtime/base/member-reflection.h"
#include "hphp/runtime/base/memory-manager.h"
#include "hphp/runtime/base/perf-mem-event.h"
#include "hphp/runtime/base/php-globals.h"
#include "hphp/runtime/base/plain-file.h"
#include "hphp/runtime/base/runtime-error.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/stat-cache.h"
#include "hphp/runtime/base/stream-wrapper-registry.h"
#include "hphp/runtime/base/surprise-flags.h"
#include "hphp/runtime/base/thread-safe-setlocale.h"
#include "hphp/runtime/base/tracing.h"
#include "hphp/runtime/base/unit-cache.h"
#include "hphp/runtime/base/variable-serializer.h"
#include "hphp/runtime/debugger/debugger.h"
#include "hphp/runtime/debugger/debugger_client.h"
#include "hphp/runtime/debugger/debugger_hook_handler.h"
#include "hphp/runtime/ext/apc/ext_apc.h"
#include "hphp/runtime/ext/extension-registry.h"
#include "hphp/runtime/ext/json/ext_json.h"
#include "hphp/runtime/ext/server/ext_server.h"
#include "hphp/runtime/ext/std/ext_std_file.h"
#include "hphp/runtime/ext/std/ext_std_function.h"
#include "hphp/runtime/ext/std/ext_std_variable.h"
#include "hphp/runtime/ext/strobelight/ext_strobelight.h"
#include "hphp/runtime/ext/xenon/ext_xenon.h"
#include "hphp/runtime/ext/xhprof/ext_xhprof.h"
#include "hphp/runtime/server/admin-request-handler.h"
#include "hphp/runtime/server/cli-server.h"
#include "hphp/runtime/server/http-request-handler.h"
#include "hphp/runtime/server/http-server.h"
#include "hphp/runtime/server/log-writer.h"
#include "hphp/runtime/server/pagelet-server.h"
#include "hphp/runtime/server/replay-transport.h"
#include "hphp/runtime/server/rpc-request-handler.h"
#include "hphp/runtime/server/server-note.h"
#include "hphp/runtime/server/server-stats.h"
#include "hphp/runtime/server/warmup-request-handler.h"
#include "hphp/runtime/server/xbox-server.h"
#include "hphp/runtime/vm/debug/debug.h"
#include "hphp/runtime/vm/jit/code-cache.h"
#include "hphp/runtime/vm/jit/mcgen-translate.h"
#include "hphp/runtime/vm/jit/mcgen.h"
#include "hphp/runtime/vm/jit/prof-data-serialize.h"
#include "hphp/runtime/vm/jit/prof-data.h"
#include "hphp/runtime/vm/jit/tc.h"
#include "hphp/runtime/vm/jit/translator.h"
#include "hphp/runtime/vm/repo-file.h"
#include "hphp/runtime/vm/repo-global-data.h"
#include "hphp/runtime/vm/runtime-compiler.h"
#include "hphp/runtime/vm/treadmill.h"
#include "hphp/runtime/vm/unit-parser.h"
#include "hphp/util/alloc.h"
#include "hphp/util/arch.h"
#include "hphp/util/boot-stats.h"
#include "hphp/util/build-info.h"
#include "hphp/util/capability.h"
#include "hphp/util/compatibility.h"
#include "hphp/util/embedded-data.h"
#include "hphp/util/exception.h"
#include "hphp/util/hardware-counter.h"
#include "hphp/util/kernel-version.h"
#ifndef _MSC_VER
#include "hphp/util/light-process.h"
#endif
#include "hphp/util/managed-arena.h"
#include "hphp/util/maphuge.h"
#include "hphp/util/perf-event.h"
#include "hphp/util/process-exec.h"
#include "hphp/util/process.h"
#include "hphp/util/rds-local.h"
#include "hphp/util/service-data.h"
#include "hphp/util/shm-counter.h"
#include "hphp/util/stack-trace.h"
#include "hphp/util/sync-signal.h"
#include "hphp/util/timer.h"
#include "hphp/util/type-scan.h"
#include "hphp/zend/zend-math.h"
#include "hphp/zend/zend-string.h"
#include "hphp/zend/zend-strtod.h"
#include <folly/CPortability.h>
#include <folly/Portability.h>
#include <folly/Random.h>
#include <folly/Range.h>
#include <folly/Singleton.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/Libgen.h>
#include <folly/portability/Stdlib.h>
#include <folly/portability/Unistd.h>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/filesystem.hpp>
#ifndef FACEBOOK
// Needed on libevent2
#include <event2/thread.h>
#endif
#include <oniguruma.h>
// Onigurama defines UChar to unsigned char, but ICU4C defines it to signed
// 16-bit int. This is supposed to be resolved by ONIG_ESCAPE_UCHAR_COLLISION,
// however this isn't fully supported in 6.8.0 or 6.8.1.
//
// As of 2018-03-21, not in any release; hopefully will be in 6.8.2 - it's
// resolved by this commit:
//
// https://github.com/kkos/oniguruma/commit/e79406479b6be4a56e40ede6c1a87b51fba073a2
#undef UChar
#include <signal.h>
#include <libxml/parser.h>
#include <chrono>
#include <exception>
#include <fstream>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <vector>
#ifdef _MSC_VER
#include <windows.h>
#include <winuser.h>
#endif
using namespace boost::program_options;
using std::cout;
constexpr auto MAX_INPUT_NESTING_LEVEL = 64;
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
// Forward declarations.
/*
* XXX: VM process initialization is handled through a function
* pointer so libhphp_runtime.a can be linked into programs that don't
* actually initialize the VM.
*/
void (*g_vmProcessInit)();
void timezone_init();
void pcre_init();
void pcre_reinit();
///////////////////////////////////////////////////////////////////////////////
// helpers
struct ProgramOptions {
std::string mode;
std::vector<std::string> config;
std::vector<std::string> confStrings;
std::vector<std::string> iniStrings;
int port;
int portfd;
int sslportfd;
int admin_port;
std::string user;
std::string file;
std::string lint;
bool isTempFile;
int count;
bool noSafeAccessCheck;
std::vector<std::string> args;
std::string buildId;
std::string instanceId;
int xhprofFlags;
std::string show;
std::string parse;
int vsDebugPort;
std::string vsDebugDomainSocket;
bool vsDebugNoWait;
Eval::DebuggerClientOptions debugger_options;
};
struct StartTime {
StartTime() : startTime(time(nullptr)) {}
time_t startTime;
};
static bool registrationComplete = false;
static StartTime s_startTime;
static std::string tempFile;
std::vector<std::string> s_config_files;
std::vector<std::string> s_ini_strings;
time_t start_time() {
return s_startTime.startTime;
}
const StaticString
s_HPHP("HPHP"),
s_HHVM("HHVM"),
s_HHVM_JIT("HHVM_JIT"),
s_HHVM_ARCH("HHVM_ARCH"),
s_REQUEST_START_TIME("REQUEST_START_TIME"),
s_REQUEST_TIME("REQUEST_TIME"),
s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
s_SCRIPT_NAME("SCRIPT_NAME"),
s_PHP_SELF("PHP_SELF"),
s_argc("argc"),
s_argv("argv"),
s_PWD("PWD"),
s_HOSTNAME("HOSTNAME"),
s__SERVER("_SERVER"),
s__ENV("_ENV"),
s_toStringErr("(unable to call toString())");
static RDS_LOCAL(bool, s_sessionInitialized);
static void process_cmd_arguments(int argc, char **argv) {
php_global_set(s_argc, Variant(argc));
VecInit argvArray(argc);
for (int i = 0; i < argc; i++) {
argvArray.append(String(argv[i]));
}
php_global_set(s_argv, argvArray.toArray());
}
static void process_env_variables(
Array& variables,
char** envp,
const std::map<std::string, std::string>& envVariables
) {
for (auto const& kv : envVariables) {
String idx(kv.first);
auto const arrkey = variables.convertKey<IntishCast::Cast>(idx);
String str(kv.second);
variables.set(arrkey, make_tv<KindOfString>(str.get()));
}
for (char **env = envp; env && *env; env++) {
char *p = strchr(*env, '=');
if (p) {
String name(*env, p - *env, CopyString);
register_variable(variables, (char*)name.data(),
String(p + 1, CopyString));
}
}
}
void process_env_variables(Array& variables) {
process_env_variables(variables, environ, RuntimeOption::EnvVariables);
}
// Handle adding a variable to an array, supporting keys that look
// like array expressions (like 'FOO[][key1][k2]').
void register_variable(Array& variables, char *name, const Variant& value,
bool overwrite /* = true */) {
// ignore leading spaces in the variable name
char *var = name;
while (*var && *var == ' ') {
var++;
}
// ensure that we don't have spaces or dots in the variable name
// (not binary safe)
bool is_array = false;
char *ip = nullptr; // index pointer
char *p = var;
for (; *p; p++) {
if (*p == ' ' || *p == '.') {
*p = '_';
} else if (*p == '[') {
is_array = true;
ip = p;
*p = 0;
break;
}
}
int var_len = p - var;
if (var_len == 0) {
// empty variable name, or variable name with a space in it
return;
}
// The array pointer we're currently adding to. If we're doing a
// multi-dimensional set. Note that as we're essentially using this as an
// interior array pointer it's not safe to allow reentry here.
Array* symtable = &variables;
char* index = var;
int index_len = var_len;
if (is_array) {
int nest_level = 0;
while (true) {
if (++nest_level > MAX_INPUT_NESTING_LEVEL) {
Logger::Warning("Input variable nesting level exceeded");
return;
}
ip++;
char *index_s = ip;
int new_idx_len = 0;
if (isspace(*ip)) {
ip++;
}
if (*ip == ']') {
index_s = nullptr;
} else {
ip = strchr(ip, ']');
if (!ip) {
// PHP variables cannot contain '[' in their names,
// so we replace the character with a '_'
*(index_s - 1) = '_';
index_len = 0;
if (index) {
index_len = strlen(index);
}
goto plain_var;
}
*ip = 0;
new_idx_len = strlen(index_s);
}
if (!index) {
symtable->append(make_persistent_array_like_tv(ArrayData::CreateDict()));
auto const key = symtable->get()->getKey(symtable->get()->iter_last());
symtable = &asArrRef(symtable->lval(key));
} else {
String key_str(index, index_len, CopyString);
auto const key =
symtable->convertKey<IntishCast::Cast>(key_str.asTypedValue());
auto const v = symtable->lookup(key);
if (isNullType(v.type()) || !isArrayLikeType(v.type())) {
symtable->set(key, make_persistent_array_like_tv(ArrayData::CreateDict()));
}
symtable = &asArrRef(symtable->lval(key));
}
/* ip pointed to the '[' character, now obtain the key */
index = index_s;
index_len = new_idx_len;
ip++;
if (*ip == '[') {
is_array = true;
*ip = 0;
} else {
goto plain_var;
}
}
} else {
plain_var:
if (!index) {
symtable->append(value);
} else {
String key_str(index, index_len, CopyString);
auto key =
symtable->convertKey<IntishCast::Cast>(key_str.asTypedValue());
if (overwrite || !symtable->exists(key)) {
symtable->set(key, *value.asTypedValue(), true);
}
}
}
}
enum class ContextOfException {
ReqInit = 1,
Invoke,
Handler,
};
static void handle_exception_append_bt(std::string& errorMsg,
const ExtendedException& e) {
Array bt = e.getBacktrace();
if (!bt.empty()) {
errorMsg += ExtendedLogger::StringOfStackTrace(bt);
}
}
void bump_counter_and_rethrow(bool isPsp) {
try {
throw;
} catch (const RequestTimeoutException& e) {
if (isPsp) {
static auto requestTimeoutPSPCounter = ServiceData::createTimeSeries(
"requests_timed_out_psp", {ServiceData::StatsType::COUNT});
requestTimeoutPSPCounter->addValue(1);
ServerStats::Log("request.timed_out.psp", 1);
} else {
static auto requestTimeoutCounter = ServiceData::createTimeSeries(
"requests_timed_out_non_psp", {ServiceData::StatsType::COUNT});
requestTimeoutCounter->addValue(1);
ServerStats::Log("request.timed_out.non_psp", 1);
}
throw;
} catch (const RequestCPUTimeoutException& e) {
if (isPsp) {
static auto requestCPUTimeoutPSPCounter = ServiceData::createTimeSeries(
"requests_cpu_timed_out_psp", {ServiceData::StatsType::COUNT});
requestCPUTimeoutPSPCounter->addValue(1);
ServerStats::Log("request.cpu_timed_out.psp", 1);
} else {
static auto requestCPUTimeoutCounter = ServiceData::createTimeSeries(
"requests_cpu_timed_out_non_psp", {ServiceData::StatsType::COUNT});
requestCPUTimeoutCounter->addValue(1);
ServerStats::Log("request.cpu_timed_out.non_psp", 1);
}
throw;
} catch (const RequestMemoryExceededException& e) {
if (isPsp) {
static auto requestMemoryExceededPSPCounter =
ServiceData::createTimeSeries(
"requests_memory_exceeded_psp", {ServiceData::StatsType::COUNT});
requestMemoryExceededPSPCounter->addValue(1);
ServerStats::Log("request.memory_exceeded.psp", 1);
} else {
static auto requestMemoryExceededCounter = ServiceData::createTimeSeries(
"requests_memory_exceeded_non_psp", {ServiceData::StatsType::COUNT});
requestMemoryExceededCounter->addValue(1);
ServerStats::Log("request.memory_exceeded.non_psp", 1);
}
throw;
} catch (const RequestOOMKilledException& e) {
if (isPsp) {
static auto requestHostOOMPSPCounter = ServiceData::createTimeSeries(
"requests_oom_killed_psp", {ServiceData::StatsType::COUNT});
requestHostOOMPSPCounter->addValue(1);
ServerStats::Log("request.oom_killed.psp", 1);
} else {
static auto requestHostOOMCounter = ServiceData::createTimeSeries(
"requests_oom_killed_non_psp", {ServiceData::StatsType::COUNT});
requestHostOOMCounter->addValue(1);
ServerStats::Log("request.oom_killed.non_psp", 1);
}
if (RuntimeOption::EvalLogKilledRequests && StructuredLog::enabled()) {
StructuredLogEntry entry;
entry.setInt("mem_used", e.m_usedBytes);
entry.setInt("is_psp", static_cast<int>(isPsp));
if (g_context) {
entry.setStr("url", g_context->getRequestUrl());
}
StructuredLog::log("hhvm_oom_killed", entry);
}
throw;
}
}
static void handle_exception_helper(bool& ret,
ExecutionContext* context,
std::string& errorMsg,
ContextOfException where,
bool& error,
bool richErrorMsg) {
// Clear oom/timeout while handling exception and restore them afterwards.
auto& flags = stackLimitAndSurprise();
auto const origFlags = flags.fetch_and(~ResourceFlags) & ResourceFlags;
SCOPE_EXIT {
flags.fetch_or(origFlags);
};
try {
bump_counter_and_rethrow(false /* isPsp */);
} catch (const Eval::DebuggerException& e) {
throw;
} catch (const ExitException& e) {
if (where == ContextOfException::ReqInit) {
ret = false;
} else if (where != ContextOfException::Handler &&
!context->getExitCallback().isNull() &&
is_callable(context->getExitCallback())) {
Array stack = e.getBacktrace();
Array argv = make_vec_array(*rl_exit_code, stack);
vm_call_user_func(context->getExitCallback(), argv);
}
} catch (const PhpFileDoesNotExistException& e) {
ret = false;
if (where != ContextOfException::Handler) {
raise_notice(e.getMessage());
} else {
Logger::Error(e.getMessage());
}
if (richErrorMsg) {
handle_exception_append_bt(errorMsg, e);
}
} catch (const Exception& e) {
bool oldRet = ret;
bool origError = error;
std::string origErrorMsg = errorMsg;
ret = false;
error = true;
errorMsg = "";
if (where == ContextOfException::Handler) {
errorMsg = "Exception handler threw an exception: ";
}
errorMsg += e.what();
if (where == ContextOfException::Invoke) {
bool handlerRet = context->onFatalError(e);
if (handlerRet) {
ret = oldRet;
error = origError;
errorMsg = origErrorMsg;
}
} else {
Logger::Error(errorMsg);
}
if (richErrorMsg) {
auto const ee = dynamic_cast<const ExtendedException*>(&e);
if (ee) {
handle_exception_append_bt(errorMsg, *ee);
}
}
} catch (const Object& e) {
bool oldRet = ret;
bool origError = error;
auto const origErrorMsg = errorMsg;
ret = false;
error = true;
errorMsg = "";
if (where == ContextOfException::Handler) {
errorMsg = "Exception handler threw an object exception: ";
}
try {
errorMsg += throwable_to_string(e.get()).data();
} catch (...) {
errorMsg += s_toStringErr.data();
}
if (where == ContextOfException::Invoke) {
bool handlerRet = context->onUnhandledException(e);
if (handlerRet) {
ret = oldRet;
error = origError;
errorMsg = origErrorMsg;
}
} else {
Logger::Error(errorMsg);
}
} catch (...) {
ret = false;
error = true;
errorMsg = "(non-standard exception \"";
errorMsg += current_exception_name();
errorMsg += "\" was thrown)";
Logger::Error(errorMsg);
}
}
static bool hphp_chdir_file(const std::string& filename) {
bool ret = false;
String s = File::TranslatePath(filename);
char *buf = strndup(s.data(), s.size());
char *dir = dirname(buf);
assertx(dir);
if (dir) {
if (File::IsVirtualDirectory(dir)) {
g_context->setCwd(String(dir, CopyString));
ret = true;
} else {
struct stat sb;
stat(dir, &sb);
if ((sb.st_mode & S_IFMT) == S_IFDIR) {
ret = true;
if (*dir != '.') {
g_context->setCwd(String(dir, CopyString));
}
}
}
}
free(buf);
return ret;
}
static void handle_resource_exceeded_exception() {
try {
throw;
} catch (RequestTimeoutException&) {
RID().triggerTimeout(TimeoutTime);
} catch (RequestCPUTimeoutException&) {
RID().triggerTimeout(TimeoutCPUTime);
} catch (RequestMemoryExceededException&) {
setSurpriseFlag(MemExceededFlag);
RID().setRequestOOMFlag();
} catch (...) {}
}
void handle_destructor_exception(const char* situation) {
std::string errorMsg;
try {
throw;
} catch (ExitException& e) {
// ExitException is fine, no need to show a warning.
RI().setPendingException(e.clone());
return;
} catch (Object &e) {
// For user exceptions, invoke the user exception handler
errorMsg = situation;
errorMsg += " threw an object exception: ";
try {
errorMsg += throwable_to_string(e.get()).data();
} catch (...) {
handle_resource_exceeded_exception();
errorMsg += "(unable to call toString())";
}
} catch (Exception& e) {
RI().setPendingException(e.clone());
errorMsg = situation;
errorMsg += " raised a fatal error: ";
errorMsg += e.what();
} catch (...) {
errorMsg = situation;
errorMsg += " threw an unknown exception";
}
// For fatal errors and unknown exceptions, we raise a warning.
// If there is a user error handler it will be invoked, otherwise
// the default error handler will be invoked.
try {
raise_warning_unsampled(errorMsg);
} catch (...) {
handle_resource_exceeded_exception();
// The user error handler fataled or threw an exception,
// print out the error message directly to the log
Logger::Warning(errorMsg);
}
}
static RDS_LOCAL(rqtrace::Trace, tl_cmdTrace);
void init_command_line_session(int argc, char** argv) {
StackTraceNoHeap::AddExtraLogging("ThreadType", "CLI");
std::string args;
for (int i = 0; i < argc; i++) {
if (i) args += " ";
args += argv[i];
}
StackTraceNoHeap::AddExtraLogging("Arguments", args.c_str());
hphp_session_init(Treadmill::SessionKind::CLISession);
auto const context = g_context.getNoCheck();
context->obSetImplicitFlush(true);
if (RuntimeOption::EvalTraceCommandLineRequest) {
tl_cmdTrace.destroy();
context->setRequestTrace(tl_cmdTrace.getCheck());
}
}
void
init_command_line_globals(
int argc, char** argv, char** envp,
int xhprof,
const std::map<std::string, std::string>& serverVariables,
const std::map<std::string, std::string>& envVariables
) {
auto& variablesOrder = RID().getVariablesOrder();
if (variablesOrder.find('e') != std::string::npos ||
variablesOrder.find('E') != std::string::npos) {
auto envArr = Array::CreateDict();
process_env_variables(envArr, envp, envVariables);
envArr.set(s_HPHP, 1);
envArr.set(s_HHVM, 1);
if (RuntimeOption::EvalJit) {
envArr.set(s_HHVM_JIT, 1);
}
switch (arch()) {
case Arch::X64:
envArr.set(s_HHVM_ARCH, "x64");
break;
case Arch::ARM:
envArr.set(s_HHVM_ARCH, "arm");
break;
}
php_global_set(s__ENV, std::move(envArr));
}
process_cmd_arguments(argc, argv);
if (variablesOrder.find('s') != std::string::npos ||
variablesOrder.find('S') != std::string::npos) {
auto serverArr = Array::CreateDict();
process_env_variables(serverArr, envp, envVariables);
time_t now;
struct timeval tp = {0};
double now_double;
if (!gettimeofday(&tp, nullptr)) {
now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
now = tp.tv_sec;
} else {
now = time(nullptr);
now_double = (double)now;
}
String file = empty_string();
if (argc > 0) {
file = String::attach(StringData::Make(argv[0], CopyString));
}
serverArr.set(s_REQUEST_START_TIME, now);
serverArr.set(s_REQUEST_TIME, now);
serverArr.set(s_REQUEST_TIME_FLOAT, now_double);
serverArr.set(s_DOCUMENT_ROOT, empty_string_tv());
serverArr.set(s_SCRIPT_FILENAME, file);
serverArr.set(s_SCRIPT_NAME, file);
serverArr.set(s_PHP_SELF, file);
serverArr.set(s_argv, php_global(s_argv));
serverArr.set(s_argc, php_global(s_argc));
serverArr.set(s_PWD, g_context->getCwd());
char hostname[1024];
if (RuntimeOption::ServerExecutionMode() &&
!is_cli_server_mode() &&
!gethostname(hostname, sizeof(hostname))) {
// gethostname may not null-terminate
hostname[sizeof(hostname) - 1] = '\0';
serverArr.set(s_HOSTNAME, String(hostname, CopyString));
}
for (auto const& kv : serverVariables) {
serverArr.set(String{kv.first}, String{kv.second});
}
php_global_set(s__SERVER, std::move(serverArr));
}
if (xhprof) {
HHVM_FN(xhprof_enable)(xhprof, null_array);
}
if (RuntimeOption::RequestTimeoutSeconds) {
RID().setTimeout(RuntimeOption::RequestTimeoutSeconds);
}
if (RuntimeOption::XenonForceAlwaysOn) {
Xenon::getInstance().surpriseAll();
}
// Initialize the debugger
DEBUGGER_ATTACHED_ONLY(phpDebuggerRequestInitHook());
}
void execute_command_line_begin(int argc, char **argv, int xhprof) {
init_command_line_session(argc, argv);
init_command_line_globals(argc, argv, environ, xhprof,
RuntimeOption::ServerVariables,
RuntimeOption::EnvVariables);
}
void execute_command_line_end(int xhprof, bool coverage, const char *program) {
if (xhprof) {
Variant profileData = HHVM_FN(xhprof_disable)();
if (!profileData.isNull()) {
HHVM_FN(var_dump)(Variant::attach(
HHVM_FN(json_encode)(HHVM_FN(xhprof_disable)())
));
}
}
auto& ti = RI();
if (coverage && ti.m_reqInjectionData.getCoverage() &&
!RuntimeOption::CodeCoverageOutputFile.empty()) {
ti.m_coverage.dumpOnExit();
}
g_context->onShutdownPostSend(); // runs more php
Eval::Debugger::InterruptPSPEnded(program);
hphp_context_exit();
hphp_session_exit();
}
#if defined(__APPLE__) || defined(_MSC_VER)
const void* __hot_start = nullptr;
const void* __hot_end = nullptr;
#define AT_END_OF_TEXT
#else
#define AT_END_OF_TEXT __attribute__((__section__(".stub")))
#endif
#define ALIGN_HUGE_PAGE __attribute__((__aligned__(2 * 1024 * 1024)))
#if defined(__has_attribute) && __has_attribute(__no_builtin__)
#define NO_BUILTIN_MEMCPY __attribute__((__no_builtin__("memcpy")))
#else
#define NO_BUILTIN_MEMCPY
#endif
#if defined(__has_attribute) && __has_attribute(__optimize__)
#define OPTIMIZE_2 __attribute__((__optimize__("2")))
#else
#define OPTIMIZE_2
#endif
static void
NEVER_INLINE AT_END_OF_TEXT NO_BUILTIN_MEMCPY OPTIMIZE_2
hugifyMemcpy(uint64_t* dst, uint64_t* src, size_t sz) {
for (size_t cnt = 0; cnt < sz; cnt += sizeof(uint64_t)) {
*dst++ = *src++;
}
}
static void
NEVER_INLINE AT_END_OF_TEXT ALIGN_HUGE_PAGE OPTIMIZE_2
EXTERNALLY_VISIBLE
hugifyText(char* from, char* to) {
#if !FOLLY_SANITIZE && defined MADV_HUGEPAGE
if (from > to || (to - from) < sizeof(uint64_t)) {
// This shouldn't happen if HHVM is behaving correctly (I think),
// but if it does then there is nothing to do and we should bail
// out early because the call to wordcpy() below can't handle
// zero size or negative sizes.
return;
}
size_t sz = to - from;
#ifdef FACEBOOK
if (RuntimeOption::EvalNewTHPHotText) {
auto const hasKernelSupport = [] () -> bool {
KernelVersion version;
if (version.m_major < 5) return false;
if (version.m_major > 5) return true;
if (version.m_minor > 2) return true;
if ((version.m_minor == 2) && (version.m_fbk >= 5)) return true;
return false;
};
if (hasKernelSupport()) {
// The new way doesn't work if the region is locked. Note that this means
// Server.LockCodeMemory won't be applied to the region--there is no
// guarantee that the region would stay in memory, especially if the
// kernel fails to find huge pages for us.
munlock(from, sz);
madvise(from, sz, MADV_HUGEPAGE);
return;
}
}
#endif
void* mem = malloc(sz);
memcpy(mem, from, sz);
// This maps out a portion of our executable
// We need to be very careful about what we do
// until we replace the original code
mmap(from, sz,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1, 0);
// This is in glibc, which isn't a problem, except for
// the trampoline code in .plt, which we dealt with
// in the linker script
madvise(from, sz, MADV_HUGEPAGE);
// Don't use memcpy because its probably one of the
// functions thats been mapped out.
// Needs the attribute((optimize("2")) to prevent
// g++ from turning this back into memcpy(!)
hugifyMemcpy((uint64_t*)from, (uint64_t*)mem, sz);
mprotect(from, sz, PROT_READ | PROT_EXEC);
free(mem);
mlock(from, to - from);
Debug::DebugInfo::setPidMapOverlay(from, to);
std::stringstream ss;
ss << "Mapped text section onto huge pages from " <<
std::hex << (uint64_t*)from << " to " << (uint64_t*)to;
Logger::Info(ss.str());
#endif
}
static void pagein_self(void) {
#if defined(USE_JEMALLOC) && (JEMALLOC_VERSION_MAJOR >= 5)
// jemalloc 5 has background threads, which handle purging asynchronously.
bool background_threads = false;
if (mallctlRead<bool, true>("background_thread", &background_threads)) {
background_threads = false;
Logger::Warning("Failed to determine jemalloc background thread state");
}
if (background_threads &&
mallctlWrite<bool, true>("background_thread", false)) {
Logger::Warning("Failed to disable jemalloc background threads");
}
SCOPE_EXIT {
if (background_threads &&
mallctlWrite<bool, true>("background_thread", true)) {
Logger::Warning("Failed to enable jemalloc background threads");
}
};
#endif
// Other than the jemalloc background threads, which should've been stopped by
// now, the only thread allowed here is the current one. Check that and alarm
// people when they accidentally created threads before this point.
int numTry = 0;
while (Process::GetNumThreads() > 1) {
if (numTry < 3) {
numTry++;
usleep(1000);
} else {
break;
}
}
auto nThreads = Process::GetNumThreads();
if (nThreads > 1) {
Logger::Error("%d threads running, cannot hugify text!", nThreads);
fprintf(stderr,
"HHVM is broken: %u threads running in hugifyText()!\n",
nThreads);
if (debug) {
throw std::runtime_error{
"you cannot create threads before pagein_self"
};
}
}
#ifdef __linux__
if (RO::ServerSchedPolicy >= 0 && RO::ServerSchedPolicy <= SCHED_BATCH) {
sched_param param{};
if (RO::ServerSchedPolicy == SCHED_RR) {
param.sched_priority =
std::max(0, sched_get_priority_min(RO::ServerSchedPolicy));
}
auto const ret = sched_setscheduler(0, RO::ServerSchedPolicy, ¶m);
if (ret) {
Logger::Error("failed to adjust scheduling priority: " +
folly::errnoStr(errno));
} else {
Logger::Info("successfully adjusted scheduling priority to %d",
RO::ServerSchedPolicy);
}
}
if (RO::ServerSchedPriority) {
auto const ret = setpriority(PRIO_PROCESS, 0, RO::ServerSchedPriority);
if (ret) {
Logger::FError("failed to setpriority to {}: {}", RO::ServerSchedPriority,
folly::errnoStr(errno));
}
}
#endif
auto mapped_huge = false;
#ifdef __linux__
auto const try_map_huge =
hugePagesSupported() &&
RuntimeOption::EvalMaxHotTextHugePages > 0 &&
(char*)__hot_start != nullptr && (char*)__hot_end != nullptr &&
nThreads <= 1;
SCOPE_EXIT {
if (try_map_huge != mapped_huge) {
Logger::Warning("Failed to hugify the .text section");
}
};
#else
// MacOS doesn't have transparent huge pages. It uses mmap() with
// VM_FLAGS_SUPERPAGE_SIZE_2MB, which we don't do here, so don't bother.
auto constexpr try_map_huge = false;
#endif
char mapname[PATH_MAX];
// pad due to the spaces between the inode number and the mapname
auto const bufsz =
sizeof(unsigned long) * 4 + sizeof(mapname) + sizeof(char) * 11 + 100;
auto buf = static_cast<char*>(malloc(bufsz));
if (auto fp = fopen("/proc/self/maps", "r")) {
while (!feof(fp)) {
if (fgets(buf, bufsz, fp) == 0)
break;
unsigned long begin, end, inode, pgoff;
char perm[5];
char dev[11];
int r = sscanf(buf, "%lx-%lx %4s %lx %10s %ld %s",
&begin, &end, perm, &pgoff, dev, &inode, mapname);
// page in read-only segments that correspond to a file on disk
if (r != 7 ||
perm[0] != 'r' ||
perm[1] != '-' ||
access(mapname, F_OK) != 0) {
continue;
}
auto beginPtr = (char*)begin;
auto endPtr = (char*)end;
auto hotStart = (char*)__hot_start;
auto hotEnd = (char*)__hot_end;
const size_t hugePageBytes = 2L * 1024 * 1024;
if (mlock(beginPtr, end - begin) == 0) {
if (try_map_huge && beginPtr <= hotStart && hotEnd <= endPtr) {
char* from = hotStart - ((intptr_t)hotStart & (hugePageBytes - 1));
char* to = hotEnd + (hugePageBytes - 1);
to -= (intptr_t)to & (hugePageBytes - 1);
const size_t maxHugeHotTextBytes =
RuntimeOption::EvalMaxHotTextHugePages * hugePageBytes;
if (to - from > maxHugeHotTextBytes) {
to = from + maxHugeHotTextBytes;
}
// Check that hugifyText() does not start in hot text.
if (to <= (void*)hugifyText || from > (void*)hugifyText) {
mapped_huge = true;
hugifyText(from, to);
}
}
if (!RuntimeOption::LockCodeMemory) {
munlock(beginPtr, end - begin);
}
}
}
fclose(fp);
}
free(buf);
}
/* Sets RuntimeOption::ExecutionMode according to commandline options prior to
* config load. Returns false upon unrecognized mode.
*/
static bool set_execution_mode(folly::StringPiece mode) {
if (mode == "daemon" || mode == "server" || mode == "replay") {
RuntimeOption::ServerMode = true;
Logger::Escape = true;
return true;
} else if (mode == "run" || mode == "debug" || mode == "translate" ||
mode == "dumphhas" || mode == "verify" || mode == "vsdebug" ||
mode == "getoption" || mode == "eval" || mode == "dumpcoverage") {
// We don't run PHP in "translate" mode, so just treat it like cli mode.
RuntimeOption::ServerMode = false;
Logger::Escape = false;
return true;
}
// Invalid mode.
return false;
}
static void init_repo_file() {
if (!RO::RepoAuthoritative) return;
assertx(!RO::RepoPath.empty());
RepoFile::init(RO::RepoPath);
}
/* Reads a file into the OS page cache, with rate limiting. */
static bool readahead_rate(const char* path, int64_t mbPerSec) {
int ret = open(path, O_RDONLY);
if (ret < 0) return false;
const int fd = ret;
SCOPE_EXIT { close(fd); };
constexpr size_t kReadaheadBytes = 1 << 20;
std::unique_ptr<char[]> buf(new char[kReadaheadBytes]);
int64_t total = 0;
auto startTime = std::chrono::steady_clock::now();
do {
ret = read(fd, buf.get(), kReadaheadBytes);
if (ret > 0) {
total += ret;
// Unit math: bytes / (MB / seconds) = microseconds
auto endTime = startTime + std::chrono::microseconds(total / mbPerSec);
auto sleepT = endTime - std::chrono::steady_clock::now();
// Don't sleep too frequently.
if (sleepT >= std::chrono::seconds(1)) {
Logger::Info(folly::sformat(
"readahead sleeping {}ms after total {}b",
std::chrono::duration_cast<std::chrono::milliseconds>(sleepT).count(),
total));
/* sleep override */ std::this_thread::sleep_for(sleepT);
}
}
} while (ret > 0);
return ret == 0;
}
static int start_server(const std::string &username, int xhprof) {
if (!registrationComplete) {
folly::SingletonVault::singleton()->registrationComplete();
registrationComplete = true;
}
BootStats::start();
HttpServer::CheckMemAndWait();
InitFiniNode::ServerPreInit();
if (!RuntimeOption::EvalUnixServerPath.empty()) {
init_cli_server(RuntimeOption::EvalUnixServerPath.c_str());
}
// Before we start the webserver, make sure the entire
// binary is paged into memory.
pagein_self();
BootStats::mark("pagein_self");
set_execution_mode("server");
#if !defined(SKIP_USER_CHANGE)
if (!username.empty()) {
if (Logger::UseCronolog) {
for (const auto& el : RuntimeOption::ErrorLogs) {
Cronolog::changeOwner(username, el.second.symLink);
}
}
if (!Capability::ChangeUnixUser(username, RuntimeOption::AllowRunAsRoot)) {
_exit(1);
}
LightProcess::ChangeUser(username);
} else if (getuid() == 0 && !RuntimeOption::AllowRunAsRoot) {
Logger::Error("hhvm not allowed to run as root unless "
"-vServer.AllowRunAsRoot=1 is used.");
_exit(1);
}
Capability::SetDumpable();
#endif
// Include hugetlb pages in core dumps.
Process::SetCoreDumpHugePages();
init_repo_file();
BootStats::mark("init_repo_file");
hphp_process_init();
SCOPE_EXIT {
hphp_process_exit();
Logger::Info("all servers stopped");
Logger::FlushAll();
};
HttpRequestHandler::GetAccessLog().init
(RuntimeOption::AccessLogDefaultFormat, RuntimeOption::AccessLogs,
username);
AdminRequestHandler::GetAccessLog().init
(RuntimeOption::AdminLogFormat, RuntimeOption::AdminLogSymLink,
RuntimeOption::AdminLogFile,
username);
RPCRequestHandler::GetAccessLog().init
(RuntimeOption::AccessLogDefaultFormat, RuntimeOption::RPCLogs,
username);
SCOPE_EXIT {
Logger::FlushAll();
HttpRequestHandler::GetAccessLog().fini();
AdminRequestHandler::GetAccessLog().fini();
RPCRequestHandler::GetAccessLog().fini();
};
if (RuntimeOption::ServerInternalWarmupThreads > 0) {
HttpServer::CheckMemAndWait();
InitFiniNode::WarmupConcurrentStart(
RuntimeOption::ServerInternalWarmupThreads);
}
HttpServer::CheckMemAndWait();
// Create the HttpServer before any warmup requests to properly
// initialize the process
HttpServer::Server = std::make_shared<HttpServer>();
if (xhprof) {
HHVM_FN(xhprof_enable)(xhprof, uninit_null().toArray());
}
std::unique_ptr<std::thread> readaheadThread;
if (RO::RepoAuthoritative &&
RO::RepoLocalReadaheadRate > 0 &&
!RO::RepoPath.empty()) {
HttpServer::CheckMemAndWait();
readaheadThread = std::make_unique<std::thread>([&] {
assertx(RuntimeOption::ServerExecutionMode());
BootStats::Block timer("Readahead Repo", true);
auto path = RuntimeOption::RepoPath.c_str();
Logger::Info("readahead %s", path);
#ifdef __linux__
// glibc doesn't have a wrapper for ioprio_set(), so we need to use
// syscall(). The constants here are consistent with the kernel source.
// See http://lxr.free-electrons.com/source/include/linux/ioprio.h
auto constexpr IOPRIO_CLASS_SHIFT = 13;
enum {
IOPRIO_CLASS_NONE,
IOPRIO_CLASS_RT,
IOPRIO_CLASS_BE,
IOPRIO_CLASS_IDLE,
};
// Set to lowest IO priority.
constexpr int ioprio = (IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT);
// ioprio_set() is available starting kernel 2.6.13
KernelVersion version;
if (version.m_major > 2 ||
(version.m_major == 2 &&
(version.m_minor > 6 ||
(version.m_minor == 6 && version.m_release >= 13)))) {
syscall(SYS_ioprio_set,
1 /* IOPRIO_WHO_PROCESS, in fact, it is this thread */,
0 /* current thread */,
ioprio);
}
#endif
const auto mbPerSec = RuntimeOption::RepoLocalReadaheadRate;
if (!readahead_rate(path, mbPerSec)) {
Logger::Error("readahead failed: %s", strerror(errno));
}
});
if (!RuntimeOption::RepoLocalReadaheadConcurrent) {
// TODO(10152762): Run this concurrently with non-disk warmup.
readaheadThread->join();
readaheadThread.reset();
}
}
if (RuntimeOption::ServerInternalWarmupThreads > 0) {
BootStats::Block timer("concurrentWaitForEnd", true);
InitFiniNode::WarmupConcurrentWaitForEnd();
}
// If we have any warmup requests, replay them before listening for
// real connections
{
Logger::Info("Warming up");
if (!RuntimeOption::EvalJitProfileWarmupRequests) profileWarmupStart();
SCOPE_EXIT { profileWarmupEnd(); };
InternalWarmupRequestPlayer(RuntimeOption::ServerWarmupThreadCount,
RuntimeOption::ServerDedupeWarmupRequests)
.runAfterDelay(RuntimeOption::ServerWarmupRequests);
}
BootStats::mark("warmup");
if (RuntimeOption::StopOldServer) HttpServer::StopOldServer();
if (RuntimeOption::EvalEnableNuma) {
purge_all();
enable_numa();
BootStats::mark("enable_numa");
}
HttpServer::CheckMemAndWait(true); // Final wait
if (readaheadThread.get()) {
readaheadThread->join();
readaheadThread.reset();
}
if (!RuntimeOption::EvalUnixServerPath.empty()) {
start_cli_server();
}
if (jit::mcgen::retranslateAllScheduled()) {
// We ran retranslateAll from deserialized profile.
BootStats::Block timer("waitForRetranslateAll", true);
jit::mcgen::joinWorkerThreads();
}
#ifdef USE_JEMALLOC
// Eventually, we are going to remove options Eval.Num1GPagesForSlabs and
// Eval.Num2MPagesForSlabs, and use the ForReqHeap spec together with
// Eval.NumReservedSlabs. For now, we keep the old options working.
auto const reqHeapSpec = PageSpec{
std::max(RuntimeOption::EvalNum1GPagesForReqHeap,
RuntimeOption::EvalNum1GPagesForSlabs),
std::max(RuntimeOption::EvalNum2MPagesForReqHeap,
RuntimeOption::EvalNum2MPagesForSlabs)
};
auto const nSlabs =
std::max(RuntimeOption::EvalNumReservedSlabs,
RuntimeOption::EvalNum2MPagesForSlabs +
512 * RuntimeOption::EvalNum1GPagesForSlabs);
setup_local_arenas(reqHeapSpec, nSlabs);
#endif
HttpServer::Server->runOrExitProcess();
HttpServer::Server.reset();
return 0;
}
static void logSettings() {
if (RuntimeOption::ServerLogSettingsOnStartup) {
Logger::Info("Settings: %s\n", IniSetting::GetAllAsJSON().c_str());
}
}
static InitFiniNode s_logSettings(logSettings, InitFiniNode::When::ServerInit);
std::string translate_stack(const char *hexencoded, bool with_frame_numbers) {
if (!hexencoded || !*hexencoded) {
return "";
}
StackTrace st(hexencoded);
std::vector<std::shared_ptr<StackFrameExtra>> frames;
st.get(frames);
std::ostringstream out;
for (size_t i = 0; i < frames.size(); i++) {
auto f = frames[i];
if (with_frame_numbers) {
out << "# " << (i < 10 ? " " : "") << i << ' ';
}
out << f->toString();
out << '\n';
}
return out.str();
}
///////////////////////////////////////////////////////////////////////////////
static void prepare_args(int &argc,
char **&argv,
const std::vector<std::string> &args,
const char *file) {
argv = (char **)malloc((args.size() + 2) * sizeof(char*));
argc = 0;
if (file && *file) {
argv[argc++] = (char*)file;
}
for (int i = 0; i < (int)args.size(); i++) {
argv[argc++] = (char*)args[i].c_str();
}
argv[argc] = nullptr;
}
static int execute_program_impl(int argc, char **argv);
int execute_program(int argc, char **argv) {
int ret_code = -1;
try {
try {
ret_code = execute_program_impl(argc, argv);
} catch (const Exception& e) {
Logger::Error("Uncaught exception: %s", e.what());
throw;
} catch (const std::exception& e) {
Logger::Error("Uncaught exception: %s", e.what());
throw;
} catch (...) {
Logger::Error("Uncaught exception: (unknown)");
throw;
}
if (tempFile.length() && boost::filesystem::exists(tempFile)) {
boost::filesystem::remove(tempFile);
}
} catch (...) {
if (HttpServer::Server ||
folly::SingletonVault::singleton()->livingSingletonCount()) {
// an exception was thrown that prevented proper shutdown. Its not
// safe to destroy the globals, or run atexit handlers.
// abort() so it shows up as a crash, and we can diagnose/fix the
// exception
abort();
}
}
return ret_code;
}
static bool open_server_log_files() {
bool openedLog = false;
for (const auto& el : RuntimeOption::ErrorLogs) {
bool ok = true;
const auto& name = el.first;
const auto& errlog = el.second;
if (!errlog.logFile.empty()) {
if (errlog.isPipeOutput()) {
auto output = popen(errlog.logFile.substr(1).c_str(), "w");
ok = (output != nullptr);
Logger::SetOutput(name, output, true);
} else if (Logger::UseCronolog && errlog.hasTemplate()) {
auto cronoLog = Logger::CronoOutput(name);
always_assert(cronoLog);
cronoLog->m_template = errlog.logFile;
cronoLog->setPeriodicity();
if (errlog.periodMultiplier) {
cronoLog->m_periodMultiple = errlog.periodMultiplier;
}
cronoLog->m_linkName = errlog.symLink;
} else {
auto output = fopen(errlog.logFile.c_str(), "a");
ok = (output != nullptr);
Logger::SetOutput(name, output, false);
}
if (!ok) Logger::Error("Can't open log file: %s", errlog.logFile.c_str());
openedLog |= ok;
}
}
return openedLog;
}
static int compute_hhvm_argc(const options_description& desc,
int argc, char** argv) {
enum ArgCode {
NO_ARG = 0,
ARG_REQUIRED = 1,
ARG_OPTIONAL = 2
};
const auto& vec = desc.options();
std::map<std::string,ArgCode> long_options;
std::map<std::string,ArgCode> short_options;
// Build lookup maps for the short options and the long options
for (unsigned i = 0; i < vec.size(); ++i) {
auto opt = vec[i];
auto long_name = opt->long_name();
ArgCode code = NO_ARG;
if (opt->semantic()->max_tokens() == 1) {
if (opt->semantic()->min_tokens() == 1) {
code = ARG_REQUIRED;
} else {
code = ARG_OPTIONAL;
}
}
long_options[long_name] = code;
auto format_name = opt->format_name();
if (format_name.size() >= 2 && format_name[0] == '-' &&
format_name[1] != '-') {
auto short_name = format_name.substr(1,1);
short_options[short_name] = code;
}
}
// Loop over the args
int pos = 1;
while (pos < argc) {
const char* str = argv[pos];
int len = strlen(str);
if (len == 2 && memcmp(str, "--", 2) == 0) {
// We found "--". All args after this are intended for the
// PHP application
++pos;
break;
}
if (len >= 3 && str[0] == '-' && str[1] == '-') {
// Handle long options
++pos;
std::string s(str+2);
auto it = long_options.find(s);
if (it != long_options.end() && it->second != NO_ARG && pos < argc &&
(it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
++pos;
}
} else if (len >= 2 && str[0] == '-') {
// Handle short options
++pos;
std::string s;
s.append(1, str[1]);
auto it = short_options.find(s);
if (it != short_options.end() && it->second != 0 && len == 2 &&
pos < argc && (it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
++pos;
}
} else {
// We've found a non-option argument. This arg and all args
// that follow are intended for the PHP application
break;
}
}
return pos;
}
/*
* alloc.h defines a minimum C++ stack size but that only applies to threads we
* manually create. When the main thread will be executing PHP rather than just
* managing a server, make sure its stack is big enough.
*/
static void set_stack_size() {
struct rlimit rlim;
if (getrlimit(RLIMIT_STACK, &rlim) != 0) return;
if (rlim.rlim_cur < kStackSizeMinimum || rlim.rlim_cur == RLIM_INFINITY) {
#ifdef _WIN32
Logger::Error("stack limit too small, use peflags -x to increase %zd\n",
kStackSizeMinimum);
#else
rlim.rlim_cur = kStackSizeMinimum;
if (setrlimit(RLIMIT_STACK, &rlim)) {
Logger::Error("failed to set stack limit to %zd\n", kStackSizeMinimum);
}
#endif
}
}
std::vector<int> get_executable_lines(const Unit* compiled) {
std::vector<int> lines;
compiled->forEachFunc([&](const Func* func) {
auto const lineTable = func->getOrLoadLineTableCopy();
lines.reserve(lines.size() + lineTable.size());
for (auto& ent : lineTable) lines.push_back(ent.val());
return false;
});
std::sort(lines.begin(), lines.end());
auto const last = std::unique(lines.begin(), lines.end());
lines.erase(last, lines.end());
return lines;
}
static int execute_program_impl(int argc, char** argv) {
std::string usage = "Usage:\n\n ";
usage += argv[0];
usage += " [-m <mode>] [<options>] [<arg1>] [<arg2>] ...\n\nOptions";
ProgramOptions po;
options_description desc(usage.c_str());
desc.add_options()
("help", "display this message")
("version", "display version number")
("modules", "display modules")
("info", "PHP information")
("php", "emulate the standard php command line")
("compiler-id", "display the git hash for the compiler")
("repo-schema", "display the repository schema id")
("mode,m", value<std::string>(&po.mode)->default_value("run"),
"run | debug (d) | vsdebug | server (s) | daemon | replay | "
"translate (t) | verify | getoption | eval")
("interactive,a", "Shortcut for --mode debug") // -a is from PHP5
("config,c", value<std::vector<std::string>>(&po.config)->composing(),
"load specified config file")
("config-value,v",
value<std::vector<std::string>>(&po.confStrings)->composing(),
"individual configuration string in a format of name=value, where "
"name can be any valid configuration for a config file")
("define,d", value<std::vector<std::string>>(&po.iniStrings)->composing(),
"define an ini setting in the same format ( foo[=bar] ) as provided in a "
".ini file")
("no-config", "don't use the default php.ini")
("port,p", value<int>(&po.port)->default_value(-1),
"start an HTTP server at specified port")
("port-fd", value<int>(&po.portfd)->default_value(-1),
"use specified fd instead of creating a socket")
("ssl-port-fd", value<int>(&po.sslportfd)->default_value(-1),
"use specified fd for SSL instead of creating a socket")
("admin-port", value<int>(&po.admin_port)->default_value(-1),
"start admin listener at specified port")
("debug-config", value<std::string>(&po.debugger_options.configFName),
"load specified debugger config file")
("debug-host,h",
value<std::string>(&po.debugger_options.host)->implicit_value("localhost"),
"connect to debugger server at specified address")
("debug-port", value<int>(&po.debugger_options.port)->default_value(-1),
"connect to debugger server at specified port")
("debug-extension", value<std::string>(&po.debugger_options.extension),
"PHP file that extends command 'arg'")
("debug-cmd", value<std::vector<std::string>>(
&po.debugger_options.cmds)->composing(),
"executes this debugger command and returns its output in stdout")
("debug-sandbox",
value<std::string>(&po.debugger_options.sandbox)->default_value("default"),
"initial sandbox to attach to when debugger is started")
("user,u", value<std::string>(&po.user),
"run server under this user account")
("file,f", value<std::string>(&po.file),
"execute specified file")
("lint,l", value<std::string>(&po.lint),
"lint specified file")
("show,w", value<std::string>(&po.show),
"output specified file and do nothing else")
("check-repo", "attempt to load repo and then exit")
("temp-file",
"file specified is temporary and removed after execution")
("count", value<int>(&po.count)->default_value(1),
"how many times to repeat execution")
("no-safe-access-check",
value<bool>(&po.noSafeAccessCheck)->default_value(false),
"whether to ignore safe file access check")
("arg", value<std::vector<std::string>>(&po.args)->composing(),
"arguments")
("extra-header", value<std::string>(&Logger::ExtraHeader),
"extra-header to add to log lines")
("build-id", value<std::string>(&po.buildId),
"unique identifier of compiled server code")
("instance-id", value<std::string>(&po.instanceId),
"unique identifier of server instance")
("xhprof-flags", value<int>(&po.xhprofFlags)->default_value(0),
"Set XHProf flags")
("vsDebugPort", value<int>(&po.vsDebugPort)->default_value(-1),
"Debugger TCP port to listen on for the VS Code debugger extension")
("vsDebugDomainSocketPath",
value<std::string>(&po.vsDebugDomainSocket)->default_value(""),
"Debugger port to listen on for the VS Code debugger extension")
("vsDebugNoWait", value<bool>(&po.vsDebugNoWait)->default_value(false),
"Indicates the debugger should not block script startup waiting for "
"a debugger client to attach. Only applies if vsDebugPort or "
"vsDebugDomainSocketPath is specified.")
;
positional_options_description p;
p.add("arg", -1);
variables_map vm;
// Before invoking the boost command line parser, we do a manual pass
// to find the first occurrence of either "--" or a non-option argument
// in order to determine which arguments should be consumed by HHVM and
// which arguments should be passed along to the PHP application. This
// is necessary so that the boost command line parser doesn't choke on
// args intended for the PHP application.
int hhvm_argc = compute_hhvm_argc(desc, argc, argv);
// Need to have a parent try for opts so I can use opts in the catch of
// one of the sub-tries below.
try {
// Invoke the boost command line parser to parse the args for HHVM.
auto opts = command_line_parser(hhvm_argc, argv)
.options(desc)
.positional(p)
// If these style options are changed, compute_hhvm_argc() will
// need to be updated appropriately
.style(command_line_style::default_style &
~command_line_style::allow_guessing &
~command_line_style::allow_sticky &
~command_line_style::long_allow_adjacent)
.run();
try {
// Manually append the args for the PHP application.
int pos = 0;
for (unsigned m = 0; m < opts.options.size(); ++m) {
const auto& bo = opts.options[m];
if (bo.string_key == "arg") {
++pos;
}
}
for (unsigned m = hhvm_argc; m < argc; ++m) {
std::string str = argv[m];
basic_option<char> bo;
bo.string_key = "arg";
bo.position_key = pos++;
bo.value.push_back(str);
bo.original_tokens.push_back(str);
bo.unregistered = false;
bo.case_insensitive = false;
opts.options.push_back(bo);
}
// Process the options
store(opts, vm);
notify(vm);
if (vm.count("interactive") /* or -a */) po.mode = "debug";
else if (po.mode.empty()) po.mode = "run";
else if (po.mode == "d") po.mode = "debug";
else if (po.mode == "s") po.mode = "server";
else if (po.mode == "t") po.mode = "translate";
if (!set_execution_mode(po.mode)) {
Logger::Error("Error in command line: invalid mode: %s",
po.mode.c_str());
cout << desc << "\n";
return -1;
}
if (po.config.empty() && !vm.count("no-config")
&& ::getenv("HHVM_NO_DEFAULT_CONFIGS") == nullptr) {
auto file_callback = [&po] (const char *filename) {
Logger::Verbose("Using default config file: %s", filename);
po.config.push_back(filename);
};
add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/php*.ini",
file_callback);
add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/config*.hdf",
file_callback);
}
const auto env_config = ::getenv("HHVM_CONFIG_FILE");
if (env_config != nullptr) {
add_default_config_files_globbed(
env_config,
[&po](const char* filename) {
Logger::Verbose("Using config file from environment: %s", filename);
po.config.push_back(filename);
}
);
}
} catch (const error &e) {
Logger::Error("Error in command line: %s", e.what());
cout << desc << "\n";
return -1;
} catch (...) {
Logger::Error("Error in command line.");
cout << desc << "\n";
return -1;
}
} catch (const error &e) {
Logger::Error("Error in command line: %s", e.what());
cout << desc << "\n";
return -1;
} catch (...) {
Logger::Error("Error in command line parsing.");
cout << desc << "\n";
return -1;
}
// reuse -h for help command if possible
if (vm.count("help") || (vm.count("debug-host") && po.mode != "debug")) {
cout << desc << "\n";
return 0;
}
if (vm.count("version")) {
cout << "HipHop VM";
cout << " " << HHVM_VERSION;
cout << " (" << (debug ? "dbg" : "rel") << ")";
cout << " (" << (use_lowptr ? "lowptr" : "non-lowptr") << ")\n";
cout << "Compiler: " << compilerId() << "\n";
cout << "Repo schema: " << repoSchemaId() << "\n";
return 0;
}
if (vm.count("modules")) {
tl_heap.getCheck();
Array exts = ExtensionRegistry::getLoaded();
cout << "[PHP Modules]" << "\n";
for (ArrayIter iter(exts); iter; ++iter) {
cout << iter.second().toString().toCppString() << "\n";
}
return 0;
}
if (vm.count("compiler-id")) {
cout << compilerId() << "\n";
return 0;
}
if (vm.count("repo-schema")) {
cout << repoSchemaId() << "\n";
return 0;
}
if (!po.show.empty()) {
hphp_thread_init();
g_context.getCheck();
SCOPE_EXIT { hphp_thread_exit(); };
auto f = req::make<PlainFile>();
f->open(po.show, "r");
if (!f->valid()) {
Logger::Error("Unable to open file %s", po.show.c_str());
return 1;
}
f->print();
f->close();
return 0;
}
po.isTempFile = vm.count("temp-file");
// forget the source for systemlib.php unless we are debugging
if (po.mode != "debug" && po.mode != "vsdebug") SystemLib::s_source = "";
if (po.mode == "vsdebug") {
RuntimeOption::EnableVSDebugger = true;
RuntimeOption::VSDebuggerListenPort = po.vsDebugPort;
RuntimeOption::VSDebuggerDomainSocketPath = po.vsDebugDomainSocket;
RuntimeOption::VSDebuggerNoWait = po.vsDebugNoWait;
}
// we need to to initialize these very early
pcre_init();
// this is needed for libevent2 to be thread-safe, which backs Hack ASIO.
#ifndef FACEBOOK
// FB uses a custom libevent 1
evthread_use_pthreads();
#endif
rds::local::init();
SCOPE_EXIT { rds::local::fini(); };
tl_heap.getCheck();
if (RuntimeOption::ServerExecutionMode()) {
// Create the hardware counter before reading options,
// so that the main thread never has inherit set in server
// mode
HardwareCounter::s_counter.getCheck();
}
std::vector<std::string> messages;
// We want the ini map to be freed after processing and loading the options
// So put this in its own block
{
IniSettingMap ini = IniSettingMap();
Hdf config;
s_config_files = po.config;
// Start with .hdf and .ini files
for (auto& filename : s_config_files) {
if (boost::filesystem::exists(filename)) {
Config::ParseConfigFile(filename, ini, config);
} else {
Logger::Warning(
"The configuration file %s does not exist",
filename.c_str()
);
}
}
auto const scriptFilePath =
!po.file.empty() ? po.file :
!po.args.empty() ? po.args[0] :
std::string("");
// Now, take care of CLI options and then officially load and bind things
s_ini_strings = po.iniStrings;
RuntimeOption::Load(
ini,
config,
po.iniStrings,
po.confStrings,
&messages,
scriptFilePath
);
if (RuntimeOption::WhitelistExec ||
RuntimeOption::WhitelistExecWarningOnly ||
!RuntimeOption::AllowedExecCmds.empty()) {
fprintf(stderr, "Configurations Server.WhitelistExec, "
"Server.WhitelistExecWarningOnly, Server.AllowedExecCmds "
"are not supported.\n");
return 1;
}
std::vector<std::string> badnodes;
config.lint(badnodes);
for (const auto& badnode : badnodes) {
const auto msg = "Possible bad config node: " + badnode;
fprintf(stderr, "%s\n", msg.c_str());
messages.push_back(msg);
}
if (po.mode == "getoption") {
if (po.args.size() < 1) {
fprintf(stderr, "Must specify an option to load\n");
return 1;
}
Variant value;
bool ret = IniSetting::Get(po.args[0], value);
if (!ret) {
fprintf(stderr, "No such option: %s\n", po.args[0].data());
return 1;
}
if (!value.isString()) {
VariableSerializer vs{VariableSerializer::Type::JSON};
value = vs.serializeValue(value, false);
}
printf("%s\n", value.toString().data());
return 0;
}
}
std::vector<int> inherited_fds;
RuntimeOption::BuildId = po.buildId;
RuntimeOption::InstanceId = po.instanceId;
// Do this as early as possible to avoid creating temp files and spawing
// light processes. Correct compilation still requires loading all of the
// ini/hdf/cli options.
if (po.mode == "dumphhas" || po.mode == "verify" ||
po.mode == "dumpcoverage") {
if (po.file.empty() && po.args.empty()) {
std::cerr << "Nothing to do. Pass a hack file to compile.\n";
return 1;
}
auto const file = [] (std::string file) -> std::string {
if (!FileUtil::isAbsolutePath(file)) {
return SourceRootInfo::GetCurrentSourceRoot() + std::move(file);
}
return file;
}(po.file.empty() ? po.args[0] : po.file);
std::fstream fs(file, std::ios::in);
if (!fs) {
std::cerr << "Unable to open \"" << file << "\"\n";
return 1;
}
std::stringstream contents;
contents << fs.rdbuf();
auto const str = contents.str();
auto const sha1 = SHA1{
mangleUnitSha1(string_sha1(str), file, RepoOptions::defaults().flags())
};
if (!registrationComplete) {
folly::SingletonVault::singleton()->registrationComplete();
registrationComplete = true;
}
compilers_start();
hphp_thread_init();
g_context.getCheck();
SCOPE_EXIT { hphp_thread_exit(); };
if (po.mode == "dumphhas") RuntimeOption::EvalDumpHhas = true;
else if (po.mode != "dumpcoverage") RuntimeOption::EvalVerifyOnly = true;
SystemLib::s_inited = true;
// Ensure write to SystemLib::s_inited is visible by other threads.
std::atomic_thread_fence(std::memory_order_release);
LazyUnitContentsLoader loader{sha1, str, RepoOptions::defaults().flags()};
auto compiled =
compile_file(loader, file.c_str(), Native::s_noNativeFuncs, nullptr);
if (po.mode == "verify") {
return 0;
}
// This will dump the hhas for file as EvalDumpHhas was set
if (!compiled) {
std::cerr << "Unable to compile \"" << file << "\"\n";
return 1;
}
if (po.mode == "dumpcoverage") {
std::cout << "[" << folly::join(", ", get_executable_lines(compiled))
<< "]" << std::endl;
return 0;
}
return 0;
}
if (po.port != -1) {
RuntimeOption::ServerPort = po.port;
}
if (po.portfd != -1) {
RuntimeOption::ServerPortFd = po.portfd;
inherited_fds.push_back(po.portfd);
}
if (po.sslportfd != -1) {
RuntimeOption::SSLPortFd = po.sslportfd;
inherited_fds.push_back(po.sslportfd);
}
if (po.admin_port != -1) {
RuntimeOption::AdminServerPort = po.admin_port;
}
if (po.noSafeAccessCheck) {
RuntimeOption::SafeFileAccess = false;
}
IniSetting::s_system_settings_are_set = true;
if (debug) tl_heap->checkHeap("resetRuntimeOptions");
tl_heap.destroy();
rds::local::fini();
// From this point on there is no tl_heap until hphp_process_init is called.
auto opened_logs = open_server_log_files();
if (po.mode == "daemon") {
if (!opened_logs) {
Logger::Error("Log file not specified under daemon mode.\n\n");
}
proc::daemonize();
}
if (RuntimeOption::ServerExecutionMode()) {
for (auto const& m : messages) {
Logger::Info(m);
}
}
#ifndef _MSC_VER
// Defer the initialization of light processes until the log file handle is
// created, so that light processes can log to the right place. If we ever
// lose a light process, stop the server instead of proceeding in an
// uncertain state. Don't start them in DumpHhas mode because
// it _Exit()s after loading the first non-systemlib unit.
if (!RuntimeOption::EvalDumpHhas) {
LightProcess::SetLostChildHandler([](pid_t /*child*/) {
if (!HttpServer::Server) return;
if (!HttpServer::Server->isStopped()) {
HttpServer::Server->stopOnSignal(SIGCHLD);
}
});
LightProcess::Initialize(RuntimeOption::LightProcessFilePrefix,
RuntimeOption::LightProcessCount,
RuntimeOption::EvalRecordSubprocessTimes,
inherited_fds);
}
#endif
#if USE_JEMALLOC_EXTENT_HOOKS
if (RuntimeOption::EvalEnableArenaMetadata1GPage) {
// Set up extent hook so that we can place jemalloc metadata on 1G pages.
// This needs to be done after initializing LightProcess (which forks),
// because the child process does malloc which won't work with jemalloc
// metadata on 1G huge pages.
setup_jemalloc_metadata_extent_hook(
RuntimeOption::EvalEnableArenaMetadata1GPage,
RuntimeOption::EvalEnableNumaArenaMetadata1GPage,
RuntimeOption::EvalArenaMetadataReservedSize
);
} else if (RuntimeOption::ServerExecutionMode()) {
purge_all();
setup_arena0({RuntimeOption::EvalNum1GPagesForA0,
RuntimeOption::EvalNum2MPagesForA0});
}
if (RuntimeOption::EvalFileBackedColdArena) {
set_cold_file_dir(RuntimeOption::EvalColdArenaFileDir.c_str());
enable_high_cold_file();
}
#endif
auto const addTypeToEmbeddedPath = [&](std::string path, const char* type) {
auto const typePlaceholder = "%{type}";
assertx(strstr(type, typePlaceholder) == nullptr);
size_t idx;
if ((idx = path.find(typePlaceholder)) != std::string::npos) {
path.replace(idx, strlen(typePlaceholder), type);
}
return path;
};
// We want to initialize the type-scanners as early as possible
// because any allocations before-hand will get a generic unknown
// type type-index.
SCOPE_EXIT {
// this would be handled by hphp_process_exit, but some paths
// short circuit before getting there.
embedded_data_cleanup();
};
try {
type_scan::init(
addTypeToEmbeddedPath(
RuntimeOption::EvalEmbeddedDataExtractPath,
"type_scanners"
),
addTypeToEmbeddedPath(
RuntimeOption::EvalEmbeddedDataFallbackPath,
"type_scanners"
),
RuntimeOption::EvalEmbeddedDataTrustExtract
);
} catch (const type_scan::InitException& exn) {
Logger::Error("Unable to initialize GC type-scanners: %s", exn.what());
exit(HPHP_EXIT_FAILURE);
}
ThreadLocalManager::GetManager().initTypeIndices();
// It's okay if this fails.
init_member_reflection(
addTypeToEmbeddedPath(
RuntimeOption::EvalEmbeddedDataExtractPath,
"member_reflection"
),
addTypeToEmbeddedPath(
RuntimeOption::EvalEmbeddedDataFallbackPath,
"member_reflection"
),
RuntimeOption::EvalEmbeddedDataTrustExtract
);
if (!ShmCounters::initialize(true, Logger::Error)) {
exit(HPHP_EXIT_FAILURE);
}
if (vm.count("check-repo")) {
hphp_thread_init();
always_assert(RO::RepoAuthoritative);
init_repo_file();
LitstrTable::init();
LitarrayTable::init();
RepoFile::loadGlobalTables(RO::RepoLitstrLazyLoad);
RepoFile::globalData().load();
return 0;
}
if (!po.lint.empty()) {
Logger::LogHeader = false;
Logger::LogLevel = Logger::LogInfo;
Logger::UseCronolog = false;
Logger::UseLogFile = true;
// we're linting, reset whatever logger settings and write once to stdout
Logger::ClearThreadLog();
for (auto& el : RuntimeOption::ErrorLogs) {
const auto& name = el.first;
Logger::SetTheLogger(name, nullptr);
}
Logger::SetTheLogger(Logger::DEFAULT, new Logger());
if (po.isTempFile) {
tempFile = po.lint;
}
if (!registrationComplete) {
folly::SingletonVault::singleton()->registrationComplete();
registrationComplete = true;
}
compilers_start();
hphp_thread_init();
g_context.getCheck();
SCOPE_EXIT { hphp_thread_exit(); };
SystemLib::s_inited = true;
// Ensure write to SystemLib::s_inited is visible by other threads.
std::atomic_thread_fence(std::memory_order_release);
try {
auto const file = [&] {
if (!FileUtil::isAbsolutePath(po.lint)) {
return SourceRootInfo::GetCurrentSourceRoot() + po.lint;
}
return po.lint;
}();
std::fstream fs{file, std::ios::in};
if (!fs) throw FileOpenException(po.lint);
std::stringstream contents;
contents << fs.rdbuf();
auto const repoOptions = RepoOptions::forFile(file.c_str());
auto const str = contents.str();
auto const sha1 =
SHA1{mangleUnitSha1(string_sha1(str), file, repoOptions.flags())};
// Disable any cache hooks because they're generally not useful
// if we're just going to lint (and they might be expensive).
g_unit_emitter_cache_hook = nullptr;
LazyUnitContentsLoader loader{sha1, str, repoOptions.flags()};
auto const unit =
compile_file(loader, file.c_str(), Native::s_noNativeFuncs, nullptr);
if (!unit) {
std::cerr << "Unable to compile \"" << file << "\"\n";
return 1;
}
if (auto const info = unit->getFatalInfo()) {
raise_parse_error(unit->filepath(), info->m_fatalMsg.c_str(),
info->m_fatalLoc);
}
} catch (const FatalErrorException& e) {
RuntimeOption::CallUserHandlerOnFatals = false;
RuntimeOption::AlwaysLogUnhandledExceptions = false;
g_context->onFatalError(e);
return 1;
}
Logger::Info("No syntax errors detected in %s", po.lint.c_str());
return 0;
}
if (argc <= 1 || po.mode == "run" || po.mode == "debug" ||
po.mode == "vsdebug" || po.mode == "eval") {
set_stack_size();
if (po.isTempFile) {
tempFile = po.file;
}
set_execution_mode("run");
/* recreate the hardware counters for the main thread now that we know
* whether to include subprocess times */
HardwareCounter::s_counter.destroy();
HardwareCounter::s_counter.getCheck();
int new_argc;
char **new_argv;
prepare_args(new_argc, new_argv, po.args, po.file.c_str());
std::string const cliFile = !po.file.empty() ? po.file :
new_argv[0] ? new_argv[0] : "";
if (po.mode != "debug" && po.mode != "eval" && cliFile.empty()) {
std::cerr << "Nothing to do. Either pass a hack file to run, or "
"use -m server\n";
return 1;
}
if (po.mode == "eval" && po.args.empty()) {
std::cerr << "Nothing to do. Pass a command to run with mode eval\n";
return 1;
}
if (RuntimeOption::EvalUseRemoteUnixServer != "no" &&
!RuntimeOption::EvalUnixServerPath.empty() &&
(!po.file.empty() || !po.args.empty()) && po.mode != "eval") {
// CLI server clients use a wacky delayed initialization scheme for
// RDS, and therefore requires RDS_LOCALS be outside RDS.
rds::local::init();
SCOPE_EXIT { rds::local::fini(); };
std::vector<std::string> args;
if (!po.file.empty()) {
args.emplace_back(po.file);
}
args.insert(args.end(), po.args.begin(), po.args.end());
run_command_on_cli_server(
RuntimeOption::EvalUnixServerPath.c_str(), args, po.count
);
if (RuntimeOption::EvalUseRemoteUnixServer == "only") {
Logger::Error("Failed to connect to unix server.");
exit(255);
}
}
int ret = 0;
init_repo_file();
hphp_process_init();
SCOPE_EXIT { hphp_process_exit(); };
block_sync_signals_and_start_handler_thread();
std::string file;
if (new_argc > 0) {
file = new_argv[0];
}
if (po.mode == "debug") {
StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
RuntimeOption::EnableHphpdDebugger = true;
po.debugger_options.fileName = file;
po.debugger_options.user = po.user;
Eval::DebuggerProxyPtr localProxy =
Eval::Debugger::StartClient(po.debugger_options);
if (!localProxy) {
Logger::Error("Failed to start debugger client\n\n");
return 1;
}
Eval::Debugger::RegisterSandbox(localProxy->getDummyInfo());
std::shared_ptr<std::vector<std::string>> client_args;
bool restart = false;
ret = 0;
while (true) {
try {
assertx(po.debugger_options.fileName == file);
execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
// Set the proxy for this thread to be the localProxy we just
// created. If we're script debugging, this will be the proxy that
// does all of our work. If we're remote debugging, this proxy will
// go unused until we finally stop it when the user quits the
// debugger.
g_context->setSandboxId(localProxy->getDummyInfo().id());
if (restart) {
// Systemlib.php is not loaded again, so we need this if we
// are to hit any breakpoints in systemlib.
proxySetBreakPoints(localProxy.get());
}
Eval::Debugger::DebuggerSession(po.debugger_options, restart);
restart = false;
execute_command_line_end(po.xhprofFlags, true, file.c_str());
} catch (const Eval::DebuggerRestartException& e) {
execute_command_line_end(0, false, nullptr);
if (!e.m_args->empty()) {
file = e.m_args->at(0);
po.debugger_options.fileName = file;
client_args = e.m_args;
free(new_argv);
prepare_args(new_argc, new_argv, *client_args, nullptr);
}
restart = true;
} catch (const Eval::DebuggerClientExitException& e) {
execute_command_line_end(0, false, nullptr);
break; // end user quitting debugger
}
}
} else {
tracing::Request _{
"cli-request",
file,
[&] { return tracing::Props{}.add("file", file); }
};
ret = 0;
for (int i = 0; i < po.count; i++) {
execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
ret = 255;
if (po.mode == "eval") {
String code{"<?hh " + file};
auto const r = g_context->evalPHPDebugger(code.get(), 0);
if (!r.failed) ret = 0;
} else if (hphp_invoke_simple(file, false /* warmup only */)) {
ret = *rl_exit_code;
}
const bool last = i == po.count - 1;
if (last && jit::tc::dumpEnabled()) {
jit::mcgen::joinWorkerThreads();
jit::tc::dump();
}
execute_command_line_end(po.xhprofFlags, true, file.c_str());
}
}
free(new_argv);
return ret;
}
if (po.mode == "daemon" || po.mode == "server") {
if (!po.user.empty()) RuntimeOption::ServerUser = po.user;
return start_server(RuntimeOption::ServerUser, po.xhprofFlags);
}
if (po.mode == "replay" && !po.args.empty()) {
RuntimeOption::RecordInput = false;
set_execution_mode("server");
HttpServer server; // so we initialize runtime properly
HttpRequestHandler handler(0);
for (int i = 0; i < po.count; i++) {
for (unsigned int j = 0; j < po.args.size(); j++) {
ReplayTransport rt;
rt.replayInput(po.args[j].c_str());
handler.run(&rt);
printf("%s\n", rt.getResponse().c_str());
}
}
return 0;
}
if (po.mode == "translate" && !po.args.empty()) {
printf("%s", translate_stack(po.args[0].c_str()).c_str());
return 0;
}
cout << desc << "\n";
return -1;
}
String canonicalize_path(const String& p, const char* root, int rootLen) {
String path = FileUtil::canonicalize(p);
if (path.charAt(0) == '/') {
auto const& sourceRoot = RuntimeOption::SourceRoot;
int len = sourceRoot.size();
if (len && strncmp(path.data(), sourceRoot.c_str(), len) == 0) {
return path.substr(len);
}
if (root && rootLen && strncmp(path.data(), root, rootLen) == 0) {
return path.substr(rootLen);
}
}
return path;
}
static std::string systemlib_split(const std::string& slib, std::string* hhas) {
auto pos = slib.find("\n<?hhas\n");
if (pos != std::string::npos) {
if (hhas) *hhas = slib.substr(pos + 8);
return slib.substr(0, pos);
}
return slib;
}
// Retrieve a systemlib (or mini systemlib) from the
// current executable or another ELF object file.
//
// Additionally, when retrieving the main systemlib
// from the current executable, honor the
// HHVM_SYSTEMLIB environment variable as an override.
std::string get_systemlib(std::string* hhas,
const std::string §ion /*= "systemlib" */,
const std::string &filename /*= "" */) {
if (filename.empty() && section == "systemlib") {
if (auto const file = getenv("HHVM_SYSTEMLIB")) {
std::ifstream ifs(file);
if (ifs.good()) {
return systemlib_split(std::string(
std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>()), hhas);
}
}
}
embedded_data desc;
if (!get_embedded_data(section.c_str(), &desc, filename)) return "";
auto const data = read_embedded_data(desc);
return systemlib_split(data, hhas);
}
///////////////////////////////////////////////////////////////////////////////
// C++ ffi
#ifndef _MSC_VER
namespace {
void on_timeout(int sig, siginfo_t* info, void* /*context*/) {
if (sig == SIGVTALRM && info && info->si_code == SI_TIMER) {
auto data = (RequestTimer*)info->si_value.sival_ptr;
if (data) {
data->onTimeout();
}
}
}
}
#endif
/*
* Update constants to their real values and sync some runtime options
*/
static void update_constants_and_options() {
assertx(ExtensionRegistry::modulesInitialised());
// If extension constants were used in the ini files (e.g., E_ALL) they
// would have come out as 0 in the previous pass until we load and
// initialize our extensions, which we do in RuntimeOption::Load() via
// ExtensionRegistry::ModuleLoad() and in ExtensionRegistry::ModuleInit()
// in hphp_process_init(). We will re-import and set only the constants that
// have been now bound to their proper value.
IniSettingMap ini = IniSettingMap();
for (auto& filename: s_config_files) {
Config::ParseIniFile(filename, ini, true);
}
// Reset the INI settings from the CLI.
for (auto& iniStr: s_ini_strings) {
Config::ParseIniString(iniStr, ini, true);
}
// Reset, possibly, some request dependent runtime options based on certain
// setting values. Do this here so we ensure the constants have been loaded
// correctly (e.g., error_reporting E_ALL, etc.)
Variant sys;
if (IniSetting::GetSystem("error_reporting", sys)) {
RuntimeOption::RuntimeErrorReportingLevel = sys.toInt64();
RID().setErrorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel);
}
if (IniSetting::GetSystem("memory_limit", sys)) {
RID().setMemoryLimit(sys.toString().toCppString());
RuntimeOption::RequestMemoryMaxBytes = RID().getMemoryLimitNumeric();
}
}
void hphp_thread_init() {
init_current_pthread_stack_limits();
#if USE_JEMALLOC_EXTENT_HOOKS
arenas_thread_init();
#endif
rds::threadInit();
ServerStats::GetLogger();
zend_get_bigint_data();
zend_rand_init();
get_server_note();
tl_heap.getCheck()->init();
assertx(RequestInfo::s_requestInfo.isNull());
RequestInfo::s_requestInfo.getCheck()->init();
HardwareCounter::s_counter.getCheck();
ExtensionRegistry::threadInit();
InitFiniNode::ThreadInit();
// Ensure that there's no request-allocated memory. This call must happen at
// least once after RDS has been initialized to ensure
// MemoryManager::resetGC() sets a proper trigger threshold.
hphp_memory_cleanup();
}
void hphp_thread_exit() {
InitFiniNode::ThreadFini();
ExtensionRegistry::threadShutdown();
if (!g_context.isNull()) g_context.destroy();
rds::threadExit();
#if USE_JEMALLOC_EXTENT_HOOKS
arenas_thread_exit();
#endif
}
void cli_client_init() {
if (*s_sessionInitialized) return;
Process::InitProcessStatics();
HHProf::Init();
rds::processInit();
rds::threadInit();
ServerStats::GetLogger();
zend_rand_init();
get_server_note();
assertx(RequestInfo::s_requestInfo.isNull());
RequestInfo::s_requestInfo.getCheck()->init();
HardwareCounter::s_counter.getCheck();
InitFiniNode::ThreadInit();
hphp_memory_cleanup();
g_context.getCheck();
AsioSession::Init();
Socket::clearLastError();
RI().onSessionInit();
tl_heap->resetExternalStats();
g_thread_safe_locale_handler->reset();
Treadmill::startRequest(Treadmill::SessionKind::CLIServer);
*s_sessionInitialized = true;
}
void init_current_pthread_stack_limits() {
pthread_attr_t attr;
// Linux+GNU extension
#if defined(_GNU_SOURCE) && defined(__linux__)
if (pthread_getattr_np(pthread_self(), &attr) != 0 ) {
Logger::Error("pthread_getattr_np failed before checking stack limits");
_exit(1);
}
#else
if (pthread_attr_init(&attr) != 0 ) {
Logger::Error("pthread_attr_init failed before checking stack limits");
_exit(1);
}
#endif
init_stack_limits(&attr);
if (pthread_attr_destroy(&attr) != 0 ) {
Logger::Error("pthread_attr_destroy failed after checking stack limits");
_exit(1);
}
}
void hphp_process_init(bool skipModules) {
init_current_pthread_stack_limits();
BootStats::mark("pthread_init");
Process::InitProcessStatics();
BootStats::mark("Process::InitProcessStatics");
HHProf::Init();
// initialize the tzinfo cache.
timezone_init();
BootStats::mark("timezone_init");
// start any external compilers
compilers_start();
BootStats::mark("compilers_start");
rds::processInit();
hphp_thread_init();
#ifndef _MSC_VER
struct sigaction action = {};
action.sa_sigaction = on_timeout;
action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_RESTART;
sigaction(SIGVTALRM, &action, nullptr);
#endif
// start takes milliseconds, Period is a double in seconds
Xenon::getInstance().start(1000 * RuntimeOption::XenonPeriodSeconds);
BootStats::mark("xenon");
// set up strobelight signal handling
Strobelight::getInstance().init();
BootStats::mark("strobelight");
// reinitialize pcre table
pcre_reinit();
BootStats::mark("pcre_reinit");
// the liboniguruma docs say this isnt needed,
// but the implementation of init is not
// thread safe due to bugs
onig_init();
BootStats::mark("onig_init");
g_context.getCheck();
// Some event handlers are registered during the startup process.
g_context->acceptRequestEventHandlers(true);
if (!registrationComplete) {
folly::SingletonVault::singleton()->registrationComplete();
registrationComplete = true;
}
InitFiniNode::ProcessPreInit();
// TODO(9795696): Race in thread map may trigger spurious logging at
// thread exit, so for now, only spawn threads if we're a server.
const uint32_t maxWorkers = RuntimeOption::ServerExecutionMode() ? 3 : 0;
InitFiniNode::ProcessInitConcurrentStart(maxWorkers);
SCOPE_EXIT {
InitFiniNode::ProcessInitConcurrentWaitForEnd();
BootStats::mark("extra_process_init_concurrent_wait");
};
// Allocate this even if EvalEnableImplicitContext is false. The
// correct setting might come from RepoGlobalData, and that isn't
// loaded until we call g_vmProcessInit. This needs to run prior to
// emitting unique stubs (in jit::mcgen::processInit), so we cannot
// move it after the RepoGlobalData load.
ImplicitContext::activeCtx
.bind(rds::Mode::Normal, rds::LinkID{"ImplicitContext::activeCtx"});
jit::mcgen::processInit();
jit::processInitProfData();
g_vmProcessInit();
BootStats::mark("g_vmProcessInit");
PageletServer::Restart();
BootStats::mark("PageletServer::Restart");
XboxServer::Restart();
BootStats::mark("XboxServer::Restart");
Stream::RegisterCoreWrappers();
BootStats::mark("Stream::RegisterCoreWrappers");
if (!skipModules) {
ExtensionRegistry::moduleInit();
BootStats::mark("ExtensionRegistry::moduleInit");
}
if (!RuntimeOption::DeploymentId.empty()) {
StackTraceNoHeap::AddExtraLogging(
"DeploymentId", RuntimeOption::DeploymentId);
}
if (!skipModules) {
// Now that constants have been bound we can update options using constants
// in ini files (e.g., E_ALL) and sync some other options
update_constants_and_options();
}
InitFiniNode::ProcessInit();
BootStats::mark("extra_process_init");
if (RuntimeOption::RepoAuthoritative &&
!RuntimeOption::EvalJitSerdesFile.empty() &&
jit::mcgen::retranslateAllEnabled()) {
auto const mode = RuntimeOption::EvalJitSerdesMode;
auto const numWorkers = RuntimeOption::EvalJitWorkerThreadsForSerdes ?
RuntimeOption::EvalJitWorkerThreadsForSerdes : Process::GetCPUCount();
auto const deserialize = [&] (auto const& f) {
#if USE_JEMALLOC_EXTENT_HOOKS
auto const numArenas =
std::min(RO::EvalJitWorkerArenas,
std::max(RO::EvalJitWorkerThreads, numWorkers));
setup_extra_arenas(numArenas);
#endif
return f(
RO::EvalJitSerdesFile,
RO::EvalJitParallelDeserialize ? numWorkers : 1,
false
);
};
auto const rta = [&] (const std::string& mark, bool skipSerialize) {
BootStats::mark(mark);
BootStats::set("prof_data_source_host",
jit::ProfData::buildHost()->toCppString());
BootStats::set("prof_data_timestamp", jit::ProfData::buildTime());
RO::EvalJitProfileRequests = 0;
RO::EvalJitWorkerThreads = numWorkers;
// Run retranslateAll asynchronously, without waiting for it to finish
// here.
jit::mcgen::checkRetranslateAll(true, skipSerialize);
};
auto const tryPartialDeserialize = [&] {
if (!isJitSerializing()) return;
if (!jit::serializeOptProfEnabled()) return;
if (!RO::EvalJitSerializeOptProfRestart) return;
if (RO::ServerExecutionMode()) {
Logger::FInfo("Attempting to deserialize partial profile-data file: {}",
RO::EvalJitSerdesFile);
}
auto const success = deserialize(jit::tryDeserializePartialProfData);
if (success) {
if (RO::ServerExecutionMode()) {
Logger::FInfo("Successfully deserialized partial profile-data file. "
"Loaded {} units with {} workers",
numLoadedUnits(), numWorkers);
}
rta("jit::tryDeserializePartialProfData", true);
} else if (RO::ServerExecutionMode()) {
Logger::FInfo("Failed deserializing partial profile-data file. "
"Proceeding normally");
}
};
if (isJitDeserializing()) {
if (RuntimeOption::ServerExecutionMode()) {
Logger::FInfo("JitDeserializeFrom: {}",
RuntimeOption::EvalJitSerdesFile);
}
auto const errMsg = deserialize(jit::deserializeProfData);
if (mode == JitSerdesMode::DeserializeAndDelete) {
// Delete the serialized profile data when we finish reading
if (RuntimeOption::ServerExecutionMode()) {
Logger::FInfo("Deleting serialized profile-data file: {}",
RuntimeOption::EvalJitSerdesFile);
}
unlink(RuntimeOption::EvalJitSerdesFile.c_str());
}
if (errMsg.empty()) {
if (RuntimeOption::ServerExecutionMode()) {
Logger::FInfo("JitDeserialize: Loaded {} Units with {} workers",
numLoadedUnits(), numWorkers);
}
rta("jit::deserializeProfData", false);
if (mode == JitSerdesMode::DeserializeAndExit) {
if (RuntimeOption::ServerExecutionMode()) {
Logger::Info("JitDeserialize finished; exiting");
}
if (jit::tc::dumpEnabled()) {
jit::mcgen::joinWorkerThreads();
jit::tc::dump();
}
hphp_process_exit();
exit(0);
}
} else { // failed to deserialize
if (mode == JitSerdesMode::DeserializeOrFail ||
mode == JitSerdesMode::DeserializeAndExit) {
Logger::Error(errMsg);
hphp_process_exit();
exit(1);
}
if (mode == JitSerdesMode::DeserializeOrGenerate) {
Logger::Info(errMsg +
", scheduling one time serialization and restart");
RuntimeOption::EvalJitSerdesMode = JitSerdesMode::SerializeAndExit;
tryPartialDeserialize();
} else {
Logger::Info(errMsg + ", will profile then retranslateAll");
}
}
} else {
// We're not deserializing. Check if we're serializing and if we
// have a partial file. If so, deserialize it and do a RTA.
tryPartialDeserialize();
}
}
rds::requestExit();
BootStats::mark("rds::requestExit");
// Reset the preloaded g_context
ExecutionContext *context = g_context.getNoCheck();
context->onRequestShutdown(); // TODO T20898959 kill early REH usage.
context->~ExecutionContext();
new (context) ExecutionContext();
BootStats::mark("ExecutionContext");
}
static void handle_exception(bool& ret, ExecutionContext* context,
std::string& errorMsg, ContextOfException where,
bool& error, bool richErrorMsg) {
assertx(where == ContextOfException::Invoke ||
where == ContextOfException::ReqInit);
try {
handle_exception_helper(ret, context, errorMsg, where, error, richErrorMsg);
} catch (const ExitException& e) {
// Got an ExitException during exception handling, handle
// similarly to the case below but don't call obEndAll().
} catch (...) {
handle_exception_helper(ret, context, errorMsg, ContextOfException::Handler,
error, richErrorMsg);
context->obEndAll();
}
}
static void handle_reqinit_exception(bool &ret, ExecutionContext *context,
std::string &errorMsg, bool &error) {
handle_exception(ret, context, errorMsg, ContextOfException::ReqInit, error,
false);
}
static void handle_invoke_exception(bool &ret, ExecutionContext *context,
std::string &errorMsg, bool &error,
bool richErrorMsg) {
handle_exception(ret, context, errorMsg, ContextOfException::Invoke, error,
richErrorMsg);
}
void invoke_prelude_script(
const char* currentDir,
const std::string& document,
const std::string& prelude,
const char* root
) {
// If $SOURCE_ROOT is found in prelude path
// Execute the script from PHP root folder
// or from current folder
static const std::string s_phpRootVar("${SOURCE_ROOT}");
std::string preludeScript(prelude);
auto posPhpRoot = preludeScript.find(s_phpRootVar);
if (std::string::npos != posPhpRoot){
preludeScript.replace(posPhpRoot, s_phpRootVar.length(),
root ? root : SourceRootInfo::GetCurrentSourceRoot().c_str());
}
FileUtil::runRelative(
preludeScript,
String(document, CopyString),
currentDir,
[currentDir] (const String& f) {
auto const w = Stream::getWrapperFromURI(f, nullptr, false);
if (w->access(f, R_OK) == 0) {
include_impl_invoke(f, true, currentDir, true);
return true;
}
return false;
}
);
}
static bool hphp_warmup(ExecutionContext *context,
const std::string& cmd,
const std::string &reqInitFunc,
const std::string &reqInitDoc,
const std::string &prelude,
bool &error,
bool runEntryPoint) {
tracing::Block _{
"warmup",
[&] {
return tracing::Props{}
.add("cmd", cmd)
.add("req_init_doc", reqInitDoc)
.add("req_init_func", reqInitFunc)
.add("prelude", prelude);
}
};
bool ret = true;
error = false;
std::string errorMsg;
ServerStatsHelper ssh("reqinit");
try {
if (!prelude.empty() && (!cmd.empty() || !reqInitDoc.empty())) {
auto const currentDir = context->getCwd();
auto const& document = !reqInitDoc.empty() ? reqInitDoc : cmd;
invoke_prelude_script(currentDir.data(), document, prelude);
}
if (!reqInitDoc.empty()) {
include_impl_invoke(reqInitDoc, true, "", runEntryPoint);
}
if (!reqInitFunc.empty()) {
invoke(reqInitFunc, Array());
}
context->backupSession();
} catch (...) {
handle_reqinit_exception(ret, context, errorMsg, error);
}
return ret;
}
void hphp_session_init(Treadmill::SessionKind session_kind,
Transport* transport) {
assertx(!*s_sessionInitialized);
g_context.getCheck();
AsioSession::Init();
Socket::clearLastError();
RI().onSessionInit();
tl_heap->resetExternalStats();
g_thread_safe_locale_handler->reset();
Treadmill::startRequest(session_kind);
// Ordering is sensitive; StatCache::requestInit produces work that
// must be done in ExecutionContext::requestInit.
StatCache::requestInit();
// Allow request event handlers to be created now that a new request has
// started.
g_context->acceptRequestEventHandlers(true);
g_context->requestInit(); // must happen after treadmill start
if (transport != nullptr) g_context->setTransport(transport);
*s_sessionInitialized = true;
ExtensionRegistry::requestInit();
// Sample function calls for this request
if (RID().logFunctionCalls()) {
EventHook::Enable();
}
auto const pme_freq = RuntimeOption::EvalPerfMemEventRequestFreq;
if (pme_freq > 0 && folly::Random::rand32(pme_freq) == 0) {
// Enable memory access sampling for this request.
perf_event_enable(
RuntimeOption::EvalPerfMemEventSampleFreq,
[] (PerfEvent) { setSurpriseFlag(PendingPerfEventFlag); }
);
}
}
bool hphp_invoke_simple(const std::string& filename, bool warmupOnly) {
bool error;
std::string errorMsg;
return hphp_invoke(g_context.getNoCheck(), filename, false, null_array,
nullptr, "", "", error, errorMsg,
true /* once */,
warmupOnly,
false /* richErrorMsg */,
RuntimeOption::EvalPreludePath);
}
bool hphp_invoke(ExecutionContext *context, const std::string &cmd,
bool func, const Array& funcParams, Variant* funcRet,
const std::string &reqInitFunc, const std::string &reqInitDoc,
bool &error, std::string &errorMsg,
bool once, bool warmupOnly,
bool richErrorMsg, const std::string& prelude,
bool allowDynCallNoPointer /* = false */) {
tracing::Block _{"invoke", [&] { return tracing::Props{}.add("cmd", cmd); }};
bool isServer =
RuntimeOption::ServerExecutionMode() && !is_cli_server_mode();
error = false;
// Make sure we have the right current working directory within the repo
// based on what server.source_root was set to (current process directory
// being the default)
if (RuntimeOption::RepoAuthoritative) {
context->setCwd(RuntimeOption::SourceRoot);
}
String oldCwd;
if (isServer) {
oldCwd = context->getCwd();
}
if (!hphp_warmup(context, cmd, reqInitFunc, reqInitDoc, prelude, error,
func)) {
if (isServer) context->setCwd(oldCwd);
return false;
}
tl_heap->resetCouldOOM(isStandardRequest());
RID().resetTimers();
bool ret = true;
if (!warmupOnly) {
try {
ServerStatsHelper ssh("invoke");
if (!RuntimeOption::AutoPrependFile.empty() &&
RuntimeOption::AutoPrependFile.compare("none") ) {
require(RuntimeOption::AutoPrependFile, false,
context->getCwd().data(), true);
}
if (func) {
auto const ret = invoke(cmd, funcParams, allowDynCallNoPointer);
if (funcRet) *funcRet = ret;
} else {
if (isServer) hphp_chdir_file(cmd);
include_impl_invoke(cmd.c_str(), once, "", true);
}
if (!RuntimeOption::AutoAppendFile.empty() &&
RuntimeOption::AutoAppendFile.compare("none")) {
require(RuntimeOption::AutoAppendFile, false,
context->getCwd().data(), true);
}
} catch (...) {
handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
}
}
try {
context->onShutdownPreSend();
} catch (...) {
handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
}
if (isServer) context->setCwd(oldCwd);
return ret;
}
void hphp_context_exit() {
// Run shutdown handlers. This may cause user code to run.
g_thread_safe_locale_handler->reset();
auto const context = g_context.getNoCheck();
context->onRequestShutdown();
// Extensions could have shutdown handlers
ExtensionRegistry::requestShutdown();
InitFiniNode::RequestFini();
// Extension shutdown could have re-initialized some
// request locals
context->onRequestShutdown();
// This causes request event handler registration to fail until the next
// request starts.
context->acceptRequestEventHandlers(false);
// Clean up a bunch of request state. No user code after this point.
MemoryManager::setExiting();
context->requestExit();
context->obProtect(false);
context->obEndAll();
}
void hphp_memory_cleanup() {
auto& mm = *tl_heap;
// sweep functions are allowed to access g_context,
// so we can't destroy it yet
mm.sweep();
// We should never have any registered RequestEventHandlers. If we do
// something after onRequestShutdown registered a RequestEventHandler.
// Its now too late to run the requestShutdown functions, but if we carry
// on, requestInit and requestShutdown will never be called again.
// I considered just clearing the inited flags; which works for some
// RequestEventHandlers - but its a disaster for others. So just fail hard
// here.
always_assert(g_context.isNull() || !g_context->hasRequestEventHandlers());
// g_context is request allocated, and has some members that need
// cleanup, so destroy it before its too late
g_context.destroy();
weakref_cleanup();
mm.resetAllocator();
mm.resetCouldOOM();
APCTypedValue::FreeHazardPointers();
}
void hphp_session_exit(Transport* transport) {
assertx(*s_sessionInitialized);
// Server note and INI have to live long enough for the access log to fire.
// RequestLocal is too early.
ServerNote::Reset();
IniSetting::ResetSavedDefaults();
// In JitPGO mode, check if it's time to schedule the retranslation of all
// profiled functions and, if so, schedule it.
jit::mcgen::checkRetranslateAll();
jit::mcgen::checkSerializeOptProf();
jit::tc::requestExit();
if (RO::EvalIdleUnitTimeoutSecs > 0 && !RO::RepoAuthoritative) {
// Update the timestamp of any Unit we touched in this request. We
// defer this until the end of the request to prevent Units from
// being considered "expired" while a request is still using it.
auto const now = Unit::TouchClock::now();
for (auto const u : g_context->m_touchedUnits) u->setLastTouchTime(now);
} else {
assertx(g_context->m_touchedUnits.empty());
}
// Similarly, apc strings could be in the ServerNote array, and
// it's possible they are scheduled to be destroyed after this request
// finishes.
Treadmill::finishRequest();
RI().onSessionExit();
// We might have events from after the final surprise flag check of the
// request, so consume them here.
perf_event_consume(record_perf_mem_event);
perf_event_disable();
// Get some memory-related counters before tearing down the MemoryManager.
auto entry = transport ? transport->getStructuredLogEntry() : nullptr;
if (entry) tl_heap->recordStats(*entry);
{
ServerStatsHelper ssh("rollback");
hphp_memory_cleanup();
}
assertx(tl_heap->empty());
*s_sessionInitialized = false;
s_extra_request_nanoseconds = 0;
if (transport) {
HardwareCounter::UpdateServiceData(transport->getCpuTime(),
transport->getWallTime(),
entry,
true /*psp*/);
if (entry) {
entry->setInt("response_code", transport->getResponseCode());
entry->setInt("uptime", f_server_uptime());
entry->setInt("rss", ProcStatus::adjustedRssKb());
if (use_lowptr) {
entry->setInt("low_mem", alloc::getLowMapped());
}
StructuredLog::log("hhvm_request_perf", *entry);
transport->resetStructuredLogEntry();
}
}
}
void hphp_process_exit() noexcept {
// We want to do clean up on a best-effort basis: don't skip later steps if
// an earlier step fails, and don't propagate exceptions ouf of this function
#define LOG_AND_IGNORE(voidexpr) try { voidexpr; } catch (...) { \
Logger::Error("got exception in cleanup step: " #voidexpr); }
LOG_AND_IGNORE(teardown_cli_server())
LOG_AND_IGNORE(Xenon::getInstance().stop())
LOG_AND_IGNORE(jit::mcgen::joinWorkerThreads())
LOG_AND_IGNORE(jit::tc::processExit())
LOG_AND_IGNORE(bespoke::waitOnExportProfiles())
LOG_AND_IGNORE(PageletServer::Stop())
LOG_AND_IGNORE(XboxServer::Stop())
// Debugger::Stop() needs an execution context
LOG_AND_IGNORE(g_context.getCheck())
LOG_AND_IGNORE(Eval::Debugger::Stop())
LOG_AND_IGNORE(g_context.destroy())
LOG_AND_IGNORE(shutdownUnitPrefetcher());
LOG_AND_IGNORE(shutdownUnitReaper());
LOG_AND_IGNORE(Strobelight::shutdown())
LOG_AND_IGNORE(ExtensionRegistry::moduleShutdown())
#ifndef _MSC_VER
LOG_AND_IGNORE(LightProcess::Close())
#endif
LOG_AND_IGNORE(InitFiniNode::ProcessFini())
LOG_AND_IGNORE(folly::SingletonVault::singleton()->destroyInstances())
LOG_AND_IGNORE(embedded_data_cleanup())
LOG_AND_IGNORE(Debug::destroyDebugInfo())
LOG_AND_IGNORE(clearUnitCacheForExit())
#undef LOG_AND_IGNORE
}
bool is_hphp_session_initialized() {
return *s_sessionInitialized;
}
static struct SetThreadInitFini {
template<class ThreadT> static typename std::enable_if<
std::is_integral<ThreadT>::value || std::is_pointer<ThreadT>::value>::type
recordThreadAddr(ThreadT threadId, char* stackAddr, size_t stackSize) {
// In the current glibc implementation, pthread_t is a 64-bit unsigned
// integer, whose value equals the address of the thread control block
// (TCB). In x64_64, this is right above the TLS block. In addition,
// TLS and TCB sits at the high end of the stack, i.e.,
//
// stackAddr + stackSize ----> +---------------+
// | TCB |
// threadId ----> +---------------+
// | TLS |
// +---------------+
// | Stack |
// . .
// . .
// stackAddr ----> +---------------+
auto const tcbBase = reinterpret_cast<char*>(threadId);
auto stackEnd = stackAddr + stackSize;
if (tcbBase > stackAddr && tcbBase < stackEnd) { // the expected layout
// TCB
Debug::DebugInfo::recordDataMap(
tcbBase, stackEnd,
folly::sformat("Thread-{}", static_cast<void*>(tcbBase)));
// TLS
auto const tlsRange = getCppTdata();
auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
stackEnd = tcbBase - tlsSize;
if (tlsSize) {
Debug::DebugInfo::recordDataMap(
stackEnd, tcbBase,
folly::sformat("TLS-{}", static_cast<void*>(tcbBase)));
}
}
Debug::DebugInfo::recordDataMap(
stackAddr, stackEnd,
folly::sformat("Stack-{}", static_cast<void*>(tcbBase)));
}
template <class ThreadT>
static typename std::enable_if<!std::is_integral<ThreadT>::value &&
!std::is_pointer<ThreadT>::value>::type
recordThreadAddr(ThreadT /*threadId*/, char* stackAddr, size_t stackSize) {
// pthread_t is not an integer or pointer to TCB in this pthread
// implementation. But we can still figure out where TLS is.
auto const tlsRange = getCppTdata();
auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
auto const tlsBaseAddr = reinterpret_cast<char*>(tlsBase());
Debug::DebugInfo::recordDataMap(
tlsBaseAddr, tlsBaseAddr + tlsSize,
folly::sformat("TLS-{}", static_cast<void*>(stackAddr)));
Debug::DebugInfo::recordDataMap(
stackAddr, stackAddr + stackSize,
folly::sformat("Stack-{}", static_cast<void*>(stackAddr)));
}
SetThreadInitFini() {
AsyncFuncImpl::SetThreadInitFunc(
[] (void*) {
#if defined(_GNU_SOURCE) && defined(__linux__)
if (RuntimeOption::EvalPerfDataMap) {
pthread_t threadId = pthread_self();
pthread_attr_t attr;
pthread_getattr_np(threadId, &attr);
void* stackAddr{nullptr};
size_t stackSize{0};
pthread_attr_getstack(&attr, &stackAddr, &stackSize);
pthread_attr_destroy(&attr);
recordThreadAddr(threadId, static_cast<char*>(stackAddr), stackSize);
}
#endif
hphp_thread_init();
},
nullptr);
AsyncFuncImpl::SetThreadFiniFunc([](void*) { hphp_thread_exit(); },
nullptr);
}
} s_SetThreadInitFini;
///////////////////////////////////////////////////////////////////////////////
}