This directory contains a collection of Python-GDB utilities for HHVM debugging, catered in particular towards debugging with a core, when no live inferior is present.
If you have installed the hhvm-dev
package, there should be an hhvm-gdb
command available, which will automatically load these library files for you.
Otherwise, you can simply execute the following line when running GDB, or add it to your ~/.gdbinit (substituting the path to your hphp directory as appropriate):
source <path-to-hphp>/tools/gdb/hhvm.py
A brief overview of the supported commands and utility functions appears below. Usage for commands can be obtained by running
(gdb) help <command>
A collection of pretty-printers for HHVM data structures is also included;
details can be discovered by reading pretty.py
.
The $ptr
convenience function strips away smart pointer wrapper classes and
returns the raw underlying pointer.
(gdb) whatis $1->m_cls
type = HPHP::LowClassPtr
(gdb) p $ptr($1->m_cls)
$2 = (HPHP::Class *) 0x262ec0f8
The $deref
convenience function can be used to fully dereference a value,
stripping away all layers of *
, &
, and all supported smart pointer
wrappers. Qualifiers (const/volatile) are also stripped.
(gdb) whatis function.m_data.pobj->m_cls
type = HPHP::LowClassPtr
(gdb) p/r $deref(function.m_data.pobj->m_cls)
$1 = {
<HPHP::AtomicCountable> = {
m_count = {
<std::__atomic_base<int>> = {
_M_i = 1
}, <No data fields>}
},
members of HPHP::Class:
---Type <return> to continue, or q <return> to quit---
The unit
command sets or prints the current context Unit. This is primarily
used for printing out literal strings without having to supply an explicit Unit
argument.
(gdb) p $1
$2 = (const HPHP::Func *) 0x2b83e520
(gdb) p $1->m_unit
$3 = (HPHP::Unit *) 0x2b7ee680
(gdb) unit $3
$4 = (const HPHP::Unit *) 0x2b7ee680
(gdb) unit
$5 = (const HPHP::Unit *) 0x2b7ee680
The repo
subcommands repo set-central
and repo set-local
set the central
or local repo path respectively. Setting one or both of these repos enables
other commands to produce more detailed output where appropriate---e.g.,
stacktrace functions will be able to find PHP function line numbers.
The repo show
subcommand displays the repo paths that have been set.
The idx
command, and corresponding $idx
convenience function, can be used
to index into arbitrary container objects, such as std::unordered_map
. See
idx.py for a list of supported containers.
(gdb) idx jit::mcg->m_fixupMap.m_fixups 0x1b0e0000
$14 = {
firstElem = 838,
fixup = {
pcOffset = 838,
spOffset = 15
},
indirect = {
magic = 838,
returnIpDisp = 15
}
}
(gdb) whatis $14
type = HPHP::jit::FixupMap::FixupEntry
The sizeof
command prints the sizes of various container types.
(gdb) whatis $2
type = HPHP::Class *
(gdb) whatis $2->m_interfaces
type = HPHP::Class::InterfaceMap
(gdb) sizeof $2->m_interfaces
$3 = 15
The nameof
command prints the string names of named VM metadata objects, like
Funcs or Classes.
(gdb) p $1
$2 = (const HPHP::Func *) 0x2b83e520
(gdb) nameof $1
"ServiceMemoizer::__call_async"
The lookup
supercommand allows for looking up various VM objects by ID.
Currently supported flavors include lookup func
and lookup litstr
, the
latter of which requires a second Unit argument, or an already-set current Unit
(via the unit
command).
The lookup func
command also comes as a convenience function called
$lookup_func
.
(gdb) lookup func 44766
$1 = (const HPHP::Func *) 0x2b83e520
(gdb) p $1->m_funcId
$2 = 44766
The hhx
commands dumps bytecode beginning at the specified address.
If two arguments are provided, the first is interpreted as the start PC, and the second, as the number of opcodes to print. Subsequent calls to `hhx' may omit these argument to print the same number of opcodes starting wherever the previous call left off.
If only a single argument is provided, if it is in the range for bytecode allocations (i.e., > 0xffffffff), it replaces the saved PC and defaults the count to 1 before printing. Otherwise, it replaces the count and the PC remains where it left off after the previous call.
Litstr IDs are converted to strings based on the context Unit set using unit
.
If no Unit is set, the raw IDs are printed instead.
(gdb) lookup func 12033
$1 = (const HPHP::Func *) 0x28d04280
(gdb) unit $1->m_unit
$2 = (const HPHP::Unit *) 0x28ce9b40
(gdb) hhx $3 25
0x7f2b2f9460ae+0: This
0x7f2b2f9460ae+1: AssertRATStk 0 SubObj
0x7f2b2f9460ae+15: AssertRATL 0 Cell
0x7f2b2f9460ae+18: CGetL 0
0x7f2b2f9460ae+22: FCallObjMethodD <> 1 1 - "MyClient" "send"
0x7f2b2f9460ae+33: AssertRATL 1 Uninit
0x7f2b2f9460ae+36: SetL 1
0x7f2b2f9460ae+38: PopC
0x7f2b2f9460ae+39: CGetM <vector>
0x7f2b2f9460ae+61: AssertRATL 1 InitCell
0x7f2b2f9460ae+64: CGetL 1
0x7f2b2f9460ae+68: FCallObjMethodD <> 1 1 - "" "genWait"
0x7f2b2f9460ae+71: Dup
0x7f2b2f9460ae+72: IsTypeC 0
0x7f2b2f9460ae+74: JmpNZ 28
0x7f2b2f9460ae+79: Dup
0x7f2b2f9460ae+80: InstanceOfD "HH\WaitHandle"
0x7f2b2f9460ae+85: JmpNZ 15
Initialize the custom HHVM unwinder. This is necessary for getting a coherent
callchain via backtrace
in the presence of jitted frames.
It is also called implicitly by walkstk
, which also requires the unwinder.
The walkstk
command prints out the native stack interleaved with the PHP
stack, incorporating code and stack pointers, function names, and filepaths.
(gdb) walkstk
#0 0x7fff52a59250 @ 0x7f41eece6499: __GI_raise() at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1 {inline frame} @ 0x7f41eecdf113: __GI_abort at abort.c:90
#2 0x7fff52a592e0 @ 0x7f41eecdf113: __assert_fail_base() at assert.c:92
#3 0x7fff52a59310 @ 0x7f41eecdf1c3: __GI___assert_fail() at assert.c:101
#4 0x7fff52a59550 @ 0x3290f7d: offsetOf() at hphp/.../vm/unit-inl.h:167
#5 {inline frame} @ 0x2e84b55: HPHP::createBacktrace at hphp/.../base/backtrace.cpp:110
#6 0x7fff52a596b0 @ 0x2e84b55: HPHP::debug_string_backtrace() at hphp/.../std/ext_std_errorfunc.cpp:103
#7 0x7fff52a59cf0 @ 0x3495199: HPHP::bt_handler() at hphp/.../base/crash-reporter.cpp:81
#8 0x7fff52a5a3d0 @ 0x7f41eece6520: __restore_rt()
#9 {inline frame} @ 0x7f41eecdf113: __GI_raise at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#10 {inline frame} @ 0x7f41eecdf113: __GI_abort at abort.c:90
#11 0x7fff52a5a460 @ 0x7f41eecdf113: __assert_fail_base() at assert.c:92
#12 0x7fff52a5a490 @ 0x7f41eecdf1c3: __GI___assert_fail() at assert.c:101
#13 0x7fff52a5a740 @ 0x38fe9cd: HPHP::jit::irgen::(anonymous namespace)::emitBaseLCR() at hphp/.../jit/irgen-minstr.cpp:625
#14 0x7fff52a5adf0 @ 0x3916127: emitBaseOp() at hphp/.../jit/irgen-minstr.cpp:754
#15 {inline frame} @ 0x3bbae8f: emitMPre at hphp/.../jit/irgen-minstr.cpp:1242
#16 {inline frame} @ 0x3bbae8f: HPHP::jit::irgen::(anonymous namespace)::implMInstr at hphp/.../jit/irgen-minstr.cpp:2067
#17 {inline frame} @ 0x3bbae8f: HPHP::jit::irgen::emitCGetM at hphp/.../jit/irgen-minstr.cpp:2077
#18 0x7fff52a5af80 @ 0x3bbae8f: translateDispatch() at hphp/.../jit/translator.cpp:1886
#19 {inline frame} @ 0x3bc11b5: HPHP::jit::translateInstr at hphp/.../jit/translator.cpp:1963
#20 0x7fff52a5b6a0 @ 0x3bc11b5: HPHP::jit::(anonymous namespace)::translateRegionImpl() at hphp/.../jit/translator.cpp:2361
#21 0x7fff52a5bdc0 @ 0x3bc0c60: HPHP::jit::(anonymous namespace)::translateRegionImpl() at hphp/.../jit/translator.cpp:2287
#22 0x7fff52a5c190 @ 0x3bc31c7: HPHP::jit::translateRegion() at hphp/.../jit/translator.cpp:2450
#23 0x7fff52a5c920 @ 0x3a0048a: HPHP::jit::MCGenerator::translateWork() at hphp/.../jit/mc-generator.cpp:1688
#24 0x7fff52a5c9b0 @ 0x3a0336b: HPHP::jit::MCGenerator::translate() at hphp/.../jit/mc-generator.cpp:476
#25 0x7fff52a5cac0 @ 0x3a0a92b: HPHP::jit::MCGenerator::retranslateOpt() at hphp/.../jit/mc-generator.cpp:249
#26 0x7fff52a5cb80 @ 0x3a0c4ce: HPHP::jit::MCGenerator::handleServiceRequest() at hphp/.../jit/mc-generator.cpp:1234
#27 0x7fff52a5cc60 @ 0x3a0d49e: HPHP::jit::MCGenerator::enterTC() at hphp/.../jit/mc-generator.cpp:1093
#28 0x7fff52a5cca0 @ 0x3ebe7ac: enterTCAfterPrologue() at hphp/.../jit/mc-generator.h:257
#29 {inline frame} @ 0x3ebebf6: HPHP::ExecutionContext::enterVMAtAsyncFunc at hphp/.../vm/bytecode.cpp:1855
#30 0x7fff52a5cd00 @ 0x3ebebf6: HPHP::ExecutionContext::enterVM() at hphp/.../vm/bytecode.cpp:1951
#31 0x7fff52a5cd50 @ 0x3ec3ea9: HPHP::ExecutionContext::resumeAsyncFunc() at hphp/.../vm/bytecode.cpp:2241
#32 {inline frame} @ 0x2c01315: HPHP::c_AsyncFunctionWaitHandle::resume at hphp/.../asio/async_function_wait_handle.cpp:122
#33 0x7fff52a5cdc0 @ 0x2c01315: resume() at hphp/.../asio/resumable_wait_handle-defs.h:30
#34 {inline frame} @ 0x2c290eb: HPHP::AsioContext::runUntil at hphp/.../asio/asio_context.cpp:130
#35 0x7fff52a5ce10 @ 0x2c290eb: HPHP::c_WaitableWaitHandle::join() at hphp/.../asio/waitable_wait_handle.cpp:94
#36 0x7fff52a5ce50 @ 0x2c281cb: HPHP::c_WaitHandle::t_join() at hphp/.../asio/wait_handle.cpp:68
#37 0x7fff52a5ce80 @ 0x2547d25: HPHP::tg_10WaitHandle_join() at hphp/.../idl/asio.ext_hhvm.cpp:287
#38 0x7f40f7a7fc40 @ 0xc013ff2: [PHP] HH\WaitHandle::join()
#38 0x7f40f7a7fcc0 @ 0xb04be8b: [PHP] prep() at path/to/prep.php:17
#38 0x7f40f7a7fda0 @ 0xc13c03e: [PHP] Logger::log() at path/to/logger.php:42
#38 0x7f40f7a7fde0 @ 0xc043b45: [PHP] LoggerConfig::log() at path/to/logger-config.php:69
#38 0x7f40f7a7fe50 @ 0x125971b9: [PHP] Closure$WebBaseController::__construct() at path/to/this/thing.php:27
#38 0x7f40f458c530 @ 0xc1136b4: [PHP] Closure$PSP::__construct() at path/to/this/other/thing.php:104
#38 0x7fff52a5d2a0 @ 0xb000010: <unknown>
#39 {inline frame} @ 0x35a88f9: enterTCHelper at hphp/.../jit/translator-asm-helpers.S:66
#40 0x7fff52a5d2e0 @ 0x35a88f9: HPHP::jit::x64::BackEnd::enterTCHelper() at hphp/.../jit/back-end-x64.cpp:118
#41 0x7fff52a5d3c0 @ 0x3a0d0d4: HPHP::jit::MCGenerator::enterTC() at hphp/.../jit/mc-generator.cpp:1064
#42 0x7fff52a5d400 @ 0x3ebe7ac: enterTCAfterPrologue() at hphp/.../jit/mc-generator.h:257
#43 {inline frame} @ 0x3ebebf6: HPHP::ExecutionContext::enterVMAtAsyncFunc at hphp/.../vm/bytecode.cpp:1855
#44 0x7fff52a5d460 @ 0x3ebebf6: HPHP::ExecutionContext::enterVM() at hphp/.../vm/bytecode.cpp:1951
#45 0x7fff52a5d4b0 @ 0x3ec3ea9: HPHP::ExecutionContext::resumeAsyncFunc() at hphp/.../vm/bytecode.cpp:2241
#46 {inline frame} @ 0x2c01315: HPHP::c_AsyncFunctionWaitHandle::resume at hphp/.../asio/async_function_wait_handle.cpp:122
#47 0x7fff52a5d520 @ 0x2c01315: resume() at hphp/.../asio/resumable_wait_handle-defs.h:30
#48 {inline frame} @ 0x2c290eb: HPHP::AsioContext::runUntil at hphp/.../asio/asio_context.cpp:130
#49 0x7fff52a5d570 @ 0x2c290eb: HPHP::c_WaitableWaitHandle::join() at hphp/.../asio/waitable_wait_handle.cpp:94
#50 0x7fff52a5d5b0 @ 0x2c281cb: HPHP::c_WaitHandle::t_join() at hphp/.../asio/wait_handle.cpp:68
#51 0x7fff52a5d5e0 @ 0x2547d25: HPHP::tg_10WaitHandle_join() at hphp/.../idl/asio.ext_hhvm.cpp:287
#52 0x7f40f7a7fe80 @ 0xc013ff2: [PHP] HH\WaitHandle::join()
#52 0x7f40f7a7ff00 @ 0xb01fd6f: [PHP] prep() at path/to/prep.php:17
#52 0x7f40f7a7ff70 @ 0x12532489: [PHP] PSP::run() at path/to/this/other/thing.php:181
#52 0x7f40f7a7ffc0 @ 0x1252b3ac: [PHP] Closure$PSP::doStuff() at path/to/this/other/thing.php:287
#52 0x7fff52a5da00 @ 0xb000010: <unknown>
#53 {inline frame} @ 0x35a88f9: enterTCHelper at hphp/.../jit/translator-asm-helpers.S:66
#54 0x7fff52a5da40 @ 0x35a88f9: HPHP::jit::x64::BackEnd::enterTCHelper() at hphp/.../jit/back-end-x64.cpp:118
#55 0x7fff52a5db20 @ 0x3a0d0d4: HPHP::jit::MCGenerator::enterTC() at hphp/.../jit/mc-generator.cpp:1064
#56 {inline frame} @ 0x3ebec46: enterTCAfterPrologue at hphp/.../jit/mc-generator.h:257
#57 {inline frame} @ 0x3ebec46: HPHP::ExecutionContext::enterVMAtFunc at hphp/.../vm/bytecode.cpp:1891
#58 0x7fff52a5db80 @ 0x3ebec46: HPHP::ExecutionContext::enterVM() at hphp/.../vm/bytecode.cpp:1949
#59 0x7fff52a5dcd0 @ 0x3ebf52b: HPHP::ExecutionContext::invokeFunc() at hphp/.../vm/bytecode.cpp:2116
#60 0x7fff52a5dd60 @ 0x33fae19: HPHP::vm_call_user_func() at hphp/.../base/builtin-functions.cpp:356
#61 0x7fff52a5de50 @ 0x33dab7b: HPHP::ExecutionContext::executeFunctions() at hphp/.../base/execution-context.cpp:574
#62 0x7fff52a5deb0 @ 0x33e246c: HPHP::ExecutionContext::onShutdownPostSend() at hphp/.../base/execution-context.cpp:598
#63 0x7fff52a5e0a0 @ 0x32bacaf: HPHP::HttpRequestHandler::executePHPRequest() at hphp/.../server/http-request-handler.cpp:483
#64 0x7fff52a5e430 @ 0x32bc8f6: HPHP::HttpRequestHandler::handleRequest() at hphp/.../server/http-request-handler.cpp:329
#65 0x7fff52a606f0 @ 0x346ae25: run() at hphp/.../server/server.h:112
#66 {inline frame} @ 0x346ccaf: start_server at hphp/.../base/program-functions.cpp:885
#67 {inline frame} @ 0x346ccaf: HPHP::execute_program_impl at hphp/.../base/program-functions.cpp:1569
#68 0x7fff52a60750 @ 0x346ccaf: HPHP::execute_program() at hphp/.../base/program-functions.cpp:956
#69 0x7fff52a60830 @ 0x1eefac0: main() at hphp/.../hhvm/main.cpp:611
The walkfp
command, like walkstk
, prints out an interleaved stacktrace.
The output is less detailed, but the command has fewer dependencies and is more
robust to issues like debuginfo or core corruption, gdb internal errors,
unwinder problems, etc.
(gdb) walkfp
#0 0x7ffe34921c20 @ 0x006e67b5: HPHP::AsioContext::runUntil(HPHP::c_WaitableWaitHandle*)
#1 0x7ffe34921c50 @ 0x006e651f: HPHP::c_WaitableWaitHandle::join()
#2 0x7ffe34921ca0 @ 0x00a2fec6: HPHP::f_join(HPHP::Object const&)
#3 0x7ffe34921cc0 @ 0x00e9b8eb: HPHP::Native::callFuncIndirectImpl<HPHP::Variant>(HPHP::TypedValue* (*)(HPHP::ActRec*), long*, int, double*, int)
#4 0x7ffe34921e60 @ 0x00c1ab15: HPHP::Native::callFunc<false>(HPHP::Func const*, void*, HPHP::TypedValue*, int, HPHP::TypedValue&)
#5 0x7ffe34921ea0 @ 0x00c1adb4: HPHP::Native::functionWrapper<false>(HPHP::ActRec*)
#6 0x7ffe34922990 @ 0x01929451: HPHP::dispatchImpl<false>()
#7 0x7ffe349229f0 @ 0x006e7005: HPHP::ExecutionContext::resumeAsyncFunc(HPHP::Resumable*, HPHP::ObjectData*, HPHP::TypedValue)
#8 0x7ffe34922a00 @ 0x006e6d2e: HPHP::c_AsyncFunctionWaitHandle::resume()
#9 0x7ffe34922a80 @ 0x006e670d: HPHP::AsioContext::runUntil(HPHP::c_WaitableWaitHandle*)
#10 0x7ffe34922ab0 @ 0x006e651f: HPHP::c_WaitableWaitHandle::join()
#11 0x7ffe34922b00 @ 0x00a2fec6: HPHP::f_join(HPHP::Object const&)
#12 0x7ffe34922b20 @ 0x00e9b8eb: HPHP::Native::callFuncIndirectImpl<HPHP::Variant>(HPHP::TypedValue* (*)(HPHP::ActRec*), long*, int, double*, int)
#13 0x7ffe34922cc0 @ 0x00c1ab15: HPHP::Native::callFunc<false>(HPHP::Func const*, void*, HPHP::TypedValue*, int, HPHP::TypedValue&)
#14 0x7ffe34922d00 @ 0x00c1adb4: HPHP::Native::functionWrapper<false>(HPHP::ActRec*)
#15 0x7ffe349237f0 @ 0x01929451: HPHP::dispatchImpl<false>()
#16 0x7ffe34923880 @ 0x00642f83: HPHP::ExecutionContext::invokeFunc(HPHP::Func const*, HPHP::Variant const&, HPHP::ObjectData*, HPHP::Class*, HPHP::VarEnv*, HPHP::StringData*, HPHP::ExecutionContext::InvokeFlags, bool)
#17 0x7ffe349238c0 @ 0x008f71a1: HPHP::ExecutionContext::invokeUnit(HPHP::Unit const*)
#18 0x7ffe34923920 @ 0x007a4879: HPHP::invoke_file(HPHP::String const&, bool, char const*)
#19 0x7ffe349239a0 @ 0x00bff301: HPHP::include_impl_invoke(HPHP::String const&, bool, char const*)
#20 0x7ffe34923b50 @ 0x00bfe934: HPHP::hphp_invoke(HPHP::ExecutionContext*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool, HPHP::Array const&, HPHP::VRefParamValue const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, bool, bool, bool)
#21 0x7ffe34923c40 @ 0x03b90583: HPHP::hphp_invoke_simple(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool)
#22 0x7ffe34924c20 @ 0x03ba5562: HPHP::execute_program_impl(int, char**)
#23 0x7ffe34924ce0 @ 0x03ba85b8: HPHP::execute_program(int, char**)
#24 0x7ffe34924dd0 @ 0x01954ceb: main(int, char**)
#25 0x7ffe34924ea0 @ 0x7ffbab134858: __libc_start_main
#26 0x0 @ 0x01952ab9: _start
The asyncstk
command prints out the async function PHP stack for a given
WaitHandle, ending at the synchronous join().
#0 0x7fa15c724e60 @ {suspended}: [PHP] gen_usleep() at /path/to/some/file.php:22
#1 0x7fa15c71d940 @ {suspended}: [PHP] DBClient::genRetryOnFailure() at /path/to/fancier/file.php:280
#2 0x7fa15c71ce70 @ {suspended}: [PHP] DBClient::gen() at /path/to/fancier/file.php:801
#3 0x7fa159e9fc30 @ {suspended}: [PHP] Logger::writeToDB() at /path/to/logging/and/other/stuff/file.php:28
#4 0x7fa15c70bc30 @ {suspended}: [PHP] PSP::__invoke() at /path/to/path/to/path/to/file.php:39
#5 0x7fa133c3ff40 @ 0x????????: [PHP] HH\WaitHandle::join()
The info asio
command provides a metadata dump about the current state of the
ASIO scheduler in the current thread.
Some of the stacktraces provided are truncated; full stacktraces can be
obtained by pointing the asyncstk
command at the appropriate WaitHandle.
(gdb) i asio
1 stacked AsioContext (current: (HPHP::AsioContext *) 0x7fa131c13210)
Currently executing WaitHandle: (HPHP::c_WaitableWaitHandle *) 0x7fa131c0c0c0 [state: BLOCKED]
#0 0x7fa131c0c080 @ {suspended}: [PHP] Asio::__invoke() at /path/to/fancy/abstraction.php
#1 0x7fa133c3fee0 @ 0x????????: [PHP] HH\WaitHandle::join()
0 other resumables queued
1 pending sleep event
0 pending external thread events
(HPHP::c_SleepWaitHandle *) 0x7fa131cc2360 [state: WAITING]
#0 0x7fa15c724e60 @ {suspended}: [PHP] gen_usleep() at /path/to/some/file.php:22
#1 0x7fa15c71d940 @ {suspended}: [PHP] DBClient::genRetryOnFailure() at /path/to/fancier/file.php:280
#2 0x7fa15c71ce70 @ {suspended}: [PHP] DBClient::gen() at /path/to/fancier/file.php:801
...
#8 0x7fa133c3fee0 @ 0x????????: [PHP] HH\WaitHandle::join()