Check if other threads are running before hugifyText
[hiphop-php.git] / hphp / runtime / base / program-functions.cpp
blob0823c19e9fe78ddf54f37c27cac1cc435d9235bf
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/program-functions.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/backtrace.h"
21 #include "hphp/runtime/base/builtin-functions.h"
22 #include "hphp/runtime/base/code-coverage.h"
23 #include "hphp/runtime/base/config.h"
24 #include "hphp/runtime/base/execution-context.h"
25 #include "hphp/runtime/base/extended-logger.h"
26 #include "hphp/runtime/base/externals.h"
27 #include "hphp/runtime/base/file-util.h"
28 #include "hphp/runtime/base/hhprof.h"
29 #include "hphp/runtime/base/ini-setting.h"
30 #include "hphp/runtime/base/member-reflection.h"
31 #include "hphp/runtime/base/memory-manager.h"
32 #include "hphp/runtime/base/perf-mem-event.h"
33 #include "hphp/runtime/base/php-globals.h"
34 #include "hphp/runtime/base/plain-file.h"
35 #include "hphp/runtime/base/runtime-error.h"
36 #include "hphp/runtime/base/runtime-option.h"
37 #include "hphp/runtime/base/stat-cache.h"
38 #include "hphp/runtime/base/stream-wrapper-registry.h"
39 #include "hphp/runtime/base/surprise-flags.h"
40 #include "hphp/runtime/base/init-fini-node.h"
41 #include "hphp/runtime/base/unit-cache.h"
42 #include "hphp/runtime/base/thread-safe-setlocale.h"
43 #include "hphp/runtime/base/variable-serializer.h"
44 #include "hphp/runtime/base/zend-math.h"
45 #include "hphp/runtime/base/zend-strtod.h"
46 #include "hphp/runtime/debugger/debugger.h"
47 #include "hphp/runtime/debugger/debugger_client.h"
48 #include "hphp/runtime/debugger/debugger_hook_handler.h"
49 #include "hphp/runtime/ext/apc/ext_apc.h"
50 #include "hphp/runtime/ext/xhprof/ext_xhprof.h"
51 #include "hphp/runtime/ext/extension-registry.h"
52 #include "hphp/runtime/ext/json/ext_json.h"
53 #include "hphp/runtime/ext/std/ext_std_file.h"
54 #include "hphp/runtime/ext/std/ext_std_function.h"
55 #include "hphp/runtime/ext/std/ext_std_variable.h"
56 #include "hphp/runtime/ext/xdebug/status.h"
57 #include "hphp/runtime/ext/xenon/ext_xenon.h"
58 #include "hphp/runtime/server/admin-request-handler.h"
59 #include "hphp/runtime/server/cli-server.h"
60 #include "hphp/runtime/server/http-request-handler.h"
61 #include "hphp/runtime/server/log-writer.h"
62 #include "hphp/runtime/server/rpc-request-handler.h"
63 #include "hphp/runtime/server/http-server.h"
64 #include "hphp/runtime/server/pagelet-server.h"
65 #include "hphp/runtime/server/replay-transport.h"
66 #include "hphp/runtime/server/server-note.h"
67 #include "hphp/runtime/server/server-stats.h"
68 #include "hphp/runtime/server/xbox-server.h"
69 #include "hphp/runtime/vm/debug/debug.h"
70 #include "hphp/runtime/vm/jit/code-cache.h"
71 #include "hphp/runtime/vm/jit/tc.h"
72 #include "hphp/runtime/vm/jit/mcgen.h"
73 #include "hphp/runtime/vm/jit/translator.h"
74 #include "hphp/runtime/vm/extern-compiler.h"
75 #include "hphp/runtime/vm/repo.h"
76 #include "hphp/runtime/vm/runtime.h"
77 #include "hphp/runtime/vm/treadmill.h"
79 #include "hphp/util/abi-cxx.h"
80 #include "hphp/util/arch.h"
81 #include "hphp/util/boot-stats.h"
82 #include "hphp/util/build-info.h"
83 #include "hphp/util/compatibility.h"
84 #include "hphp/util/capability.h"
85 #include "hphp/util/embedded-data.h"
86 #include "hphp/util/hardware-counter.h"
87 #include "hphp/util/hphp-config.h"
88 #include "hphp/util/kernel-version.h"
89 #ifndef _MSC_VER
90 #include "hphp/util/light-process.h"
91 #endif
92 #include "hphp/util/perf-event.h"
93 #include "hphp/util/process-exec.h"
94 #include "hphp/util/process.h"
95 #include "hphp/util/service-data.h"
96 #include "hphp/util/shm-counter.h"
97 #include "hphp/util/stack-trace.h"
98 #include "hphp/util/timer.h"
99 #include "hphp/util/type-scan.h"
101 #include "hphp/zend/zend-string.h"
103 #include <folly/CPortability.h>
104 #include <folly/Portability.h>
105 #include <folly/Random.h>
106 #include <folly/Range.h>
107 #include <folly/Singleton.h>
108 #include <folly/portability/Fcntl.h>
109 #include <folly/portability/Libgen.h>
110 #include <folly/portability/Stdlib.h>
111 #include <folly/portability/Unistd.h>
113 #include <boost/algorithm/string/replace.hpp>
114 #include <boost/program_options/options_description.hpp>
115 #include <boost/program_options/positional_options.hpp>
116 #include <boost/program_options/variables_map.hpp>
117 #include <boost/filesystem.hpp>
119 #include <oniguruma.h>
120 // Onigurama defines UChar to unsigned char, but ICU4C defines it to signed
121 // 16-bit int. This is supposed to be resolved by ONIG_ESCAPE_UCHAR_COLLISION,
122 // however this isn't fully supported in 6.8.0 or 6.8.1.
124 // As of 2018-03-21, not in any release; hopefully will be in 6.8.2 - it's
125 // resolved by this commit:
127 // https://github.com/kkos/oniguruma/commit/e79406479b6be4a56e40ede6c1a87b51fba073a2
128 #undef UChar
129 #include <signal.h>
130 #include <libxml/parser.h>
132 #include <chrono>
133 #include <exception>
134 #include <fstream>
135 #include <iterator>
136 #include <map>
137 #include <memory>
138 #include <string>
139 #include <vector>
141 #ifdef _MSC_VER
142 #include <windows.h>
143 #include <winuser.h>
144 #endif
146 using namespace boost::program_options;
147 using std::cout;
149 constexpr auto MAX_INPUT_NESTING_LEVEL = 64;
151 namespace HPHP {
153 ///////////////////////////////////////////////////////////////////////////////
154 // Forward declarations.
156 void initialize_repo();
159 * XXX: VM process initialization is handled through a function
160 * pointer so libhphp_runtime.a can be linked into programs that don't
161 * actually initialize the VM.
163 void (*g_vmProcessInit)();
165 void timezone_init();
167 void pcre_init();
168 void pcre_reinit();
170 ///////////////////////////////////////////////////////////////////////////////
171 // helpers
173 struct ProgramOptions {
174 std::string mode;
175 std::vector<std::string> config;
176 std::vector<std::string> confStrings;
177 std::vector<std::string> iniStrings;
178 int port;
179 int portfd;
180 int sslportfd;
181 int admin_port;
182 std::string user;
183 std::string file;
184 std::string lint;
185 bool isTempFile;
186 int count;
187 bool noSafeAccessCheck;
188 std::vector<std::string> args;
189 std::string buildId;
190 std::string instanceId;
191 int xhprofFlags;
192 std::string show;
193 std::string parse;
194 int vsDebugPort;
195 bool vsDebugNoWait;
196 Eval::DebuggerClientOptions debugger_options;
199 struct StartTime {
200 StartTime() : startTime(time(nullptr)) {}
201 time_t startTime;
204 static bool registrationComplete = false;
205 static StartTime s_startTime;
206 static std::string tempFile;
207 std::vector<std::string> s_config_files;
208 std::vector<std::string> s_ini_strings;
210 time_t start_time() {
211 return s_startTime.startTime;
214 const StaticString
215 s_HPHP("HPHP"),
216 s_HHVM("HHVM"),
217 s_HHVM_JIT("HHVM_JIT"),
218 s_HHVM_ARCH("HHVM_ARCH"),
219 s_REQUEST_START_TIME("REQUEST_START_TIME"),
220 s_REQUEST_TIME("REQUEST_TIME"),
221 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
222 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
223 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
224 s_SCRIPT_NAME("SCRIPT_NAME"),
225 s_PHP_SELF("PHP_SELF"),
226 s_argc("argc"),
227 s_argv("argv"),
228 s_PWD("PWD"),
229 s_HOSTNAME("HOSTNAME"),
230 s__SERVER("_SERVER"),
231 s__ENV("_ENV");
233 static __thread bool s_sessionInitialized{false};
235 static void process_cmd_arguments(int argc, char **argv) {
236 php_global_set(s_argc, Variant(argc));
237 Array argvArray(staticEmptyArray());
238 for (int i = 0; i < argc; i++) {
239 argvArray.append(String(argv[i]));
241 php_global_set(s_argv, argvArray);
244 static void process_env_variables(Array& variables, char** envp,
245 std::map<std::string, std::string>& envVariables) {
246 for (auto& kv : envVariables) {
247 variables.set(String(kv.first), String(kv.second));
249 for (char **env = envp; env && *env; env++) {
250 char *p = strchr(*env, '=');
251 if (p) {
252 String name(*env, p - *env, CopyString);
253 register_variable(variables, (char*)name.data(),
254 String(p + 1, CopyString));
259 void process_env_variables(Array& variables) {
260 process_env_variables(variables, environ, RuntimeOption::EnvVariables);
263 // Handle adding a variable to an array, supporting keys that look
264 // like array expressions (like 'FOO[][key1][k2]').
265 void register_variable(Array& variables, char *name, const Variant& value,
266 bool overwrite /* = true */) {
267 // ignore leading spaces in the variable name
268 char *var = name;
269 while (*var && *var == ' ') {
270 var++;
273 // ensure that we don't have spaces or dots in the variable name
274 // (not binary safe)
275 bool is_array = false;
276 char *ip = nullptr; // index pointer
277 char *p = var;
278 for (; *p; p++) {
279 if (*p == ' ' || *p == '.') {
280 *p = '_';
281 } else if (*p == '[') {
282 is_array = true;
283 ip = p;
284 *p = 0;
285 break;
288 int var_len = p - var;
289 if (var_len == 0) {
290 // empty variable name, or variable name with a space in it
291 return;
294 // GPC elements holds Variants that are acting as smart pointers to
295 // RefDatas that we've created in the process of a multi-dim key.
296 std::vector<Variant> gpc_elements;
297 if (is_array) gpc_elements.reserve(MAX_INPUT_NESTING_LEVEL);
299 // The array pointer we're currently adding to. If we're doing a
300 // multi-dimensional set, this will point at the m_data.parr inside
301 // of a RefData sometimes (via toArrRef on the variants in
302 // gpc_elements).
303 Array* symtable = &variables;
305 char* index = var;
306 int index_len = var_len;
308 if (is_array) {
309 int nest_level = 0;
310 while (true) {
311 if (++nest_level > MAX_INPUT_NESTING_LEVEL) {
312 Logger::Warning("Input variable nesting level exceeded");
313 return;
316 ip++;
317 char *index_s = ip;
318 int new_idx_len = 0;
319 if (isspace(*ip)) {
320 ip++;
322 if (*ip == ']') {
323 index_s = nullptr;
324 } else {
325 ip = strchr(ip, ']');
326 if (!ip) {
327 // PHP variables cannot contain '[' in their names,
328 // so we replace the character with a '_'
329 *(index_s - 1) = '_';
331 index_len = 0;
332 if (index) {
333 index_len = strlen(index);
335 goto plain_var;
337 *ip = 0;
338 new_idx_len = strlen(index_s);
341 if (!index) {
342 auto lval = symtable->lvalAt();
343 lval.type() = KindOfPersistentArray;
344 lval.val().parr = staticEmptyArray();
345 gpc_elements.push_back(uninit_null());
346 gpc_elements.back().assignRef(tvAsVariant(lval.tv_ptr()));
347 } else {
348 String key(index, index_len, CopyString);
349 auto const v = symtable->rvalAt(key).unboxed();
350 if (isNullType(v.type()) || !isArrayLikeType(v.type())) {
351 symtable->set(key, Array::Create());
353 gpc_elements.push_back(uninit_null());
354 gpc_elements.back().assignRef(
355 tvAsVariant(symtable->lvalAt(key).tv_ptr())
358 symtable = &gpc_elements.back().toArrRef();
359 /* ip pointed to the '[' character, now obtain the key */
360 index = index_s;
361 index_len = new_idx_len;
363 ip++;
364 if (*ip == '[') {
365 is_array = true;
366 *ip = 0;
367 } else {
368 goto plain_var;
371 } else {
372 plain_var:
373 if (!index) {
374 symtable->append(value);
375 } else {
376 String key(index, index_len, CopyString);
377 if (overwrite || !symtable->exists(key)) {
378 symtable->set(key, value);
384 enum class ContextOfException {
385 ReqInit = 1,
386 Invoke,
387 Handler,
390 static void handle_exception_append_bt(std::string& errorMsg,
391 const ExtendedException& e) {
392 Array bt = e.getBacktrace();
393 if (!bt.empty()) {
394 errorMsg += ExtendedLogger::StringOfStackTrace(bt);
398 void bump_counter_and_rethrow(bool isPsp) {
399 try {
400 throw;
401 } catch (const RequestTimeoutException& e) {
402 if (isPsp) {
403 static auto requestTimeoutPSPCounter = ServiceData::createTimeSeries(
404 "requests_timed_out_psp", {ServiceData::StatsType::COUNT});
405 requestTimeoutPSPCounter->addValue(1);
406 ServerStats::Log("request.timed_out.psp", 1);
407 } else {
408 static auto requestTimeoutCounter = ServiceData::createTimeSeries(
409 "requests_timed_out_non_psp", {ServiceData::StatsType::COUNT});
410 requestTimeoutCounter->addValue(1);
411 ServerStats::Log("request.timed_out.non_psp", 1);
413 throw;
414 } catch (const RequestCPUTimeoutException& e) {
415 if (isPsp) {
416 static auto requestCPUTimeoutPSPCounter = ServiceData::createTimeSeries(
417 "requests_cpu_timed_out_psp", {ServiceData::StatsType::COUNT});
418 requestCPUTimeoutPSPCounter->addValue(1);
419 ServerStats::Log("request.cpu_timed_out.psp", 1);
420 } else {
421 static auto requestCPUTimeoutCounter = ServiceData::createTimeSeries(
422 "requests_cpu_timed_out_non_psp", {ServiceData::StatsType::COUNT});
423 requestCPUTimeoutCounter->addValue(1);
424 ServerStats::Log("request.cpu_timed_out.non_psp", 1);
426 throw;
427 } catch (const RequestMemoryExceededException& e) {
428 if (isPsp) {
429 static auto requestMemoryExceededPSPCounter =
430 ServiceData::createTimeSeries(
431 "requests_memory_exceeded_psp", {ServiceData::StatsType::COUNT});
432 requestMemoryExceededPSPCounter->addValue(1);
433 ServerStats::Log("request.memory_exceeded.psp", 1);
434 } else {
435 static auto requestMemoryExceededCounter = ServiceData::createTimeSeries(
436 "requests_memory_exceeded_non_psp", {ServiceData::StatsType::COUNT});
437 requestMemoryExceededCounter->addValue(1);
438 ServerStats::Log("request.memory_exceeded.non_psp", 1);
441 #ifdef USE_JEMALLOC
442 // Capture a pprof (C++) dump when we OOM a request
443 // TODO: (t3753133) Should dump a PHP-instrumented pprof dump here as well
444 jemalloc_pprof_dump("", false);
445 #endif
447 throw;
451 static void handle_exception_helper(bool& ret,
452 ExecutionContext* context,
453 std::string& errorMsg,
454 ContextOfException where,
455 bool& error,
456 bool richErrorMsg) {
457 // Clear oom/timeout while handling exception and restore them afterwards.
458 auto& flags = stackLimitAndSurprise();
459 auto const origFlags = flags.fetch_and(~ResourceFlags) & ResourceFlags;
461 SCOPE_EXIT {
462 flags.fetch_or(origFlags);
465 try {
466 bump_counter_and_rethrow(false /* isPsp */);
467 } catch (const Eval::DebuggerException &e) {
468 throw;
469 } catch (const ExitException &e) {
470 if (where == ContextOfException::ReqInit) {
471 ret = false;
472 } else if (where != ContextOfException::Handler &&
473 !context->getExitCallback().isNull() &&
474 is_callable(context->getExitCallback())) {
475 Array stack = e.getBacktrace();
476 Array argv = make_packed_array(tl_exit_code, stack);
477 vm_call_user_func(context->getExitCallback(), argv);
479 } catch (const XDebugExitExn&) {
480 // Do nothing, this is normal behavior.
481 } catch (const PhpFileDoesNotExistException &e) {
482 ret = false;
483 if (where != ContextOfException::Handler) {
484 raise_notice("%s", e.getMessage().c_str());
485 } else {
486 Logger::Error("%s", e.getMessage().c_str());
488 if (richErrorMsg) {
489 handle_exception_append_bt(errorMsg, e);
491 } catch (const Exception &e) {
492 bool oldRet = ret;
493 bool origError = error;
494 std::string origErrorMsg = errorMsg;
495 ret = false;
496 error = true;
497 errorMsg = "";
498 if (where == ContextOfException::Handler) {
499 errorMsg = "Exception handler threw an exception: ";
501 errorMsg += e.what();
502 if (where == ContextOfException::Invoke) {
503 bool handlerRet = context->onFatalError(e);
504 if (handlerRet) {
505 ret = oldRet;
506 error = origError;
507 errorMsg = origErrorMsg;
509 } else {
510 Logger::Error("%s", errorMsg.c_str());
512 if (richErrorMsg) {
513 const ExtendedException *ee = dynamic_cast<const ExtendedException *>(&e);
514 if (ee) {
515 handle_exception_append_bt(errorMsg, *ee);
518 } catch (const Object &e) {
519 bool oldRet = ret;
520 bool origError = error;
521 auto const origErrorMsg = errorMsg;
522 ret = false;
523 error = true;
524 errorMsg = "";
525 if (where == ContextOfException::Handler) {
526 errorMsg = "Exception handler threw an object exception: ";
528 try {
529 errorMsg += e.toString().data();
530 } catch (...) {
531 errorMsg += "(unable to call toString())";
533 if (where == ContextOfException::Invoke) {
534 bool handlerRet = context->onUnhandledException(e);
535 if (handlerRet) {
536 ret = oldRet;
537 error = origError;
538 errorMsg = origErrorMsg;
540 } else {
541 Logger::Error("%s", errorMsg.c_str());
543 } catch (...) {
544 ret = false;
545 error = true;
546 errorMsg = "(unknown exception was thrown)";
547 Logger::Error("%s", errorMsg.c_str());
551 static bool hphp_chdir_file(const std::string& filename) {
552 bool ret = false;
553 String s = File::TranslatePath(filename);
554 char *buf = strndup(s.data(), s.size());
555 char *dir = dirname(buf);
556 assertx(dir);
557 if (dir) {
558 if (File::IsVirtualDirectory(dir)) {
559 g_context->setCwd(String(dir, CopyString));
560 ret = true;
561 } else {
562 struct stat sb;
563 stat(dir, &sb);
564 if ((sb.st_mode & S_IFMT) == S_IFDIR) {
565 ret = true;
566 if (*dir != '.') {
567 g_context->setCwd(String(dir, CopyString));
572 free(buf);
573 return ret;
576 static void handle_resource_exceeded_exception() {
577 try {
578 throw;
579 } catch (RequestTimeoutException&) {
580 setSurpriseFlag(TimedOutFlag);
581 } catch (RequestCPUTimeoutException&) {
582 setSurpriseFlag(CPUTimedOutFlag);
583 } catch (RequestMemoryExceededException&) {
584 setSurpriseFlag(MemExceededFlag);
585 } catch (...) {}
588 void handle_destructor_exception(const char* situation) {
589 std::string errorMsg;
591 try {
592 throw;
593 } catch (ExitException &e) {
594 // ExitException is fine, no need to show a warning.
595 TI().setPendingException(e.clone());
596 return;
597 } catch (Object &e) {
598 // For user exceptions, invoke the user exception handler
599 errorMsg = situation;
600 errorMsg += " threw an object exception: ";
601 try {
602 errorMsg += e.toString().data();
603 } catch (...) {
604 handle_resource_exceeded_exception();
605 errorMsg += "(unable to call toString())";
607 } catch (Exception &e) {
608 TI().setPendingException(e.clone());
609 errorMsg = situation;
610 errorMsg += " raised a fatal error: ";
611 errorMsg += e.what();
612 } catch (...) {
613 errorMsg = situation;
614 errorMsg += " threw an unknown exception";
616 // For fatal errors and unknown exceptions, we raise a warning.
617 // If there is a user error handler it will be invoked, otherwise
618 // the default error handler will be invoked.
619 try {
620 raise_warning_unsampled("%s", errorMsg.c_str());
621 } catch (...) {
622 handle_resource_exceeded_exception();
624 // The user error handler fataled or threw an exception,
625 // print out the error message directly to the log
626 Logger::Warning("%s", errorMsg.c_str());
630 void init_command_line_session(int argc, char** argv) {
631 StackTraceNoHeap::AddExtraLogging("ThreadType", "CLI");
632 std::string args;
633 for (int i = 0; i < argc; i++) {
634 if (i) args += " ";
635 args += argv[i];
637 StackTraceNoHeap::AddExtraLogging("Arguments", args.c_str());
639 hphp_session_init();
640 auto const context = g_context.getNoCheck();
641 context->obSetImplicitFlush(true);
644 void
645 init_command_line_globals(int argc, char** argv, char** envp,
646 int xhprof,
647 std::map<std::string, std::string>& serverVariables,
648 std::map<std::string, std::string>& envVariables) {
649 auto& variablesOrder = RID().getVariablesOrder();
651 if (variablesOrder.find('e') != std::string::npos ||
652 variablesOrder.find('E') != std::string::npos) {
653 Array envArr(Array::Create());
654 process_env_variables(envArr, envp, envVariables);
655 envArr.set(s_HPHP, 1);
656 envArr.set(s_HHVM, 1);
657 if (RuntimeOption::EvalJit) {
658 envArr.set(s_HHVM_JIT, 1);
660 switch (arch()) {
661 case Arch::X64:
662 envArr.set(s_HHVM_ARCH, "x64");
663 break;
664 case Arch::ARM:
665 envArr.set(s_HHVM_ARCH, "arm");
666 break;
667 case Arch::PPC64:
668 envArr.set(s_HHVM_ARCH, "ppc64");
669 break;
671 php_global_set(s__ENV, envArr);
674 process_cmd_arguments(argc, argv);
676 if (variablesOrder.find('s') != std::string::npos ||
677 variablesOrder.find('S') != std::string::npos) {
678 Array serverArr(Array::Create());
679 process_env_variables(serverArr, envp, envVariables);
680 time_t now;
681 struct timeval tp = {0};
682 double now_double;
683 if (!gettimeofday(&tp, nullptr)) {
684 now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
685 now = tp.tv_sec;
686 } else {
687 now = time(nullptr);
688 now_double = (double)now;
690 String file = empty_string();
691 if (argc > 0) {
692 file = String::attach(StringData::Make(argv[0], CopyString));
694 serverArr.set(s_REQUEST_START_TIME, now);
695 serverArr.set(s_REQUEST_TIME, now);
696 serverArr.set(s_REQUEST_TIME_FLOAT, now_double);
697 serverArr.set(s_DOCUMENT_ROOT, empty_string_variant_ref);
698 serverArr.set(s_SCRIPT_FILENAME, file);
699 serverArr.set(s_SCRIPT_NAME, file);
700 serverArr.set(s_PHP_SELF, file);
701 serverArr.set(s_argv, php_global(s_argv));
702 serverArr.set(s_argc, php_global(s_argc));
703 serverArr.set(s_PWD, g_context->getCwd());
704 char hostname[1024];
705 if (RuntimeOption::ServerExecutionMode() &&
706 !is_cli_mode() &&
707 !gethostname(hostname, sizeof(hostname))) {
708 // gethostname may not null-terminate
709 hostname[sizeof(hostname) - 1] = '\0';
710 serverArr.set(s_HOSTNAME, String(hostname, CopyString));
713 for (auto& kv : serverVariables) {
714 serverArr.set(String(kv.first.c_str()), String(kv.second.c_str()));
717 php_global_set(s__SERVER, serverArr);
720 if (xhprof) {
721 HHVM_FN(xhprof_enable)(xhprof, uninit_null().toArray());
724 if (RuntimeOption::RequestTimeoutSeconds) {
725 RID().setTimeout(RuntimeOption::RequestTimeoutSeconds);
728 if (RuntimeOption::XenonForceAlwaysOn) {
729 Xenon::getInstance().surpriseAll();
732 InitFiniNode::GlobalsInit();
733 // Initialize the debugger
734 DEBUGGER_ATTACHED_ONLY(phpDebuggerRequestInitHook());
737 void execute_command_line_begin(int argc, char **argv, int xhprof) {
738 init_command_line_session(argc, argv);
739 init_command_line_globals(argc, argv, environ, xhprof,
740 RuntimeOption::ServerVariables,
741 RuntimeOption::EnvVariables);
744 void execute_command_line_end(int xhprof, bool coverage, const char *program) {
745 if (RuntimeOption::EvalDumpTC ||
746 RuntimeOption::EvalDumpIR ||
747 RuntimeOption::EvalDumpRegion) {
748 jit::mcgen::joinWorkerThreads();
749 jit::tc::dump();
751 if (xhprof) {
752 Variant profileData = HHVM_FN(xhprof_disable)();
753 if (!profileData.isNull()) {
754 HHVM_FN(var_dump)(Variant::attach(
755 HHVM_FN(json_encode)(HHVM_FN(xhprof_disable)())
759 g_context->onShutdownPostSend(); // runs more php
760 Eval::Debugger::InterruptPSPEnded(program);
761 hphp_context_exit();
762 hphp_session_exit();
763 auto& ti = TI();
764 if (coverage && ti.m_reqInjectionData.getCoverage() &&
765 !RuntimeOption::CodeCoverageOutputFile.empty()) {
766 ti.m_coverage->Report(RuntimeOption::CodeCoverageOutputFile);
770 #if defined(__APPLE__) || defined(_MSC_VER)
771 const void* __hot_start = nullptr;
772 const void* __hot_end = nullptr;
773 #define AT_END_OF_TEXT
774 #else
775 #define AT_END_OF_TEXT __attribute__((__section__(".stub")))
776 #endif
778 #define ALIGN_HUGE_PAGE __attribute__((__aligned__(2 * 1024 * 1024)))
780 static void
781 NEVER_INLINE AT_END_OF_TEXT ALIGN_HUGE_PAGE __attribute__((__optimize__("2")))
782 hugifyText(char* from, char* to) {
783 #if !FOLLY_SANITIZE && defined MADV_HUGEPAGE
784 if (from > to || (to - from) < sizeof(uint64_t)) {
785 // This shouldn't happen if HHVM is behaving correctly (I think),
786 // but if it does then there is nothing to do and we should bail
787 // out early because the call to wordcpy() below can't handle
788 // zero size or negative sizes.
789 return;
792 size_t sz = to - from;
793 void* mem = malloc(sz);
794 memcpy(mem, from, sz);
796 // This maps out a portion of our executable
797 // We need to be very careful about what we do
798 // until we replace the original code
799 mmap(from, sz,
800 PROT_READ | PROT_WRITE | PROT_EXEC,
801 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
802 -1, 0);
803 // This is in glibc, which isn't a problem, except for
804 // the trampoline code in .plt, which we dealt with
805 // in the linker script
806 madvise(from, sz, MADV_HUGEPAGE);
807 // Don't use memcpy because its probably one of the
808 // functions thats been mapped out.
809 // Needs the attribute((optimize("2")) to prevent
810 // g++ from turning this back into memcpy(!)
811 wordcpy((uint64_t*)from, (uint64_t*)mem, sz / sizeof(uint64_t));
812 mprotect(from, sz, PROT_READ | PROT_EXEC);
813 free(mem);
814 mlock(from, to - from);
815 Debug::DebugInfo::setPidMapOverlay(from, to);
816 std::stringstream ss;
817 ss << "Mapped text section onto huge pages from " <<
818 std::hex << (uint64_t*)from << " to " << (uint64_t*)to;
819 Logger::Info(ss.str());
820 #endif
823 static void pagein_self(void) {
824 #if defined(USE_JEMALLOC) && (JEMALLOC_VERSION_MAJOR >= 5)
825 // jemalloc 5 has background threads, which handle purging asynchronously.
826 bool background_threads = false;
827 if (mallctlRead("background_thread", &background_threads)) {
828 background_threads = false;
829 Logger::Warning("Failed to determine jemalloc background thread state");
831 if (background_threads &&
832 mallctlWrite("background_thread", false, /* errorOK */ true)) {
833 Logger::Warning("Failed to disable jemalloc background threads");
835 SCOPE_EXIT {
836 if (background_threads &&
837 mallctlWrite("background_thread", true, /* errorOK */ true)) {
838 Logger::Warning("Failed to enable jemalloc background threads");
841 #endif
843 // Other than the jemalloc background threads, which should've been stopped by
844 // now, the only thread allowed here is the current one. Check that and alarm
845 // people when they accidentally created threads before this point.
846 int nThreads = Process::GetNumThreads();
847 if (nThreads > 1) {
848 Logger::Error("%d threads running, cannot hugify text!", nThreads);
849 fprintf(stderr,
850 "HHVM is broken: %u threads running in hugifyText()!\n",
851 nThreads);
852 if (debug) {
853 throw std::runtime_error{"you cannot create threads before pagein_self"};
857 auto mapped_huge = false;
858 #ifdef __linux__
859 auto const try_map_huge =
860 hugePagesSupported() &&
861 RuntimeOption::EvalMaxHotTextHugePages > 0 &&
862 (char*)__hot_start != nullptr && (char*)__hot_end != nullptr &&
863 nThreads <= 1;
865 SCOPE_EXIT {
866 if (try_map_huge != mapped_huge) {
867 Logger::Warning("Failed to hugify the .text section");
870 #else
871 // MacOS doesn't have transparent huge pages. It uses mmap() with
872 // VM_FLAGS_SUPERPAGE_SIZE_2MB, which we don't do here, so don't bother.
873 auto constexpr try_map_huge = false;
874 #endif
876 BootStats::Block timer("mapping self");
877 char mapname[PATH_MAX];
878 // pad due to the spaces between the inode number and the mapname
879 auto const bufsz =
880 sizeof(unsigned long) * 4 + sizeof(mapname) + sizeof(char) * 11 + 100;
881 auto buf = static_cast<char*>(malloc(bufsz));
882 if (auto fp = fopen("/proc/self/maps", "r")) {
883 while (!feof(fp)) {
884 if (fgets(buf, bufsz, fp) == 0)
885 break;
886 unsigned long begin, end, inode, pgoff;
887 char perm[5];
888 char dev[11];
889 int r = sscanf(buf, "%lx-%lx %4s %lx %10s %ld %s",
890 &begin, &end, perm, &pgoff, dev, &inode, mapname);
892 // page in read-only segments that correspond to a file on disk
893 if (r != 7 ||
894 perm[0] != 'r' ||
895 perm[1] != '-' ||
896 access(mapname, F_OK) != 0) {
897 continue;
900 auto beginPtr = (char*)begin;
901 auto endPtr = (char*)end;
902 auto hotStart = (char*)__hot_start;
903 auto hotEnd = (char*)__hot_end;
904 const size_t hugePageBytes = 2L * 1024 * 1024;
906 if (mlock(beginPtr, end - begin) == 0) {
907 if (try_map_huge && beginPtr <= hotStart && hotEnd <= endPtr) {
908 char* from = hotStart - ((intptr_t)hotStart & (hugePageBytes - 1));
909 char* to = hotEnd + (hugePageBytes - 1);
910 to -= (intptr_t)to & (hugePageBytes - 1);
911 const size_t maxHugeHotTextBytes =
912 RuntimeOption::EvalMaxHotTextHugePages * hugePageBytes;
913 if (to - from > maxHugeHotTextBytes) {
914 to = from + maxHugeHotTextBytes;
916 if (to <= (void*)hugifyText) {
917 mapped_huge = true;
918 hugifyText(from, to);
921 if (!RuntimeOption::LockCodeMemory) {
922 munlock(beginPtr, end - begin);
926 fclose(fp);
928 free(buf);
931 /* Sets RuntimeOption::ExecutionMode according to commandline options prior to
932 * config load. Returns false upon unrecognized mode.
934 static bool set_execution_mode(folly::StringPiece mode) {
935 if (mode == "daemon" || mode == "server" || mode == "replay") {
936 RuntimeOption::ServerMode = true;
937 Logger::Escape = true;
938 return true;
939 } else if (mode == "run" || mode == "debug" || mode == "translate" ||
940 mode == "dumphhas" || mode == "verify" || mode == "vsdebug") {
941 // We don't run PHP in "translate" mode, so just treat it like cli mode.
942 RuntimeOption::ServerMode = false;
943 Logger::Escape = false;
944 return true;
946 // Invalid mode.
947 return false;
950 /* Reads a file into the OS page cache, with rate limiting. */
951 static bool readahead_rate(const char* path, int64_t mbPerSec) {
952 int ret = open(path, O_RDONLY);
953 if (ret < 0) return false;
954 const int fd = ret;
955 SCOPE_EXIT { close(fd); };
957 constexpr size_t kReadaheadBytes = 1 << 20;
958 std::unique_ptr<char[]> buf(new char[kReadaheadBytes]);
959 int64_t total = 0;
960 auto startTime = std::chrono::steady_clock::now();
961 do {
962 ret = read(fd, buf.get(), kReadaheadBytes);
963 if (ret > 0) {
964 total += ret;
965 // Unit math: bytes / (MB / seconds) = microseconds
966 auto endTime = startTime + std::chrono::microseconds(total / mbPerSec);
967 auto sleepT = endTime - std::chrono::steady_clock::now();
968 // Don't sleep too frequently.
969 if (sleepT >= std::chrono::seconds(1)) {
970 Logger::Info(folly::sformat(
971 "readahead sleeping {}ms after total {}b",
972 std::chrono::duration_cast<std::chrono::milliseconds>(sleepT).count(),
973 total));
974 /* sleep override */ std::this_thread::sleep_for(sleepT);
977 } while (ret > 0);
978 return ret == 0;
981 static int start_server(const std::string &username, int xhprof) {
982 if (!registrationComplete) {
983 folly::SingletonVault::singleton()->registrationComplete();
984 registrationComplete = true;
986 BootStats::start();
987 HttpServer::CheckMemAndWait();
988 InitFiniNode::ServerPreInit();
990 if (!RuntimeOption::EvalUnixServerPath.empty()) {
991 init_cli_server(RuntimeOption::EvalUnixServerPath.c_str());
994 // Before we start the webserver, make sure the entire
995 // binary is paged into memory.
996 pagein_self();
997 BootStats::mark("pagein_self");
999 set_execution_mode("server");
1001 #if !defined(SKIP_USER_CHANGE)
1002 if (!username.empty()) {
1003 if (Logger::UseCronolog) {
1004 for (const auto& el : RuntimeOption::ErrorLogs) {
1005 Cronolog::changeOwner(username, el.second.symLink);
1008 if (!Capability::ChangeUnixUser(username, RuntimeOption::AllowRunAsRoot)) {
1009 _exit(1);
1011 LightProcess::ChangeUser(username);
1012 compilers_set_user(username);
1013 } else if (getuid() == 0 && !RuntimeOption::AllowRunAsRoot) {
1014 Logger::Error("hhvm not allowed to run as root unless "
1015 "-vServer.AllowRunAsRoot=1 is used.");
1016 _exit(1);
1018 Capability::SetDumpable();
1019 // Include hugetlb pages in core dumps.
1020 Process::SetCoreDumpHugePages();
1021 #endif
1023 hphp_process_init();
1024 SCOPE_EXIT {
1025 hphp_process_exit();
1026 try { Logger::Info("all servers stopped"); } catch(...) {}
1029 HttpRequestHandler::GetAccessLog().init
1030 (RuntimeOption::AccessLogDefaultFormat, RuntimeOption::AccessLogs,
1031 username);
1032 AdminRequestHandler::GetAccessLog().init
1033 (RuntimeOption::AdminLogFormat, RuntimeOption::AdminLogSymLink,
1034 RuntimeOption::AdminLogFile,
1035 username);
1036 RPCRequestHandler::GetAccessLog().init
1037 (RuntimeOption::AccessLogDefaultFormat, RuntimeOption::RPCLogs,
1038 username);
1039 SCOPE_EXIT { HttpRequestHandler::GetAccessLog().flushAllWriters(); };
1040 SCOPE_EXIT { AdminRequestHandler::GetAccessLog().flushAllWriters(); };
1041 SCOPE_EXIT { RPCRequestHandler::GetAccessLog().flushAllWriters(); };
1042 SCOPE_EXIT { Logger::FlushAll(); };
1044 if (RuntimeOption::ServerInternalWarmupThreads > 0) {
1045 HttpServer::CheckMemAndWait();
1046 InitFiniNode::WarmupConcurrentStart(
1047 RuntimeOption::ServerInternalWarmupThreads);
1050 HttpServer::CheckMemAndWait();
1051 // Create the HttpServer before any warmup requests to properly
1052 // initialize the process
1053 HttpServer::Server = std::make_shared<HttpServer>();
1055 if (xhprof) {
1056 HHVM_FN(xhprof_enable)(xhprof, uninit_null().toArray());
1059 std::unique_ptr<std::thread> readaheadThread;
1061 if (RuntimeOption::RepoLocalReadaheadRate > 0 &&
1062 !RuntimeOption::RepoLocalPath.empty()) {
1063 HttpServer::CheckMemAndWait();
1064 readaheadThread = std::make_unique<std::thread>([&] {
1065 BootStats::Block timer("Readahead Repo");
1066 auto path = RuntimeOption::RepoLocalPath.c_str();
1067 Logger::Info("readahead %s", path);
1068 #ifdef __linux__
1069 // glibc doesn't have a wrapper for ioprio_set(), so we need to use
1070 // syscall(). The constants here are consistent with the kernel source.
1071 // See http://lxr.free-electrons.com/source/include/linux/ioprio.h
1072 auto constexpr IOPRIO_CLASS_SHIFT = 13;
1073 enum {
1074 IOPRIO_CLASS_NONE,
1075 IOPRIO_CLASS_RT,
1076 IOPRIO_CLASS_BE,
1077 IOPRIO_CLASS_IDLE,
1079 // Set to lowest IO priority.
1080 constexpr int ioprio = (IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT);
1082 // ioprio_set() is available starting kernel 2.6.13
1083 KernelVersion version;
1084 if (version.m_major > 2 ||
1085 (version.m_major == 2 &&
1086 (version.m_minor > 6 ||
1087 (version.m_minor == 6 && version.m_release >= 13)))) {
1088 syscall(SYS_ioprio_set,
1089 1 /* IOPRIO_WHO_PROCESS, in fact, it is this thread */,
1090 0 /* current thread */,
1091 ioprio);
1093 #endif
1094 const auto mbPerSec = RuntimeOption::RepoLocalReadaheadRate;
1095 if (!readahead_rate(path, mbPerSec)) {
1096 Logger::Error("readahead failed: %s", strerror(errno));
1099 if (!RuntimeOption::RepoLocalReadaheadConcurrent) {
1100 // TODO(10152762): Run this concurrently with non-disk warmup.
1101 readaheadThread->join();
1102 readaheadThread.reset();
1106 if (RuntimeOption::ServerInternalWarmupThreads > 0) {
1107 BootStats::Block timer("concurrentWaitForEnd");
1108 InitFiniNode::WarmupConcurrentWaitForEnd();
1111 if (RuntimeOption::RepoPreload) {
1112 HttpServer::CheckMemAndWait();
1113 BootStats::Block timer("Preloading Repo");
1114 profileWarmupStart();
1115 preloadRepo();
1116 profileWarmupEnd();
1119 // If we have any warmup requests, replay them before listening for
1120 // real connections
1122 Logger::Info("Warming up");
1123 if (!RuntimeOption::EvalJitProfileWarmupRequests) profileWarmupStart();
1124 SCOPE_EXIT { profileWarmupEnd(); };
1125 std::map<std::string, int> seen;
1126 for (auto& file : RuntimeOption::ServerWarmupRequests) {
1127 HttpServer::CheckMemAndWait();
1128 // Take only the last part
1129 folly::StringPiece f(file);
1130 auto pos = f.rfind('/');
1131 std::string str(pos == f.npos ? file : f.subpiece(pos + 1).str());
1132 auto count = seen[str];
1133 BootStats::Block timer(folly::sformat("warmup:{}:{}", str, count++));
1134 seen[str] = count;
1136 HttpRequestHandler handler(0);
1137 ReplayTransport rt;
1138 timespec start;
1139 Timer::GetMonotonicTime(start);
1140 std::string error;
1141 Logger::Info("Replaying warmup request %s", file.c_str());
1143 try {
1144 rt.onRequestStart(start);
1145 rt.replayInput(Hdf(file));
1146 handler.run(&rt);
1148 timespec stop;
1149 Timer::GetMonotonicTime(stop);
1150 Logger::Info("Finished successfully in %ld seconds",
1151 stop.tv_sec - start.tv_sec);
1152 } catch (std::exception& e) {
1153 error = e.what();
1156 if (error.size()) {
1157 Logger::Info("Got exception during warmup: %s", error.c_str());
1161 BootStats::mark("warmup");
1163 if (RuntimeOption::StopOldServer) HttpServer::StopOldServer();
1165 if (RuntimeOption::EvalEnableNuma && !getenv("HHVM_DISABLE_NUMA")) {
1166 #ifdef USE_JEMALLOC
1167 unsigned narenas;
1168 size_t mib[3];
1169 size_t miblen = 3;
1170 if (mallctlWrite<uint64_t>("epoch", 1, true) == 0 &&
1171 mallctlRead("arenas.narenas", &narenas, true) == 0 &&
1172 mallctlnametomib("arena.0.purge", mib, &miblen) == 0) {
1173 mib[1] = size_t(narenas);
1174 mallctlbymib(mib, miblen, nullptr, nullptr, nullptr, 0);
1176 #endif
1177 enable_numa(RuntimeOption::EvalEnableNumaLocal);
1178 BootStats::mark("enable_numa");
1181 HttpServer::CheckMemAndWait(true); // Final wait
1182 if (readaheadThread.get()) {
1183 readaheadThread->join();
1184 readaheadThread.reset();
1187 if (!RuntimeOption::EvalUnixServerPath.empty()) {
1188 start_cli_server();
1191 SlabManager::init(); // allocate hugetlb pages for pooled slabs
1193 HttpServer::Server->runOrExitProcess();
1194 HttpServer::Server.reset();
1196 return 0;
1199 static void logSettings() {
1200 if (RuntimeOption::ServerLogSettingsOnStartup) {
1201 Logger::Info("Settings: %s\n", IniSetting::GetAllAsJSON().c_str());
1205 static InitFiniNode s_logSettings(logSettings, InitFiniNode::When::ServerInit);
1207 std::string translate_stack(const char *hexencoded, bool with_frame_numbers) {
1208 if (!hexencoded || !*hexencoded) {
1209 return "";
1212 StackTrace st(hexencoded);
1213 std::vector<std::shared_ptr<StackFrameExtra>> frames;
1214 st.get(frames);
1216 std::ostringstream out;
1217 for (size_t i = 0; i < frames.size(); i++) {
1218 auto f = frames[i];
1219 if (with_frame_numbers) {
1220 out << "# " << (i < 10 ? " " : "") << i << ' ';
1222 out << f->toString();
1223 out << '\n';
1225 return out.str();
1228 ///////////////////////////////////////////////////////////////////////////////
1230 static void prepare_args(int &argc,
1231 char **&argv,
1232 const std::vector<std::string> &args,
1233 const char *file) {
1234 argv = (char **)malloc((args.size() + 2) * sizeof(char*));
1235 argc = 0;
1236 if (file && *file) {
1237 argv[argc++] = (char*)file;
1239 for (int i = 0; i < (int)args.size(); i++) {
1240 argv[argc++] = (char*)args[i].c_str();
1242 argv[argc] = nullptr;
1245 static int execute_program_impl(int argc, char **argv);
1246 int execute_program(int argc, char **argv) {
1247 int ret_code = -1;
1248 try {
1249 try {
1250 initialize_repo();
1251 ret_code = execute_program_impl(argc, argv);
1252 } catch (const Exception &e) {
1253 Logger::Error("Uncaught exception: %s", e.what());
1254 throw;
1255 } catch (const std::exception &e) {
1256 Logger::Error("Uncaught exception: %s", e.what());
1257 throw;
1258 } catch (...) {
1259 Logger::Error("Uncaught exception: (unknown)");
1260 throw;
1262 if (tempFile.length() && boost::filesystem::exists(tempFile)) {
1263 boost::filesystem::remove(tempFile);
1265 } catch (...) {
1266 if (HttpServer::Server ||
1267 folly::SingletonVault::singleton()->livingSingletonCount()) {
1268 // an exception was thrown that prevented proper shutdown. Its not
1269 // safe to destroy the globals, or run atexit handlers.
1270 // abort() so it shows up as a crash, and we can diagnose/fix the
1271 // exception
1272 abort();
1276 return ret_code;
1279 static bool open_server_log_files() {
1280 bool openedLog = false;
1281 for (const auto& el : RuntimeOption::ErrorLogs) {
1282 bool ok = true;
1283 const auto& name = el.first;
1284 const auto& errlog = el.second;
1285 if (!errlog.logFile.empty()) {
1286 if (errlog.isPipeOutput()) {
1287 auto output = popen(errlog.logFile.substr(1).c_str(), "w");
1288 ok = (output != nullptr);
1289 Logger::SetOutput(name, output, true);
1290 } else if (Logger::UseCronolog && errlog.hasTemplate()) {
1291 auto cronoLog = Logger::CronoOutput(name);
1292 always_assert(cronoLog);
1293 cronoLog->m_template = errlog.logFile;
1294 cronoLog->setPeriodicity();
1295 if (errlog.periodMultiplier) {
1296 cronoLog->m_periodMultiple = errlog.periodMultiplier;
1298 cronoLog->m_linkName = errlog.symLink;
1299 } else {
1300 auto output = fopen(errlog.logFile.c_str(), "a");
1301 ok = (output != nullptr);
1302 Logger::SetOutput(name, output, false);
1304 if (!ok) Logger::Error("Can't open log file: %s", errlog.logFile.c_str());
1305 openedLog |= ok;
1308 return openedLog;
1311 static int compute_hhvm_argc(const options_description& desc,
1312 int argc, char** argv) {
1313 enum ArgCode {
1314 NO_ARG = 0,
1315 ARG_REQUIRED = 1,
1316 ARG_OPTIONAL = 2
1318 const auto& vec = desc.options();
1319 std::map<std::string,ArgCode> long_options;
1320 std::map<std::string,ArgCode> short_options;
1321 // Build lookup maps for the short options and the long options
1322 for (unsigned i = 0; i < vec.size(); ++i) {
1323 auto opt = vec[i];
1324 auto long_name = opt->long_name();
1325 ArgCode code = NO_ARG;
1326 if (opt->semantic()->max_tokens() == 1) {
1327 if (opt->semantic()->min_tokens() == 1) {
1328 code = ARG_REQUIRED;
1329 } else {
1330 code = ARG_OPTIONAL;
1333 long_options[long_name] = code;
1334 auto format_name = opt->format_name();
1335 if (format_name.size() >= 2 && format_name[0] == '-' &&
1336 format_name[1] != '-') {
1337 auto short_name = format_name.substr(1,1);
1338 short_options[short_name] = code;
1341 // Loop over the args
1342 int pos = 1;
1343 while (pos < argc) {
1344 const char* str = argv[pos];
1345 int len = strlen(str);
1346 if (len == 2 && memcmp(str, "--", 2) == 0) {
1347 // We found "--". All args after this are intended for the
1348 // PHP application
1349 ++pos;
1350 break;
1352 if (len >= 3 && str[0] == '-' && str[1] == '-') {
1353 // Handle long options
1354 ++pos;
1355 std::string s(str+2);
1356 auto it = long_options.find(s);
1357 if (it != long_options.end() && it->second != NO_ARG && pos < argc &&
1358 (it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
1359 ++pos;
1361 } else if (len >= 2 && str[0] == '-') {
1362 // Handle short options
1363 ++pos;
1364 std::string s;
1365 s.append(1, str[1]);
1366 auto it = short_options.find(s);
1367 if (it != short_options.end() && it->second != 0 && len == 2 &&
1368 pos < argc && (it->second == ARG_REQUIRED || argv[pos][0] != '-')) {
1369 ++pos;
1371 } else {
1372 // We've found a non-option argument. This arg and all args
1373 // that follow are intended for the PHP application
1374 break;
1377 return pos;
1381 * alloc.h defines a minimum C++ stack size but that only applies to threads we
1382 * manually create. When the main thread will be executing PHP rather than just
1383 * managing a server, make sure its stack is big enough.
1385 static void set_stack_size() {
1386 struct rlimit rlim;
1387 if (getrlimit(RLIMIT_STACK, &rlim) != 0) return;
1389 if (rlim.rlim_cur < kStackSizeMinimum || rlim.rlim_cur == RLIM_INFINITY) {
1390 #ifdef _WIN32
1391 Logger::Error("stack limit too small, use peflags -x to increase %zd\n",
1392 kStackSizeMinimum);
1393 #else
1394 rlim.rlim_cur = kStackSizeMinimum;
1395 if (setrlimit(RLIMIT_STACK, &rlim)) {
1396 Logger::Error("failed to set stack limit to %zd\n", kStackSizeMinimum);
1398 #endif
1402 #if defined(BOOST_VERSION) && BOOST_VERSION <= 105400
1403 std::string get_right_option_name(const basic_parsed_options<char>& opts,
1404 std::string& wrong_name) {
1405 // Remove any - from the wrong name for better comparing
1406 // since it will probably come prepended with --
1407 wrong_name.erase(
1408 std::remove(wrong_name.begin(), wrong_name.end(), '-'), wrong_name.end());
1409 for (basic_option<char> opt : opts.options) {
1410 std::string s_opt = opt.string_key;
1411 // We are only dealing with options that have a - in them.
1412 if (s_opt.find("-") != std::string::npos) {
1413 if (s_opt.find(wrong_name) != std::string::npos) {
1414 return s_opt;
1418 return "";
1420 #endif
1422 static int execute_program_impl(int argc, char** argv) {
1423 std::string usage = "Usage:\n\n ";
1424 usage += argv[0];
1425 usage += " [-m <mode>] [<options>] [<arg1>] [<arg2>] ...\n\nOptions";
1427 ProgramOptions po;
1428 options_description desc(usage.c_str());
1429 desc.add_options()
1430 ("help", "display this message")
1431 ("version", "display version number")
1432 ("modules", "display modules")
1433 ("info", "PHP information")
1434 ("php", "emulate the standard php command line")
1435 ("compiler-id", "display the git hash for the compiler")
1436 ("repo-schema", "display the repository schema id")
1437 ("mode,m", value<std::string>(&po.mode)->default_value("run"),
1438 "run | debug (d) | vsdebug | server (s) | daemon | replay | "
1439 "translate (t) | verify")
1440 ("interactive,a", "Shortcut for --mode debug") // -a is from PHP5
1441 ("config,c", value<std::vector<std::string>>(&po.config)->composing(),
1442 "load specified config file")
1443 ("config-value,v",
1444 value<std::vector<std::string>>(&po.confStrings)->composing(),
1445 "individual configuration string in a format of name=value, where "
1446 "name can be any valid configuration for a config file")
1447 ("define,d", value<std::vector<std::string>>(&po.iniStrings)->composing(),
1448 "define an ini setting in the same format ( foo[=bar] ) as provided in a "
1449 ".ini file")
1450 ("no-config", "don't use the default php.ini")
1451 ("port,p", value<int>(&po.port)->default_value(-1),
1452 "start an HTTP server at specified port")
1453 ("port-fd", value<int>(&po.portfd)->default_value(-1),
1454 "use specified fd instead of creating a socket")
1455 ("ssl-port-fd", value<int>(&po.sslportfd)->default_value(-1),
1456 "use specified fd for SSL instead of creating a socket")
1457 ("admin-port", value<int>(&po.admin_port)->default_value(-1),
1458 "start admin listener at specified port")
1459 ("debug-config", value<std::string>(&po.debugger_options.configFName),
1460 "load specified debugger config file")
1461 ("debug-host,h",
1462 value<std::string>(&po.debugger_options.host)->implicit_value("localhost"),
1463 "connect to debugger server at specified address")
1464 ("debug-port", value<int>(&po.debugger_options.port)->default_value(-1),
1465 "connect to debugger server at specified port")
1466 ("debug-extension", value<std::string>(&po.debugger_options.extension),
1467 "PHP file that extends command 'arg'")
1468 ("debug-cmd", value<std::vector<std::string>>(
1469 &po.debugger_options.cmds)->composing(),
1470 "executes this debugger command and returns its output in stdout")
1471 ("debug-sandbox",
1472 value<std::string>(&po.debugger_options.sandbox)->default_value("default"),
1473 "initial sandbox to attach to when debugger is started")
1474 ("user,u", value<std::string>(&po.user),
1475 "run server under this user account")
1476 ("file,f", value<std::string>(&po.file),
1477 "execute specified file")
1478 ("lint,l", value<std::string>(&po.lint),
1479 "lint specified file")
1480 ("show,w", value<std::string>(&po.show),
1481 "output specified file and do nothing else")
1482 ("temp-file",
1483 "file specified is temporary and removed after execution")
1484 ("count", value<int>(&po.count)->default_value(1),
1485 "how many times to repeat execution")
1486 ("no-safe-access-check",
1487 value<bool>(&po.noSafeAccessCheck)->default_value(false),
1488 "whether to ignore safe file access check")
1489 ("arg", value<std::vector<std::string>>(&po.args)->composing(),
1490 "arguments")
1491 ("extra-header", value<std::string>(&Logger::ExtraHeader),
1492 "extra-header to add to log lines")
1493 ("build-id", value<std::string>(&po.buildId),
1494 "unique identifier of compiled server code")
1495 ("instance-id", value<std::string>(&po.instanceId),
1496 "unique identifier of server instance")
1497 ("xhprof-flags", value<int>(&po.xhprofFlags)->default_value(0),
1498 "Set XHProf flags")
1499 ("vsDebugPort", value<int>(&po.vsDebugPort)->default_value(-1),
1500 "Debugger port to listen on for the VS Code debugger extension")
1501 ("vsDebugNoWait", value<bool>(&po.vsDebugNoWait)->default_value(false),
1502 "Indicates the debugger should not block script startup waiting for "
1503 "a debugger client to attach. Only applies if vsDebugPort is specified.")
1506 positional_options_description p;
1507 p.add("arg", -1);
1508 variables_map vm;
1510 // Before invoking the boost command line parser, we do a manual pass
1511 // to find the first occurrence of either "--" or a non-option argument
1512 // in order to determine which arguments should be consumed by HHVM and
1513 // which arguments should be passed along to the PHP application. This
1514 // is necessary so that the boost command line parser doesn't choke on
1515 // args intended for the PHP application.
1516 int hhvm_argc = compute_hhvm_argc(desc, argc, argv);
1517 // Need to have a parent try for opts so I can use opts in the catch of
1518 // one of the sub-tries below.
1519 try {
1520 // Invoke the boost command line parser to parse the args for HHVM.
1521 auto opts = command_line_parser(hhvm_argc, argv)
1522 .options(desc)
1523 .positional(p)
1524 // If these style options are changed, compute_hhvm_argc() will
1525 // need to be updated appropriately
1526 .style(command_line_style::default_style &
1527 ~command_line_style::allow_guessing &
1528 ~command_line_style::allow_sticky &
1529 ~command_line_style::long_allow_adjacent)
1530 .run();
1531 try {
1532 // Manually append the args for the PHP application.
1533 int pos = 0;
1534 for (unsigned m = 0; m < opts.options.size(); ++m) {
1535 const auto& bo = opts.options[m];
1536 if (bo.string_key == "arg") {
1537 ++pos;
1540 for (unsigned m = hhvm_argc; m < argc; ++m) {
1541 std::string str = argv[m];
1542 basic_option<char> bo;
1543 bo.string_key = "arg";
1544 bo.position_key = pos++;
1545 bo.value.push_back(str);
1546 bo.original_tokens.push_back(str);
1547 bo.unregistered = false;
1548 bo.case_insensitive = false;
1549 opts.options.push_back(bo);
1551 // Process the options
1552 store(opts, vm);
1553 notify(vm);
1554 if (vm.count("interactive") /* or -a */) po.mode = "debug";
1555 else if (po.mode.empty()) po.mode = "run";
1556 else if (po.mode == "d") po.mode = "debug";
1557 else if (po.mode == "s") po.mode = "server";
1558 else if (po.mode == "t") po.mode = "translate";
1560 if (!set_execution_mode(po.mode)) {
1561 Logger::Error("Error in command line: invalid mode: %s",
1562 po.mode.c_str());
1563 cout << desc << "\n";
1564 return -1;
1566 if (po.config.empty() && !vm.count("no-config")
1567 && ::getenv("HHVM_NO_DEFAULT_CONFIGS") == nullptr) {
1568 auto file_callback = [&po] (const char *filename) {
1569 Logger::Verbose("Using default config file: %s", filename);
1570 po.config.push_back(filename);
1572 add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/php*.ini",
1573 file_callback);
1574 add_default_config_files_globbed(DEFAULT_CONFIG_DIR "/config*.hdf",
1575 file_callback);
1577 const auto env_config = ::getenv("HHVM_CONFIG_FILE");
1578 if (env_config != nullptr) {
1579 add_default_config_files_globbed(
1580 env_config,
1581 [&po](const char* filename) {
1582 Logger::Verbose("Using config file from environment: %s", filename);
1583 po.config.push_back(filename);
1587 // When we upgrade boost, we can remove this and also get rid of the parent
1588 // try statement and move opts back into the original try block
1589 #if defined(BOOST_VERSION) && BOOST_VERSION >= 105000 && BOOST_VERSION <= 105400
1590 } catch (const error_with_option_name &e) {
1591 std::string wrong_name = e.get_option_name();
1592 std::string right_name = get_right_option_name(opts, wrong_name);
1593 std::string message = e.what();
1594 if (right_name != "") {
1595 boost::replace_all(message, wrong_name, right_name);
1597 Logger::Error("Error in command line: %s", message.c_str());
1598 cout << desc << "\n";
1599 return -1;
1600 #endif
1601 } catch (const error &e) {
1602 Logger::Error("Error in command line: %s", e.what());
1603 cout << desc << "\n";
1604 return -1;
1605 } catch (...) {
1606 Logger::Error("Error in command line.");
1607 cout << desc << "\n";
1608 return -1;
1610 } catch (const error &e) {
1611 Logger::Error("Error in command line: %s", e.what());
1612 cout << desc << "\n";
1613 return -1;
1614 } catch (...) {
1615 Logger::Error("Error in command line parsing.");
1616 cout << desc << "\n";
1617 return -1;
1619 // reuse -h for help command if possible
1620 if (vm.count("help") || (vm.count("debug-host") && po.mode != "debug")) {
1621 cout << desc << "\n";
1622 return 0;
1624 if (vm.count("version")) {
1625 cout << "HipHop VM";
1626 cout << " " << HHVM_VERSION;
1627 cout << " (" << (debug ? "dbg" : "rel") << ")\n";
1628 cout << "Compiler: " << compilerId() << "\n";
1629 cout << "Repo schema: " << repoSchemaId() << "\n";
1630 return 0;
1632 if (vm.count("modules")) {
1633 Array exts = ExtensionRegistry::getLoaded();
1634 cout << "[PHP Modules]" << "\n";
1635 for (ArrayIter iter(exts); iter; ++iter) {
1636 cout << iter.second().toString().toCppString() << "\n";
1638 return 0;
1640 if (vm.count("compiler-id")) {
1641 cout << compilerId() << "\n";
1642 return 0;
1645 if (vm.count("repo-schema")) {
1646 cout << repoSchemaId() << "\n";
1647 return 0;
1650 if (!po.show.empty()) {
1651 auto f = req::make<PlainFile>();
1652 f->open(po.show, "r");
1653 if (!f->valid()) {
1654 Logger::Error("Unable to open file %s", po.show.c_str());
1655 return 1;
1657 f->print();
1658 f->close();
1659 return 0;
1662 po.isTempFile = vm.count("temp-file");
1664 // forget the source for systemlib.php unless we are debugging
1665 if (po.mode != "debug" && po.mode != "vsdebug") SystemLib::s_source = "";
1666 if (po.mode == "vsdebug") {
1667 RuntimeOption::EnableVSDebugger = true;
1668 RuntimeOption::VSDebuggerListenPort = po.vsDebugPort;
1669 RuntimeOption::VSDebuggerNoWait = po.vsDebugNoWait;
1672 // we need to initialize pcre cache table very early
1673 pcre_init();
1675 tl_heap.getCheck();
1676 if (RuntimeOption::ServerExecutionMode()) {
1677 // Create the hardware counter before reading options,
1678 // so that the main thread never has inherit set in server
1679 // mode
1680 HardwareCounter::s_counter.getCheck();
1682 std::vector<std::string> messages;
1683 // We want the ini map to be freed after processing and loading the options
1684 // So put this in its own block
1686 IniSettingMap ini = IniSettingMap();
1687 Hdf config;
1688 s_config_files = po.config;
1689 // Start with .hdf and .ini files
1690 for (auto& filename : s_config_files) {
1691 if (boost::filesystem::exists(filename)) {
1692 Config::ParseConfigFile(filename, ini, config);
1693 } else {
1694 Logger::Warning(
1695 "The configuration file %s does not exist",
1696 filename.c_str()
1700 // Now, take care of CLI options and then officially load and bind things
1701 s_ini_strings = po.iniStrings;
1702 RuntimeOption::Load(ini, config, po.iniStrings, po.confStrings, &messages);
1703 std::vector<std::string> badnodes;
1704 config.lint(badnodes);
1705 for (const auto& badnode : badnodes) {
1706 const auto msg = "Possible bad config node: " + badnode;
1707 fprintf(stderr, "%s\n", msg.c_str());
1708 messages.push_back(msg);
1712 std::vector<int> inherited_fds;
1713 RuntimeOption::BuildId = po.buildId;
1714 RuntimeOption::InstanceId = po.instanceId;
1716 // Do this as early as possible to avoid creating temp files and spawing
1717 // light processes. Correct compilation still requires loading all of the
1718 // ini/hdf/cli options.
1719 if (po.mode == "dumphhas" || po.mode == "verify") {
1720 if (po.file.empty() && po.args.empty()) {
1721 std::cerr << "Nothing to do. Pass a php file to compile.\n";
1722 return 1;
1725 auto const file = [] (std::string file) -> std::string {
1726 if (!FileUtil::isAbsolutePath(file)) {
1727 return SourceRootInfo::GetCurrentSourceRoot() + std::move(file);
1729 return file;
1730 }(po.file.empty() ? po.args[0] : po.file);
1732 RuntimeOption::RepoCommit = false; // avoid initializing a repo
1734 std::fstream fs(file, std::ios::in);
1735 if (!fs) {
1736 std::cerr << "Unable to open \"" << file << "\"\n";
1737 return 1;
1739 std::stringstream contents;
1740 contents << fs.rdbuf();
1742 auto const str = contents.str();
1743 auto const md5 = MD5{mangleUnitMd5(string_md5(str))};
1745 compilers_start();
1746 hphp_thread_init();
1747 g_context.getCheck();
1748 SCOPE_EXIT { hphp_thread_exit(); };
1750 // Initialize compiler state
1751 compile_file(0, 0, MD5(), 0);
1753 if (po.mode == "dumphhas") RuntimeOption::EvalDumpHhas = true;
1754 else RuntimeOption::EvalVerifyOnly = true;
1755 SystemLib::s_inited = true;
1757 // Ensure write to SystemLib::s_inited is visible by other threads.
1758 std::atomic_thread_fence(std::memory_order_release);
1760 auto compiled = compile_file(str.c_str(), str.size(), md5, file.c_str(),
1761 nullptr);
1763 if (po.mode == "verify") {
1764 return 0;
1767 // This will dump the hhas for file as EvalDumpHhas was set
1768 if (!compiled) {
1769 std::cerr << "Unable to compile \"" << file << "\"\n";
1770 return 1;
1773 return 0;
1776 if (po.port != -1) {
1777 RuntimeOption::ServerPort = po.port;
1779 if (po.portfd != -1) {
1780 RuntimeOption::ServerPortFd = po.portfd;
1781 inherited_fds.push_back(po.portfd);
1783 if (po.sslportfd != -1) {
1784 RuntimeOption::SSLPortFd = po.sslportfd;
1785 inherited_fds.push_back(po.sslportfd);
1787 if (po.admin_port != -1) {
1788 RuntimeOption::AdminServerPort = po.admin_port;
1790 if (po.noSafeAccessCheck) {
1791 RuntimeOption::SafeFileAccess = false;
1793 IniSetting::s_system_settings_are_set = true;
1794 tl_heap->resetRuntimeOptions();
1796 auto opened_logs = open_server_log_files();
1797 if (po.mode == "daemon") {
1798 if (!opened_logs) {
1799 Logger::Error("Log file not specified under daemon mode.\n\n");
1801 proc::daemonize();
1804 if (RuntimeOption::ServerExecutionMode()) {
1805 for (auto const& m : messages) {
1806 Logger::Info(m);
1810 #ifndef _MSC_VER
1811 // Defer the initialization of light processes until the log file handle is
1812 // created, so that light processes can log to the right place. If we ever
1813 // lose a light process, stop the server instead of proceeding in an
1814 // uncertain state. Don't start them in DumpHhas mode because
1815 // it _Exit()s after loading the first non-systemlib unit.
1816 if (!RuntimeOption::EvalDumpHhas) {
1817 LightProcess::SetLostChildHandler([](pid_t /*child*/) {
1818 if (!HttpServer::Server) return;
1819 if (!HttpServer::Server->isStopped()) {
1820 HttpServer::Server->stopOnSignal(SIGCHLD);
1823 LightProcess::Initialize(RuntimeOption::LightProcessFilePrefix,
1824 RuntimeOption::LightProcessCount,
1825 RuntimeOption::EvalRecordSubprocessTimes,
1826 inherited_fds);
1828 #endif
1829 #ifdef USE_JEMALLOC_EXTENT_HOOKS
1830 // Set up extent hook so that we can place jemalloc metadata on 1G pages.
1831 // This needs to be done after initializing LightProcess (which forks),
1832 // because the child process does malloc which won't work with jemalloc
1833 // metadata on 1G huge pages.
1834 setup_jemalloc_metadata_extent_hook(
1835 RuntimeOption::EvalEnableArenaMetadata1GPage,
1836 RuntimeOption::EvalEnableNumaArenaMetadata1GPage,
1837 RuntimeOption::EvalArenaMetadataReservedSize
1839 #endif
1840 // We want to do this as early as possible because any allocations before-hand
1841 // will get a generic unknown type type-index.
1842 try {
1843 type_scan::init();
1844 } catch (const type_scan::InitException& exn) {
1845 Logger::Error("Unable to initialize GC type-scanners: %s", exn.what());
1846 exit(HPHP_EXIT_FAILURE);
1848 ThreadLocalManager::GetManager().initTypeIndices();
1850 // It's okay if this fails.
1851 init_member_reflection();
1853 if (!ShmCounters::initialize(true, Logger::Error)) {
1854 exit(HPHP_EXIT_FAILURE);
1856 // Initialize compiler state
1857 compile_file(0, 0, MD5(), 0);
1859 if (!po.lint.empty()) {
1860 Logger::LogHeader = false;
1861 Logger::LogLevel = Logger::LogInfo;
1862 Logger::UseCronolog = false;
1863 Logger::UseLogFile = true;
1864 // we're linting, reset whatever logger settings and write once to stdout
1865 Logger::ClearThreadLog();
1866 for (auto& el : RuntimeOption::ErrorLogs) {
1867 const auto& name = el.first;
1868 Logger::SetTheLogger(name, nullptr);
1870 Logger::SetTheLogger(Logger::DEFAULT, new Logger());
1872 if (po.isTempFile) {
1873 tempFile = po.lint;
1876 hphp_process_init();
1877 SCOPE_EXIT { hphp_process_exit(); };
1879 try {
1880 auto const unit = lookupUnit(
1881 makeStaticString(po.lint.c_str()), "", nullptr);
1882 if (unit == nullptr) {
1883 throw FileOpenException(po.lint);
1885 const StringData* msg;
1886 int line;
1887 if (unit->compileTimeFatal(msg, line)) {
1888 VMParserFrame parserFrame;
1889 parserFrame.filename = po.lint.c_str();
1890 parserFrame.lineNumber = line;
1891 Array bt = createBacktrace(BacktraceArgs()
1892 .withSelf()
1893 .setParserFrame(&parserFrame));
1894 raise_fatal_error(msg->data(), bt);
1896 } catch (FileOpenException &e) {
1897 Logger::Error("%s", e.getMessage().c_str());
1898 return 1;
1899 } catch (const FatalErrorException& e) {
1900 RuntimeOption::CallUserHandlerOnFatals = false;
1901 RuntimeOption::AlwaysLogUnhandledExceptions = false;
1902 g_context->onFatalError(e);
1903 return 1;
1905 Logger::Info("No syntax errors detected in %s", po.lint.c_str());
1906 return 0;
1909 if (argc <= 1 || po.mode == "run" || po.mode == "debug" ||
1910 po.mode == "vsdebug") {
1911 set_stack_size();
1913 if (po.isTempFile) {
1914 tempFile = po.file;
1917 set_execution_mode("run");
1918 /* recreate the hardware counters for the main thread now that we know
1919 * whether to include subprocess times */
1920 HardwareCounter::s_counter.destroy();
1921 HardwareCounter::s_counter.getCheck();
1923 int new_argc;
1924 char **new_argv;
1925 prepare_args(new_argc, new_argv, po.args, po.file.c_str());
1927 std::string const cliFile = !po.file.empty() ? po.file :
1928 new_argv[0] ? new_argv[0] : "";
1929 if (po.mode != "debug" && cliFile.empty()) {
1930 std::cerr << "Nothing to do. Either pass a .php file to run, or "
1931 "use -m server\n";
1932 return 1;
1934 Repo::setCliFile(cliFile);
1936 int ret = 0;
1937 hphp_process_init();
1938 SCOPE_EXIT { hphp_process_exit(); };
1940 if (RuntimeOption::EvalUseRemoteUnixServer != "no" &&
1941 !RuntimeOption::EvalUnixServerPath.empty() &&
1942 (!po.file.empty() || !po.args.empty())) {
1943 std::vector<std::string> args;
1944 if (!po.file.empty()) {
1945 args.emplace_back(po.file);
1947 args.insert(args.end(), po.args.begin(), po.args.end());
1948 run_command_on_cli_server(
1949 RuntimeOption::EvalUnixServerPath.c_str(), args
1951 if (RuntimeOption::EvalUseRemoteUnixServer == "only") {
1952 Logger::Error("Failed to connect to unix server.");
1953 exit(255);
1957 std::string file;
1958 if (new_argc > 0) {
1959 file = new_argv[0];
1962 if (po.mode == "debug") {
1963 StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
1964 RuntimeOption::EnableHphpdDebugger = true;
1965 po.debugger_options.fileName = file;
1966 po.debugger_options.user = po.user;
1967 Eval::DebuggerProxyPtr localProxy =
1968 Eval::Debugger::StartClient(po.debugger_options);
1969 if (!localProxy) {
1970 Logger::Error("Failed to start debugger client\n\n");
1971 return 1;
1973 Eval::Debugger::RegisterSandbox(localProxy->getDummyInfo());
1974 std::shared_ptr<std::vector<std::string>> client_args;
1975 bool restart = false;
1976 ret = 0;
1977 while (true) {
1978 try {
1979 assertx(po.debugger_options.fileName == file);
1980 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
1981 // Set the proxy for this thread to be the localProxy we just
1982 // created. If we're script debugging, this will be the proxy that
1983 // does all of our work. If we're remote debugging, this proxy will
1984 // go unused until we finally stop it when the user quits the
1985 // debugger.
1986 g_context->setSandboxId(localProxy->getDummyInfo().id());
1987 if (restart) {
1988 // Systemlib.php is not loaded again, so we need this if we
1989 // are to hit any breakpoints in systemlib.
1990 proxySetBreakPoints(localProxy.get());
1992 Eval::Debugger::DebuggerSession(po.debugger_options, restart);
1993 restart = false;
1994 execute_command_line_end(po.xhprofFlags, true, file.c_str());
1995 } catch (const Eval::DebuggerRestartException &e) {
1996 execute_command_line_end(0, false, nullptr);
1998 if (!e.m_args->empty()) {
1999 file = e.m_args->at(0);
2000 po.debugger_options.fileName = file;
2001 client_args = e.m_args;
2002 free(new_argv);
2003 prepare_args(new_argc, new_argv, *client_args, nullptr);
2005 restart = true;
2006 } catch (const Eval::DebuggerClientExitException &e) {
2007 execute_command_line_end(0, false, nullptr);
2008 break; // end user quitting debugger
2012 } else {
2013 ret = 0;
2014 for (int i = 0; i < po.count; i++) {
2015 execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
2016 ret = 255;
2017 if (hphp_invoke_simple(file, false /* warmup only */)) {
2018 ret = tl_exit_code;
2020 execute_command_line_end(po.xhprofFlags, true, file.c_str());
2024 free(new_argv);
2026 return ret;
2029 if (po.mode == "daemon" || po.mode == "server") {
2030 if (!po.user.empty()) RuntimeOption::ServerUser = po.user;
2031 return start_server(RuntimeOption::ServerUser, po.xhprofFlags);
2034 if (po.mode == "replay" && !po.args.empty()) {
2035 RuntimeOption::RecordInput = false;
2036 set_execution_mode("server");
2037 HttpServer server; // so we initialize runtime properly
2038 HttpRequestHandler handler(0);
2039 for (int i = 0; i < po.count; i++) {
2040 for (unsigned int j = 0; j < po.args.size(); j++) {
2041 ReplayTransport rt;
2042 rt.replayInput(po.args[j].c_str());
2043 handler.run(&rt);
2044 printf("%s\n", rt.getResponse().c_str());
2047 return 0;
2050 if (po.mode == "translate" && !po.args.empty()) {
2051 printf("%s", translate_stack(po.args[0].c_str()).c_str());
2052 return 0;
2055 cout << desc << "\n";
2056 return -1;
2059 String canonicalize_path(const String& p, const char* root, int rootLen) {
2060 String path = FileUtil::canonicalize(p);
2061 if (path.charAt(0) == '/') {
2062 auto const& sourceRoot = RuntimeOption::SourceRoot;
2063 int len = sourceRoot.size();
2064 if (len && strncmp(path.data(), sourceRoot.c_str(), len) == 0) {
2065 return path.substr(len);
2067 if (root && rootLen && strncmp(path.data(), root, rootLen) == 0) {
2068 return path.substr(rootLen);
2071 return path;
2074 static std::string systemlib_split(const std::string& slib, std::string* hhas) {
2075 auto pos = slib.find("\n<?hhas\n");
2076 if (pos != std::string::npos) {
2077 if (hhas) *hhas = slib.substr(pos + 8);
2078 return slib.substr(0, pos);
2080 return slib;
2083 // Retrieve a systemlib (or mini systemlib) from the
2084 // current executable or another ELF object file.
2086 // Additionally, when retrieving the main systemlib
2087 // from the current executable, honor the
2088 // HHVM_SYSTEMLIB environment variable as an override.
2089 std::string get_systemlib(std::string* hhas,
2090 const std::string &section /*= "systemlib" */,
2091 const std::string &filename /*= "" */) {
2092 if (filename.empty() && section == "systemlib") {
2093 if (auto const file = getenv("HHVM_SYSTEMLIB")) {
2094 std::ifstream ifs(file);
2095 if (ifs.good()) {
2096 return systemlib_split(std::string(
2097 std::istreambuf_iterator<char>(ifs),
2098 std::istreambuf_iterator<char>()), hhas);
2103 embedded_data desc;
2104 if (!get_embedded_data(section.c_str(), &desc, filename)) return "";
2106 auto const data = read_embedded_data(desc);
2107 return systemlib_split(data, hhas);
2110 ///////////////////////////////////////////////////////////////////////////////
2111 // C++ ffi
2113 #ifndef _MSC_VER
2114 static void on_timeout(int sig, siginfo_t* info, void* /*context*/) {
2115 if (sig == SIGVTALRM && info && info->si_code == SI_TIMER) {
2116 auto data = (RequestTimer*)info->si_value.sival_ptr;
2117 if (data) {
2118 data->onTimeout();
2119 } else {
2120 Xenon::getInstance().onTimer();
2124 #endif
2127 * Update constants to their real values and sync some runtime options
2129 static void update_constants_and_options() {
2130 assertx(ExtensionRegistry::modulesInitialised());
2131 // If extension constants were used in the ini files (e.g., E_ALL) they
2132 // would have come out as 0 in the previous pass until we load and
2133 // initialize our extensions, which we do in RuntimeOption::Load() via
2134 // ExtensionRegistry::ModuleLoad() and in ExtensionRegistry::ModuleInit()
2135 // in hphp_process_init(). We will re-import and set only the constants that
2136 // have been now bound to their proper value.
2137 IniSettingMap ini = IniSettingMap();
2138 for (auto& filename: s_config_files) {
2139 SuppressHackArrCompatNotices shacn;
2140 Config::ParseIniFile(filename, ini, true);
2142 // Reset the INI settings from the CLI.
2143 for (auto& iniStr: s_ini_strings) {
2144 SuppressHackArrCompatNotices shacn;
2145 Config::ParseIniString(iniStr, ini, true);
2148 // Reset, possibly, some request dependent runtime options based on certain
2149 // setting values. Do this here so we ensure the constants have been loaded
2150 // correctly (e.g., error_reporting E_ALL, etc.)
2151 Variant sys;
2152 if (IniSetting::GetSystem("error_reporting", sys)) {
2153 RuntimeOption::RuntimeErrorReportingLevel = sys.toInt64();
2154 RID().setErrorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel);
2156 if (IniSetting::GetSystem("memory_limit", sys)) {
2157 RID().setMemoryLimit(sys.toString().toCppString());
2158 RuntimeOption::RequestMemoryMaxBytes = RID().getMemoryLimitNumeric();
2162 void hphp_thread_init() {
2163 #ifdef USE_JEMALLOC_EXTENT_HOOKS
2164 high_arena_tcache_create();
2165 #endif
2166 ServerStats::GetLogger();
2167 zend_get_bigint_data();
2168 zend_rand_init();
2169 get_server_note();
2170 tl_heap.getCheck()->init();
2172 assertx(ThreadInfo::s_threadInfo.isNull());
2173 ThreadInfo::s_threadInfo.getCheck()->init();
2175 HardwareCounter::s_counter.getCheck();
2176 ExtensionRegistry::threadInit();
2177 InitFiniNode::ThreadInit();
2179 // Ensure that there's no request-allocated memory. This call must happen at
2180 // least once after RDS has been initialized by ThreadInfo::init(), to ensure
2181 // MemoryManager::resetGC() sets a proper trigger threshold.
2182 hphp_memory_cleanup();
2185 void hphp_thread_exit() {
2186 InitFiniNode::ThreadFini();
2187 ExtensionRegistry::threadShutdown();
2188 if (!g_context.isNull()) g_context.destroy();
2189 #ifdef USE_JEMALLOC_EXTENT_HOOKS
2190 high_arena_tcache_destroy();
2191 #endif
2194 void hphp_process_init() {
2195 pthread_attr_t attr;
2196 // Linux+GNU extension
2197 #if defined(_GNU_SOURCE) && defined(__linux__)
2198 if (pthread_getattr_np(pthread_self(), &attr) != 0 ) {
2199 Logger::Error("pthread_getattr_np failed before checking stack limits");
2200 _exit(1);
2202 #else
2203 if (pthread_attr_init(&attr) != 0 ) {
2204 Logger::Error("pthread_attr_init failed before checking stack limits");
2205 _exit(1);
2207 #endif
2208 init_stack_limits(&attr);
2209 if (pthread_attr_destroy(&attr) != 0 ) {
2210 Logger::Error("pthread_attr_destroy failed after checking stack limits");
2211 _exit(1);
2213 BootStats::mark("pthread_init");
2215 Process::InitProcessStatics();
2216 BootStats::mark("Process::InitProcessStatics");
2218 HHProf::Init();
2219 tl_miter_table.getCheck();
2221 // initialize the tzinfo cache.
2222 timezone_init();
2223 BootStats::mark("timezone_init");
2225 // start any external compilers
2226 compilers_start();
2227 BootStats::mark("compilers_start");
2229 hphp_thread_init();
2231 #ifndef _MSC_VER
2232 struct sigaction action = {};
2233 action.sa_sigaction = on_timeout;
2234 action.sa_flags = SA_SIGINFO | SA_NODEFER;
2235 sigaction(SIGVTALRM, &action, nullptr);
2236 #endif
2237 // start takes milliseconds, Period is a double in seconds
2238 Xenon::getInstance().start(1000 * RuntimeOption::XenonPeriodSeconds);
2239 BootStats::mark("xenon");
2241 // reinitialize pcre table
2242 pcre_reinit();
2243 BootStats::mark("pcre_reinit");
2245 // the liboniguruma docs say this isnt needed,
2246 // but the implementation of init is not
2247 // thread safe due to bugs
2248 onig_init();
2249 BootStats::mark("onig_init");
2251 // simple xml also needs one time init
2252 xmlInitParser();
2253 BootStats::mark("xmlInitParser");
2255 g_context.getCheck();
2256 // Some event handlers are registered during the startup process.
2257 g_context->acceptRequestEventHandlers(true);
2258 if (!registrationComplete) {
2259 folly::SingletonVault::singleton()->registrationComplete();
2260 registrationComplete = true;
2262 InitFiniNode::ProcessPreInit();
2263 // TODO(9795696): Race in thread map may trigger spurious logging at
2264 // thread exit, so for now, only spawn threads if we're a server.
2265 const uint32_t maxWorkers = RuntimeOption::ServerExecutionMode() ? 3 : 0;
2266 InitFiniNode::ProcessInitConcurrentStart(maxWorkers);
2267 SCOPE_EXIT {
2268 InitFiniNode::ProcessInitConcurrentWaitForEnd();
2269 BootStats::mark("extra_process_init_concurrent_wait");
2271 g_vmProcessInit();
2272 BootStats::mark("g_vmProcessInit");
2274 PageletServer::Restart();
2275 BootStats::mark("PageletServer::Restart");
2276 XboxServer::Restart();
2277 BootStats::mark("XboxServer::Restart");
2278 Stream::RegisterCoreWrappers();
2279 BootStats::mark("Stream::RegisterCoreWrappers");
2280 ExtensionRegistry::moduleInit();
2281 BootStats::mark("ExtensionRegistry::moduleInit");
2283 // Now that constants have been bound we can update options using constants
2284 // in ini files (e.g., E_ALL) and sync some other options
2285 update_constants_and_options();
2287 InitFiniNode::ProcessInit();
2288 BootStats::mark("extra_process_init");
2290 UnlimitSerializationScope unlimit;
2291 // TODO(9755792): Add real execution mode for snapshot generation.
2292 if (apcExtension::PrimeLibraryUpgradeDest != "") {
2293 Timer timer(Timer::WallTime, "optimizeApcPrime");
2294 apc_load(apcExtension::LoadThread);
2295 } else {
2296 apc_load(apcExtension::LoadThread);
2298 BootStats::mark("apc_load");
2301 rds::requestExit();
2302 BootStats::mark("rds::requestExit");
2303 // Reset the preloaded g_context
2304 ExecutionContext *context = g_context.getNoCheck();
2305 context->onRequestShutdown(); // TODO T20898959 kill early REH usage.
2306 context->~ExecutionContext();
2307 new (context) ExecutionContext();
2308 BootStats::mark("ExecutionContext");
2310 // TODO(9755792): Add real execution mode for snapshot generation.
2311 if (apcExtension::PrimeLibraryUpgradeDest != "") {
2312 Logger::Info("APC PrimeLibrary upgrade mode completed; exiting.");
2313 hphp_process_exit();
2314 exit(0);
2318 static void handle_exception(bool& ret, ExecutionContext* context,
2319 std::string& errorMsg, ContextOfException where,
2320 bool& error, bool richErrorMsg) {
2321 assertx(where == ContextOfException::Invoke ||
2322 where == ContextOfException::ReqInit);
2323 try {
2324 handle_exception_helper(ret, context, errorMsg, where, error, richErrorMsg);
2325 } catch (const ExitException &e) {
2326 // Got an ExitException during exception handling, handle
2327 // similarly to the case below but don't call obEndAll().
2328 } catch (...) {
2329 handle_exception_helper(ret, context, errorMsg, ContextOfException::Handler,
2330 error, richErrorMsg);
2331 context->obEndAll();
2335 static void handle_reqinit_exception(bool &ret, ExecutionContext *context,
2336 std::string &errorMsg, bool &error) {
2337 handle_exception(ret, context, errorMsg, ContextOfException::ReqInit, error,
2338 false);
2341 static void handle_invoke_exception(bool &ret, ExecutionContext *context,
2342 std::string &errorMsg, bool &error,
2343 bool richErrorMsg) {
2344 handle_exception(ret, context, errorMsg, ContextOfException::Invoke, error,
2345 richErrorMsg);
2348 static bool hphp_warmup(ExecutionContext *context,
2349 const std::string &reqInitFunc,
2350 const std::string &reqInitDoc, bool &error) {
2351 bool ret = true;
2352 error = false;
2353 std::string errorMsg;
2355 ServerStatsHelper ssh("reqinit");
2356 try {
2357 if (!reqInitDoc.empty()) {
2358 include_impl_invoke(reqInitDoc, true);
2360 if (!reqInitFunc.empty()) {
2361 invoke(reqInitFunc.c_str(), Array());
2363 context->backupSession();
2364 } catch (...) {
2365 handle_reqinit_exception(ret, context, errorMsg, error);
2368 return ret;
2371 void hphp_session_init(Transport* transport) {
2372 assertx(!s_sessionInitialized);
2373 g_context.getCheck();
2374 AsioSession::Init();
2375 Socket::clearLastError();
2376 TI().onSessionInit();
2377 tl_heap->resetExternalStats();
2379 g_thread_safe_locale_handler->reset();
2380 Treadmill::startRequest();
2382 #ifdef ENABLE_SIMPLE_COUNTER
2383 SimpleCounter::Enabled = true;
2384 StackTrace::Enabled = true;
2385 #endif
2387 // Ordering is sensitive; StatCache::requestInit produces work that
2388 // must be done in ExecutionContext::requestInit.
2389 StatCache::requestInit();
2391 // Allow request event handlers to be created now that a new request has
2392 // started.
2393 g_context->acceptRequestEventHandlers(true);
2395 g_context->requestInit();
2396 if (transport != nullptr) g_context->setTransport(transport);
2397 s_sessionInitialized = true;
2399 ExtensionRegistry::requestInit();
2401 // Sample function calls for this request
2402 if (RID().logFunctionCalls()) {
2403 EventHook::Enable();
2406 auto const pme_freq = RuntimeOption::EvalPerfMemEventRequestFreq;
2407 if (pme_freq > 0 && folly::Random::rand32(pme_freq) == 0) {
2408 // Enable memory access sampling for this request.
2409 perf_event_enable(
2410 RuntimeOption::EvalPerfMemEventSampleFreq,
2411 [] (PerfEvent) { setSurpriseFlag(PendingPerfEventFlag); }
2416 bool hphp_invoke_simple(const std::string& filename, bool warmupOnly) {
2417 bool error;
2418 std::string errorMsg;
2419 return hphp_invoke(g_context.getNoCheck(), filename, false, null_array,
2420 uninit_null(), "", "", error, errorMsg,
2421 true /* once */,
2422 warmupOnly,
2423 false /* richErrorMsg */,
2424 RuntimeOption::EvalPreludePath);
2427 const StaticString s_slash("/");
2429 static void run_prelude(std::string prelude, String cmd,
2430 const char* currentDir) {
2431 prelude = "/" + prelude;
2432 struct stat s;
2433 auto cwd = resolveVmInclude(cmd.get(), currentDir, &s);
2434 if (cwd.isNull()) return;
2435 auto const w = Stream::getWrapperFromURI(cwd, nullptr, false);
2436 do {
2437 cwd = f_dirname(cwd);
2438 auto const f = String::attach(
2439 StringData::Make(cwd.data(), prelude.data())
2441 if (w->access(f, R_OK) == 0) {
2442 require(f.data(), false, currentDir, true);
2443 break;
2445 } while (!cwd.empty() && !cwd.equal(s_slash));
2448 bool hphp_invoke(ExecutionContext *context, const std::string &cmd,
2449 bool func, const Array& funcParams, VRefParam funcRet,
2450 const std::string &reqInitFunc, const std::string &reqInitDoc,
2451 bool &error, std::string &errorMsg,
2452 bool once, bool warmupOnly,
2453 bool richErrorMsg, const std::string& prelude) {
2454 bool isServer =
2455 RuntimeOption::ServerExecutionMode() && !is_cli_mode();
2456 error = false;
2458 // Make sure we have the right current working directory within the repo
2459 // based on what server.source_root was set to (current process directory
2460 // being the default)
2461 if (RuntimeOption::RepoAuthoritative) {
2462 context->setCwd(RuntimeOption::SourceRoot);
2465 String oldCwd;
2466 if (isServer) {
2467 oldCwd = context->getCwd();
2469 if (!hphp_warmup(context, reqInitFunc, reqInitDoc, error)) {
2470 if (isServer) context->setCwd(oldCwd);
2471 return false;
2474 tl_heap->resetCouldOOM(isStandardRequest());
2475 RID().resetTimer();
2477 bool ret = true;
2478 if (!warmupOnly) {
2479 try {
2480 ServerStatsHelper ssh("invoke");
2481 if (!RuntimeOption::AutoPrependFile.empty() &&
2482 RuntimeOption::AutoPrependFile != "none") {
2483 require(RuntimeOption::AutoPrependFile, false,
2484 context->getCwd().data(), true);
2486 if (!prelude.empty()) {
2487 run_prelude(prelude, String(cmd, CopyString), context->getCwd().data());
2489 if (func) {
2490 funcRet.assignIfRef(invoke(cmd.c_str(), funcParams));
2491 } else {
2492 if (isServer) hphp_chdir_file(cmd);
2493 include_impl_invoke(cmd.c_str(), once);
2495 if (!RuntimeOption::AutoAppendFile.empty() &&
2496 RuntimeOption::AutoAppendFile != "none") {
2497 require(RuntimeOption::AutoAppendFile, false,
2498 context->getCwd().data(), true);
2500 } catch (...) {
2501 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
2505 try {
2506 context->onShutdownPreSend();
2507 } catch (...) {
2508 handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg);
2511 if (isServer) context->setCwd(oldCwd);
2512 return ret;
2515 void hphp_context_shutdown() {
2516 // Run shutdown handlers. This may cause user code to run.
2517 g_thread_safe_locale_handler->reset();
2519 auto const context = g_context.getNoCheck();
2520 context->destructObjects();
2521 context->onRequestShutdown();
2523 try {
2524 // Shutdown the debugger. This can throw, but we don't care about what the
2525 // error is.
2526 DEBUGGER_ATTACHED_ONLY(phpDebuggerRequestShutdownHook());
2527 } catch (...) {
2528 // Gotta catch 'em all!
2531 // Extensions could have shutdown handlers
2532 ExtensionRegistry::requestShutdown();
2533 InitFiniNode::RequestFini();
2535 // Extension shutdown could have re-initialized some
2536 // request locals
2537 context->onRequestShutdown();
2539 // This causes request event handler registration to fail until the next
2540 // request starts.
2541 context->acceptRequestEventHandlers(false);
2544 void hphp_context_exit(bool shutdown /* = true */) {
2545 if (shutdown) {
2546 hphp_context_shutdown();
2549 // Clean up a bunch of request state. No user code after this point.
2550 MemoryManager::setExiting();
2551 auto const context = g_context.getNoCheck();
2552 context->requestExit();
2553 context->obProtect(false);
2554 context->obEndAll();
2557 void hphp_memory_cleanup() {
2558 auto& mm = *tl_heap;
2559 // sweep functions are allowed to access g_context,
2560 // so we can't destroy it yet
2561 mm.sweep();
2563 // We should never have any registered RequestEventHandlers. If we do
2564 // something after onRequestShutdown registered a RequestEventHandler.
2565 // Its now too late to run the requestShutdown functions, but if we carry
2566 // on, requestInit and requestShutdown will never be called again.
2567 // I considered just clearing the inited flags; which works for some
2568 // RequestEventHandlers - but its a disaster for others. So just fail hard
2569 // here.
2570 always_assert(g_context.isNull() || !g_context->hasRequestEventHandlers());
2572 // g_context is request allocated, and has some members that need
2573 // cleanup, so destroy it before its too late
2574 g_context.destroy();
2576 weakref_cleanup();
2577 mm.resetAllocator();
2578 mm.resetCouldOOM();
2581 void hphp_session_exit(const Transport* transport) {
2582 assertx(s_sessionInitialized);
2583 // Server note and INI have to live long enough for the access log to fire.
2584 // RequestLocal is too early.
2585 ServerNote::Reset();
2586 IniSetting::ResetSavedDefaults();
2587 // In JitPGO mode, check if it's time to schedule the retranslation of all
2588 // profiled functions and, if so, schedule it.
2589 jit::mcgen::checkRetranslateAll();
2590 jit::tc::requestExit();
2591 // Similarly, apc strings could be in the ServerNote array, and
2592 // it's possible they are scheduled to be destroyed after this request
2593 // finishes.
2594 Treadmill::finishRequest();
2596 TI().onSessionExit();
2598 // We might have events from after the final surprise flag check of the
2599 // request, so consume them here.
2600 perf_event_consume(record_perf_mem_event);
2601 perf_event_disable();
2604 ServerStatsHelper ssh("rollback");
2606 hphp_memory_cleanup();
2609 assertx(tl_heap->empty());
2611 s_sessionInitialized = false;
2612 s_extra_request_nanoseconds = 0;
2614 if (transport) {
2615 std::unique_ptr<StructuredLogEntry> entry;
2616 if (RuntimeOption::EvalProfileHWStructLog) {
2617 entry = std::make_unique<StructuredLogEntry>();
2618 entry->setInt("response_code", transport->getResponseCode());
2620 HardwareCounter::UpdateServiceData(transport->getCpuTime(),
2621 transport->getWallTime(),
2622 entry.get(),
2623 true /*psp*/);
2624 if (entry) StructuredLog::log("hhvm_request_perf", *entry);
2628 void hphp_process_exit() noexcept {
2629 // We want to do clean up on a best-effort basis: don't skip later steps if
2630 // an earlier step fails, and don't propagate exceptions ouf of this function
2631 #define LOG_AND_IGNORE(voidexpr) try { voidexpr; } catch (...) { \
2632 Logger::Error("got exception in cleanup step: " #voidexpr); }
2633 LOG_AND_IGNORE(teardown_cli_server())
2634 LOG_AND_IGNORE(Xenon::getInstance().stop())
2635 LOG_AND_IGNORE(jit::mcgen::joinWorkerThreads())
2636 LOG_AND_IGNORE(jit::tc::processExit())
2637 LOG_AND_IGNORE(PageletServer::Stop())
2638 LOG_AND_IGNORE(XboxServer::Stop())
2639 // Debugger::Stop() needs an execution context
2640 LOG_AND_IGNORE(g_context.getCheck())
2641 LOG_AND_IGNORE(Eval::Debugger::Stop())
2642 LOG_AND_IGNORE(g_context.destroy())
2643 LOG_AND_IGNORE(ExtensionRegistry::moduleShutdown())
2644 LOG_AND_IGNORE(compilers_shutdown())
2645 #ifndef _MSC_VER
2646 LOG_AND_IGNORE(LightProcess::Close())
2647 #endif
2648 LOG_AND_IGNORE(InitFiniNode::ProcessFini())
2649 LOG_AND_IGNORE(folly::SingletonVault::singleton()->destroyInstances())
2650 LOG_AND_IGNORE(embedded_data_cleanup())
2651 #undef LOG_AND_IGNORE
2654 bool is_hphp_session_initialized() {
2655 return s_sessionInitialized;
2658 static struct SetThreadInitFini {
2659 template<class ThreadT> static typename std::enable_if<
2660 std::is_integral<ThreadT>::value || std::is_pointer<ThreadT>::value>::type
2661 recordThreadAddr(ThreadT threadId, char* stackAddr, size_t stackSize) {
2662 // In the current glibc implementation, pthread_t is a 64-bit unsigned
2663 // integer, whose value equals the address of the thread control block
2664 // (TCB). In x64_64, this is right above the TLS block. In addition,
2665 // TLS and TCB sits at the high end of the stack, i.e.,
2667 // stackAddr + stackSize ----> +---------------+
2668 // | TCB |
2669 // threadId ----> +---------------+
2670 // | TLS |
2671 // +---------------+
2672 // | Stack |
2673 // . .
2674 // . .
2675 // stackAddr ----> +---------------+
2676 auto const tcbBase = reinterpret_cast<char*>(threadId);
2677 auto stackEnd = stackAddr + stackSize;
2678 if (tcbBase > stackAddr && tcbBase < stackEnd) { // the expected layout
2679 // TCB
2680 Debug::DebugInfo::recordDataMap(
2681 tcbBase, stackEnd,
2682 folly::sformat("Thread-{}", static_cast<void*>(tcbBase)));
2683 // TLS
2684 auto const tlsRange = getCppTdata();
2685 auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
2686 stackEnd = tcbBase - tlsSize;
2687 if (tlsSize) {
2688 Debug::DebugInfo::recordDataMap(
2689 stackEnd, tcbBase,
2690 folly::sformat("TLS-{}", static_cast<void*>(tcbBase)));
2693 Debug::DebugInfo::recordDataMap(
2694 stackAddr, stackEnd,
2695 folly::sformat("Stack-{}", static_cast<void*>(tcbBase)));
2697 template <class ThreadT>
2698 static typename std::enable_if<!std::is_integral<ThreadT>::value &&
2699 !std::is_pointer<ThreadT>::value>::type
2700 recordThreadAddr(ThreadT /*threadId*/, char* stackAddr, size_t stackSize) {
2701 // pthread_t is not an integer or pointer to TCB in this pthread
2702 // implementation. But we can still figure out where TLS is.
2703 auto const tlsRange = getCppTdata();
2704 auto const tlsSize = (tlsRange.second + 15) / 16 * 16;
2705 auto const tlsBaseAddr = reinterpret_cast<char*>(tlsBase());
2706 Debug::DebugInfo::recordDataMap(
2707 tlsBaseAddr, tlsBaseAddr + tlsSize,
2708 folly::sformat("TLS-{}", static_cast<void*>(stackAddr)));
2709 Debug::DebugInfo::recordDataMap(
2710 stackAddr, stackAddr + stackSize,
2711 folly::sformat("Stack-{}", static_cast<void*>(stackAddr)));
2714 SetThreadInitFini() {
2715 AsyncFuncImpl::SetThreadInitFunc(
2716 [] (void*) {
2717 #if defined(_GNU_SOURCE) && defined(__linux__)
2718 if (RuntimeOption::EvalPerfDataMap) {
2719 pthread_t threadId = pthread_self();
2720 pthread_attr_t attr;
2721 pthread_getattr_np(threadId, &attr);
2722 void* stackAddr{nullptr};
2723 size_t stackSize{0};
2724 pthread_attr_getstack(&attr, &stackAddr, &stackSize);
2725 pthread_attr_destroy(&attr);
2726 recordThreadAddr(threadId, static_cast<char*>(stackAddr), stackSize);
2728 #endif
2729 hphp_thread_init();
2731 nullptr);
2732 AsyncFuncImpl::SetThreadFiniFunc([](void*) { hphp_thread_exit(); },
2733 nullptr);
2735 } s_SetThreadInitFini;
2737 ///////////////////////////////////////////////////////////////////////////////