hphpd: Interactive Debugging - Hack and HHVM (2015)

Hack and HHVM (2015)

Chapter 10. hphpd: Interactive Debugging

HHVM comes with an interactive debugger called hphpd. In case you’re not familiar with the concept, an interactive debugger is a program that lets you control other programs, and inspect their state. You can set them to pause the controlled program at certain points (e.g. when execution enters a specific function, or reaches a specific line of code). You can look at the values of variables during execution, and in some cases, modify them. Interactive debuggers are powerful tools, and they can drastically increase the ease and efficiency of debugging a large, complex program, as compared to the trial-and-error workflow of printf-debugging.

hphpd is also a read-eval-print loop (REPL) for PHP and Hack. You can interactively type in PHP and Hack code, in the context of your codebase (so you can use your library functions and so on), to try out small pieces of code.

If you’ve used other interactive debuggers like GDB or LLDB, you’ll find hphpd quite familiar. In fact, you may not even need to read this chapter; you can probably get started just using hphpd’s interactive help command, help.

In this chapter, we’ll see how to use hphpd to debug scripts and web apps, how to configure it, and how to get the most out of it.

Getting Started

Start hphpd by typing hhvm -m debug at a shell command line. Instead of executing any code, HHVM will display a welcome message and drop into the debugger prompt:

$ hhvm -m debug

Welcome to HipHop Debugger!

Type "help" or "?" for a complete list of commands.

Note: no server specified, debugging local scripts only.

If you want to connect to a server, launch with "-h" or use:

[m]achine [c]onnect <servername>

hphpd>

Whatever you type will appear after the hphpd> marker on the last line of output, just like on the shell command line. This is where you type hphpd commands. hphpd’s command line uses the GNU Readline library, so it remembers your command history, and supports features like Emacs key bindings and navigation of history using the up-arrow and down-arrow keys.

Let’s start with the most useful command in hphpd. Simply typing help or ? at the prompt will show a list of commands:

hphpd> help

────────────── Session Commands ──────────────

[m]achine connects to an HHVM server

[t]hread switches between different threads

[s]et various configuration options for hphpd

[q]uit quits debugger

──────────── Program Flow Control ────────────

[b]reak sets/clears/displays breakpoints

[e]xception catches/clears exceptions

[r]un starts over a program

<Ctrl-C> breaks program execution

[c]ontinue * continues program execution

[s]tep * steps into a function call or an expression

[n]ext * steps over a function call or a line

[o]ut * steps out a function call

────────────── Display Commands ──────────────

[p]rint prints a variable's value

[w]here displays stacktrace

[u]p goes up by frame(s)

[d]own goes down by frame(s)

[f]rame goes to a frame

[v]ariable lists all local variables

[g]lobal lists all global variables

[k]onstant lists all constants

───────────── Evaluation Commands ────────────

@ evaluates one line of PHP code

= prints right-hand-side's value, assigns to $_

${name}= assigns a value to left-hand-side

[<?]php starts input of a block of PHP code

?> ends and evaluates a block a PHP code

[a]bort aborts input of a block of PHP code

[z]end evaluates the last snippet in PHP5

──────── Documentation and Source Code ───────

[i]nfo displays documentations and other information

[l]ist * displays source codes

[h]elp ** displays this help

? displays this help

───────── Shell and Extended Commands ────────

! {cmd} executes a shell command

& {cmd} records and replays macros

x {cmd} extended commands

y {cmd} user extended commands

* These commands are replayable by just hitting return.

** Type "help help" to get more help.

The letter enclosed in square brackets at the beginning of some commands means that you can invoke that command just by typing that one letter.

You can type help followed by the name of any other command to get more specific help with that command:

hphpd> help variable

─────────────────── Variable Command ───────────────────

[v]ariable lists all local variables on stack

[v]ariable {text} full-text search local variables

This will print names and values of all variables that are currently

accessible by simple names. Use '[w]here', '[u]p {num}', '[d]own {num}',

'[f]rame {index}' commands to choose a different frame to view

variables at different level of the stack.

Specify some free text to print local variables that contain the text

either in their names or values. The search is case-insensitive and

string-based.

When getting command help, you can use the commands’ short names too; help v does the same thing as help variable.

Some commands have subcommands, which select between different behaviors the command has. For example, the break command can be used to list breakpoints (break list) or delete them (break clear), among other things. We’ll cover each command’s subcommands as we go.

Many commands also have arguments, which you pass by typing them at the debugger prompt after the command itself, separated by whitespace, much like passing arguments to a shell command.

You can exit hphpd by either using the quit command, or by typing Ctrl-C at the debugger prompt. (Typing Ctrl-C while code is executing will pause execution and put you back at the debugger prompt.)

Evaluating Code

You can use the @ command to evaluate Hack code. Everything that you type between the @ and the newline that ends the command will be executed. It can be a single statement or multiple statements. You don’t need to add a semicolon at the end.

hphpd> @echo "hello\n"

hello

hphpd> @function speak() { echo "speaking\n"; }

hphpd> @speak()

speaking

hphpd> @echo "hello "; echo "world\n"

hello world

NOTE

hphpd doesn’t leave a blank line before each prompt, but they’re added here for legibility.

You can use the = command to print the value of an expression.

hphpd> = 1 + 2

3

hphpd> = 'hello ' . 'world'

"hello world"

After each = command, the value it evaluated is stored in the variable $_.

hphpd> = 'beep'

"beep"

hphpd> @echo $_

beep

You can assign a value to a variable just by typing the assignment statement directly as a command. You could do so with @ as well, but it’s such a common operation that it’s special-cased in the debugger command syntax.

hphpd> $hello = 'hello'

hphpd> = $hello

"hello"

Finally, there’s a command that lets you inspect all local variables at once, much more quickly than by using = repeatedly: the variable command. It will print out the names and values of all local variables:

hphpd> $nums = array(10, 20, 30)

hphpd> $num = $nums[0]

hphpd> $count = count($nums)

hphpd> variable

$count = 3

$num = 10

$nums = Array

(

[0] => 10

[1] => 20

[2] => 30

)

You can pass an argument to the variable command, to filter the local variables that will be printed. Any variable whose name contains the command’s argument as a substring will be printed. Continuing from the example above:

hphpd> variable num

$num = 10

$nums = Array

(

[0] => 10

[1] => 20

[2] => 30

)

This command will be much more useful once we start executing and debugging real code.

The Execution Environment

The examples above were all working in an initially empty execution environment; none of your source files are loaded[37]. The interesting uses of hphpd happen when working with real codebases.

There are two modes that hphpd can be in: local and remote. Local mode means that the debugger is working in a PHP/Hack environment within its own process. Remote mode means that it’s working in a PHP/Hack environment inside a different process: a server-mode or daemon-mode HHVM process. That other process may be on a different machine, but it doesn’t have to be; connecting to localhost is the most common way to use remote mode.

You can tell which mode hphpd is in by the debugger prompt. If it just says hphpd, it’s in local mode. If it says something else, it’s in remote mode; that something else is the hostname of the machine it’s connected to.

You’ll use local mode when you’re just using hphpd as a REPL, or when debugging a script. Remote mode is for debugging web apps; you’ll connect to the HHVM process running your app.

Local Mode

When you start hphpd without arguments, as in simply hhvm -m debug, it will start in local mode, with no program loaded. This is only useful if you want to experiment with individual bits of PHP and Hack code; none of the code in your source files will be available.

You can start hphpd with a filename as an argument, as in hhvm -m debug test.php. This will load that file and prepare to run it. When you issue the run command in hphpd, it will start executing test.php from the top, just as if you had typed hhvm test.php at the command line.

Here’s test.php:

<?hh

echo "hello\n";

We’ll load it up in hphpd and run it:

$ hhvm -m debug test.php

Welcome to HipHop Debugger!

Type "help" or "?" for a complete list of commands.

Program test.php loaded. Type '[r]un' or '[c]ontinue' to go.

hphpd> run

hello

Program test.php exited normally.

hphpd>

If you type run again, the program will execute again, starting from the top.

After running the program once, any functions, classes, etc., that get defined in the course of running the program will be available; you can call them using @ and = without having the program actually running.

Here’s a new test.php:

<?hh

function func() {

echo "hello\n";

}

Note that there is no top-level code; just running this script won’t have any visible effects. We’ll just run it once to load the function, and then use it:

hphpd> run

hphpd> = func()

hello

hphpd>

Now, if you make changes to the file, executing the run command again will reload the file from the filesystem, and you should see your change reflected.

Remote Mode

To use hphpd’s remote mode, you need a server-mode or daemon-mode HHVM process to debug. For details on that, see Chapter 9. The process needs to have its debugger server enabled, so that hphpd can connect to it. It also needs to have sandbox mode turned on, which we’ll explain later. Here are the configuration options you’ll need to set to do all that, in INI format:

hhvm.sandbox.sandbox_mode = 1

hhvm.debugger.enable_debugger = 1

hhvm.debugger.enable_debugger_server = 1

By default, the process will listen for incoming debugger connections on port 8089; this is configurable with the option hhvm.debugger.port.

Once you have a suitable server process, start hphpd in remote mode by passing the command line argument -h followed by the hostname of the machine to connect to:

$ hhvm -m debug -h localhost

Welcome to HipHop Debugger!

Type "help" or "?" for a complete list of commands.

Connecting to localhost:8089...

Attaching to oyamauchi's default sandbox and pre-loading, please wait...

localhost>

The command prompt shows that we’re successfully connected to the machine. You can get out of remote mode and go back to local mode with the subcommand machine disconnect.

You can also enter remote mode from local mode, by using the machine command with the connect subcommand.

hphpd> machine connect localhost

Connecting to localhost:8089...

Attaching to oyamauchi's default sandbox and pre-loading, please wait...

localhost>

Now we need to take a look at the concept of sandboxes, which I mentioned earlier. In HHVM, a sandbox is a set of configuration including a document root and a log file path. It can support multiple sandboxes in a single server-mode process, essentially allowing a single process to serve multiple different web apps[38]. (You may have heard the term “sandbox” in the context of code isolation for security purposes; HHVM’s use of the term is unrelated.)

Configuring multiple sandboxes is complex and somewhat beyond our scope here. What’s relevant here is that sandbox mode must be turned on for hphpd to be able to debug a server-mode HHVM process, and when you connect to a server-mode HHVM process, you’ll have to choose a sandbox to attach to.

When you connect to a server-mode process, you’ll attach to a dummy sandbox. This is a sandbox created specifically for the debugger; it has no document root and so it has no code loaded. Its only purpose is to provide an PHP/Hack environment to evaluate code in from the debugger prompt. It’s analogous to hphpd’s local mode with no program loaded.

You can see all the sandboxes on the server with the list subcommand of machine:

localhost> machine list

1 oyamauchi's default sandbox at /oyamauchi/www/

2 __builtin's default sandbox at /home/oyamauchi/test-site/

The first entry in the list is the dummy sandbox (note that its path may be nonsense; it’s not actually used). The second one is the real one, representing the configuration with which the server is serving web requests. We need to attach to that one, with the attach subcommand, passing the sandbox number as the argument.

TIP

The real sandbox won’t show up if it hasn’t served any requests since the server started up. If you run machine list and see only the dummy sandbox, try making a web request to the server.

localhost> machine attach 2

Attaching to __builtin's default sandbox at /home/oyamauchi/test-site/ and

pre-loading, please wait...

localhost>

Now you’re in the right context, with that web app’s code loaded. You can set breakpoints (see Using Breakpoints) and view code (see Viewing Code and Documentation) and it will operate on that codebase.

Using Breakpoints

A breakpoint is a condition that, when met in the program being debugged, will cause the debugger to stop the program’s execution and drop into the debugger prompt. There are several conditions that can be used as breakpoints:

§ When execution reaches a certain line in a certain file.

§ When execution enters a certain function or method.

§ When a web request at a certain URL begins or ends.

Let’s start with a simple example. Suppose we have this file, called test.php:

<?hh

function func(string $first, string $second): void {

echo $first . "\n";

echo $second . "\n";

}

func('one', 'two');

We’ll start up hphpd, and set a breakpoint between the two echo statements. To set a breakpoint, use the hphpd command break, followed by the breakpoint’s condition[39]. In this case, we’ll set one on line 5, the line containing the second echo statement—when you set a breakpoint on a line, execution will stop just before any of that line is executed. We specify the location by typing the filename, followed (with no whitespace) by a colon, followed by the line number.

$ hhvm -m debug test.php

hphpd> break test.php:5

hphpd> run

one

Breakpoint 1 reached at func() on line 5 of /home/oyamauchi/test.php

4 echo $first . "\n";

5* echo $second . "\n";

6 }

hphpd>

The script starts executing, echoes one, and then pauses. The debugger prints out the source code surrounding the location where execution is stopped, and marks the relevant line with an asterisk. (If your terminal supports it, the output is colorized as well, highlighting the relevant line.) Note that two has not been echoed yet.

The debugger prompt is visible, meaning the debugger is waiting for a command. From here, you can inspect state with variable and evaluation commands, set more breakpoints, continue execution in small increments, or resume normal execution. We’ll see how to do all of the above in the rest of this section.

Setting Breakpoints

We’ve seen the syntax for setting a breakpoint at a certain line in a certain file. This is the syntax for setting a breakpoint on a given function:

hphpd> break my_function()

Breakpoint 1 set upon entering my_function()

Regardless of what parameters the function has, you always put an empty pair of parentheses after the function name.

You may see a message that execution won’t break until the function has been loaded. This is generally nothing to worry about; as code executes and files are loaded, the debugger will watch for a function by the given name to be loaded, and when it is, it will ensure the breakpoint gets set.

To set a breakpoint on a method, the argument to the break command is the class name, followed by two colons, followed by the method name, followed by an empty pair of parentheses.

hphpd> break MyClass::myMethod()

Breakpoint 1 set upon entering MyClass::myMethod()

The class-and-method-name pair is resolved lexically; it does not take inheritance or traits into account. In other words: if a method definition with that name is written inside the class definition with that name, the breakpoint will be set there. If the method is defined in a trait that the class uses, or if it’s inherited from an ancestor class, the breakpoint won’t be set.

The final form of breakpoint trigger is specific to remote mode, when debugging web requests. You can break at the beginning or end of a web request, as well as at the beginning of the processing of shutdown functions registered through register_shutdown_function(). The syntax for these is break start, break end, and break psp, respectively[40].

Each of those three can be modified with a further argument, which is the path part of a URI[41]. In that case, the breakpoint will only trigger on web requests to that path.

hphpd> break start /something/something.php

Breakpoint 1 set start of request when request is /something/something.php

Note that the URL that will be checked is the original request URI—the value stored in $_SERVER['REQUEST_URI']. It is not the path of the PHP or Hack file that ends up getting invoked.

Breakpoint Expressions and Conditions

Any of the above forms of breakpoint can have a Hack expression attached to it, and hphpd will evaluate the expression every time the breakpoint is hit. The most common use of this is simply to print out some value at the breakpoint, to avoid having to enter a separate command to do so every time.

The syntax for this is to append && and the Hack expression to a normal breakpoint-setting command.

<?hh

function do_something_expensive(int $level) {

// ...

}

do_something_expensive(10);

We want to break on the call to do_something_expensive(), and see what $level is.

hphpd> break do_something_expensive() && var_dump($level)

Breakpoint 1 set upon entering func() && var_dump($level)

hphpd> run

Breakpoint 1 reached at do_something_expensive() on line 3 of

/home/oyamauchi/test.php

2

3*function do_something_expensive(int $level) {}

4* // ...

5*}

6

int(10)

You can also configure a breakpoint with an expression so that the breakpoint will only trigger and stop execution if the expression evalutes to true. This is a conditional breakpoint. To create one, use the same syntax as for a breakpoint with an expression, but replace the && with if.

Here, we’ll break on do_something_expensive(), but only if its argument is over 9000. Since the argument passed in this script is 10, the breakpoint won’t trigger.

hphpd> break do_something_expensive() if $level > 9000

Breakpoint 1 set upon entering func() if $level > 9000

hphpd> run

Program test.php exited normally.

As this example shows, if you set a breakpoint on entering a function, you can use that function’s arguments in the breakpoint condition.

Breaking From Code

There’s one more way to set breakpoints, which is to call the special function hphpd_break() in your PHP or Hack code. It can be useful, for example, in situations where the physical layout of the code makes it awkward to set a breakpoint by line number.

function f(): void {

echo "one\n";

hphpd_break();

echo "two\n";

}

If hphpd is attached when the call to hphpd_break() is executed, it will be just as if you had set a breakpoint on that line: execution will pause and you’ll be given the debugger prompt. You can step or resume from this breakpoint like any other.

You can also pass a boolean argument to hphpd_break(), and it will work as a breakpoint only if the argument is true. You can use this as a conditional breakpoint.

function f(int $num): void {

hphpd_break($num < 0);

}

f(1234); // Will not trigger the breakpoint

f(-123); // Will trigger the breakpoint

In code that is not running under hphpd, hphpd_break() does nothing.

Navigating the Call Stack

To orient yourself once stopped at a breakpoint, you can get hphpd to print a stack trace with the where command. (GDB users will be happy to learn that bt does the same thing.) This fulfills a common purpose of breakpoints, which is simply to find out where some piece of code is being called from.

We’ll use this file:

<?hh

function one(string $str) {

echo $str;

}

function two() {

one("done\n");

}

function three() {

two();

}

three();

We’ll set a breakpoint on the echo statement and get a stack trace:

hphpd> break test.php:4

Breakpoint 1 set on line 4 of test.php

hphpd> r

Breakpoint 1 reached at one() on line 4 of /home/oyamauchi/test.php

3 function one(string $str) {

4* echo $str;

5 }

hphpd> bt

#0 ()

at /home/oyamauchi/test.php:4

#1 one ("done\n")

at /home/oyamauchi/test.php:8

#2 two ()

at /home/oyamauchi/test.php:12

#3 three ()

at /home/oyamauchi/test.php:15

The stack trace shows the values of arguments to the functions. You can turn this off with a configuration option; see StackArgs in ???.

Note that in the stack traces, each frame has a number. The deepest frame (i.e. the one farthest from top-level code) is numbered zero, and the number increases as you get closer to top-level code. You can use these to change which frame the debugger is operating on. This affects the evaluation commands @ and = (they operate on the current frame) and the inspection command variable. It also affects list, which we haven’t seen yet but is explained in Viewing Code and Documentation.

Here’s an example, where we’ll set a breakpoint and want to move to a different frame to see what’s going on:

<?hh

function do_something_expensive() {

// ...

}

function do_something() {

$level = get_level();

if ($level > 10) {

do_something_expensive();

}

}

do_something();

// Define get_level

// ...

We’ll run this and see what the value of $level was that resulted in do_something_expensive() being called, by moving up to do_something()’s frame and using variable.

hphpd> break do_something_expensive()

Breakpoint 1 set upon entering do_something_expensive()

hphpd> run

Breakpoint 1 reached at do_something_expensive() on line 4 of

/home/oyamauchi/test.php

3 function do_something_expensive() {

4* // ...

5 }

hphpd> where

#0 do_something_expensive ()

at /home/oyamauchi/test.php:10

#1 do_something ()

at /home/oyamauchi/test.php:14

hphpd> frame 1

#1 do_something ()

at /home/oyamauchi/test.php:14

hphpd> variable

$level = 9000

Navigating Code

Once you’re stopped at a breakpoint, there are several commands you can use to move execution forward.

The simplest of these is continue, which will simply resume normal execution. The script or web request will keep running until it terminates or hits another breakpoint. (It may hit the same breakpoint you were stopped at, if execution comes through that code again.)

<?hh

function func() {

echo "Starting func\n";

echo "Ending func\n";

}

f();

We’ll set a breakpoint before the second line of f(), and continue after execution pauses there:

hphpd> break test.php:5

Breakpoint 1 set on line 5 of test.php

hphpd> run

Starting func

Breakpoint 1 reached at func() on line 5 of /home/oyamauchi/test.php

4 echo "Starting func\n";

5* echo "Leaving func\n";

6 }

hphpd> continue

Leaving func

Program test.php exited normally.

The more interesting commands are step and next. These will execute the line of code that was about to be executed before the breakpoint was hit, and stop again after it’s done. The difference between the two is apparent if the line being executed contains a function or method call. stepwill enter the function being called, and stop just before executing its first line; next will just go to the next line, without entering the function. In other words, the callstack will never be deeper after doing next.

This is a very powerful way of debugging code. Rather than adding logging code at various places, you can set breakpoints instead, and continue execution bit-by-bit, inspecting state at each step.

Let’s look at this example:

<?hh

function inner(): void {

echo "inner\n";

}

function outer(): void {

echo "outer\n";

inner();

echo "done\n";

}

outer();

We’ll set a breakpoint on outer(), and proceed with next:

hphpd> break outer()

Breakpoint 1 set upon entering outer()

But wont break until function outer has been loaded.

hphpd> run

Breakpoint 1 reached at outer() on line 8 of /home/oyamauchi/test.php

7 function outer(): void {

8* echo "outer\n";

9 inner();

hphpd> next

outer

Break at outer() on line 9 of /home/oyamauchi/test.php

8 echo "outer\n";

9* inner();

10 echo "done\n";

hphpd> next

inner

Break at outer() on line 10 of /home/oyamauchi/test.php

9 inner();

10* echo "done\n";

11 }

hphpd> next

done

Break at outer() on line 11 of /home/oyamauchi/test.php

10 echo "done\n";

11*}

12

hphpd> next

Break on line 13 of /home/oyamauchi/test.php

12

13*outer();

14 (END)

hphpd> next

Program test.php exited normally.

Note that execution goes directly from line 9 to line 10: from the call to inner(), to the echo of done. The call to inner is being executed—you can see inner being echoed—but the debugger is not stopping inside it.

Now let’s do the same thing with step instead:

hphpd> run

Breakpoint 1 reached at outer() on line 8 of /home/oyamauchi/test.php

7 function outer(): void {

8* echo "outer\n";

9 inner();

hphpd> step

outer

Break at outer() on line 9 of /home/oyamauchi/test.php

8 echo "outer\n";

9* inner();

10 echo "done\n";

hphpd> step

Break at inner() on line 4 of /home/oyamauchi/test.php

3 function inner(): void {

4* echo "inner\n";

5 }

hphpd> step

inner

Break at inner() on line 5 of /home/oyamauchi/test.php

4 echo "inner\n";

5*}

6

hphpd> step

Break at outer() on line 9 of /home/oyamauchi/test.php

8 echo "outer\n";

9* inner();

10 echo "done\n";

hphpd> step

Break at outer() on line 10 of /home/oyamauchi/test.php

9 inner();

10* echo "done\n";

11 }

hphpd> step

done

Break at outer() on line 11 of /home/oyamauchi/test.php

10 echo "done\n";

11*}

12

hphpd> step

Break on line 13 of /home/oyamauchi/test.php

12

13*outer();

14 (END)

hphpd> step

Program test.php exited normally.

Now, after we step from line 9, we go to line 4: we’re inside inner(). Once we step to the end of inner(), we are back in outer(), on line 10, the line after the call to inner().

There is one other command in this category, which is out. It resumes execution until the function you’re stopped in has exited, either by returning, by throwing an exception (or by an exception being thrown through it from something deeper in the callstack), or, in the case of a generator, by yielding.

hphpd> break outer()

Breakpoint 1 set upon entering outer()

But wont break until function outer has been loaded.

hphpd> run

Breakpoint 1 reached at outer() on line 8 of /home/oyamauchi/test.php

7 function outer() {

8* echo "outer\n";

9 inner();

hphpd> out

outer

inner

done

Break on line 13 of /home/oyamauchi/test.php

12

13*outer();

14 (END)

In this case, we stop at the top of outer(), then do out. hphpd lets the rest of outer() execute, and stops again in the top-level code, resuming just after the call to outer() returns.

You can configure hphpd so that step and next will move forward one expression at a time rather than one line at a time; see Configuring hphpd, and the SmallSteps option in particular, for details.

TIP

To save typing, you can repeat the four flow control commands (continue, next, step, and out) just by hitting Enter at the next debugger prompt. In other words, if you hit Enter at the prompt without typing anything else, and the previous command was one of the four flow control commands, that previous command will be repeated.

WARNING

Note that the frame command does not change the stack frame that next, step, and out operate in. That is, next will move execution to the next line to be executed anywhere, not the next line to be executed in the stack frame you’re looking at; the other two commands are similar. This differs from GDB’s behavior, so take note if you’re a seasoned GDB user.

Managing Breakpoints

We’ve seen how to set breakpoints by passing a location to the break command. The same command has several subcommands which you can use to manipulate existing breakpoints.

First, though, here’s the subcommand to list all existing breakpoints: break list.

hphpd> break func()

Breakpoint 1 set upon entering func()

hphpd> break test.php:5

Breakpoint 1 set on line 5 of test.php

hphpd> break list

1 ALWAYS upon entering func()

2 ALWAYS on line 5 of test.php

The first field is the breakpoint number; this is just a unique identifier that you use to refer to that breakpoint in other commands. When hphpd stops at a breakpoint, it will print that breakpoint’s number. Breakpoint numbers are monotonically increasing and are not re-used, so if you set two breakpoints and then delete breakpoint 1, the remaining breakpoint will still be number 2. If you then set a new one, it will be number 3.

The second field is the breakpoint’s state. There are three possible states: ALWAYS, ONCE, and DISABLED. An ALWAYS breakpoint will trigger every time execution reaches it. A ONCE breakpoint will trigger the first time execution reaches it, and then it will become DISABLED. A DISABLEDbreakpoint does not trigger.

By default, when you create a breakpoint, its state is ALWAYS. You can create a ONCE breakpoint by using the subcommand once, followed by a location, in the same notation as for the plain break command.

hphpd> break once func()

Breakpoint 1 set upon entering func()

hphpd> break list

1 ONCE upon entering func()

There are three subcommands to change the state of a breakpoint: enable, disable, and toggle. enable sets a breakpoint’s state to ALWAYS, and disable sets it to DISABLED. toggle cycles a breakpoint between the three possible states.

hphpd> break func()

Breakpoint 1 set upon entering func()

hphpd> break toggle 1

Breakpoint 1's state is changed to ONCE.

hphpd> break toggle 1

Breakpoint 1's state is changed to DISABLED.

hphpd> break toggle 1

Breakpoint 1's state is changed to ALWAYS.

hphpd> break disable 1

Breakpoint 1's state is changed to DISABLED.

hphpd> break enable 1

Breakpoint 1's state is changed to ALWAYS.

To delete a breakpoint altogether, use the subcommand clear, along with the breakpoint number.

hphpd> break clear 1

Breakpoint 1 cleared upon entering func()

With the subcommands clear, disable, enable, and toggle, you can also use all in place of a breakpoint number, in which case the operation applies to all breakpoints.

hphpd> break clear all

All breakpoints are cleared.

You can also pass no argument after one of these subcommands, in which case the operation applies to the last breakpoint that was hit.

hphpd> break func()

Breakpoint 1 set upon entering func()

hphpd> = func()

Breakpoint 1 reached at func() on line 3 of /home/oyamauchi/test.php

hphpd> break clear

Breakpoint 1 is cleared at func()

Viewing Code and Documentation

If code is currently executing—that is, you’re stopped at a breakpoint or in the midst of stepping after stopping at a breakpoint—you can use the list command to show the surroundings of the code being executed. If you’re looking at a different stack frame with the frame command, listwill show the code surrounding the relevant callsite in that frame.

hphpd> break func()

Breakpoint 1 set upon entering func()

hphpd> r

Breakpoint 1 reached at func() on line 7 of /home/oyamauchi/test.php

6 function func(): void {

7* echo "Just kidding, it's pretty boring";

8 }

hphpd> list

list

1 <?hh

2

3 /**

4 * This is a very interesting function

5 */

6 function func(): void {

7* echo "Just kidding, it's pretty boring";

8 }

9

10 func();

(END)

After running a list command, if you hit Enter at the next debugger prompt without typing anything else, the debugger will show the next few lines of code. You can keep going that way until the end of the file.

There’s a wide variety of arguments you can pass to list, specifying what code you want to see. ??? shows all of them.

Line ranges

list 34-45

Lines 34 through 45 in the current file

list 34-

Line 34 through the end of the current file

list -45

Beginning of the current file up to line 45

list 34

Lines surrounding line 34 in the current file

Line ranges in a file

list test.php:34-45

Same as above, but in test.php. Paths are relative to sandbox root if attached, current working directory if not.

list test.php:34-

list test.php:-45

list test.php:34

Named entities

list func

Source code of the function func()

list ClassName

Source code of the class ClassName (also works for interfaces and traits)

list ClassName::methodName

Source code of the method methodName in the class ClassName

You can use the info command to look up documentation comments and signatures. Pass the name of a named entity[42] as an argument to the command. To look up a method, use the ClassName::methodName notation.

In our file test.php:

<?hh

/**

* A class

*/

class C {

/**

* A method inside the class

*/

public function method(C $obj): void {

}

}

/**

* A function

*/

function f(int $x): void {

}

We’ll look up this things in hphpd:

$ hhvm -m debug test.php

hphpd> info C

// defined on line 6 to 13 of /home/oyamauchi/test.php

/**

* A class

*/

class C {

// methods

[doc] public function method(C $obj);

}

hphpd> info C::method

// defined on line 11 to 12 of /home/oyamauchi/test.php

/**

* A method inside the class

*/

public function C::method(C $obj);

hphpd> info f

// defined on line 18 to 19 of /home/oyamauchi/test.php

/**

* A function

*/

function f(HH\int $x);

This also works for builtin functions, classes, and interfaces:

hphpd> info strtoupper

/**

* Returns string with all alphabetic characters converted to uppercase. Note

* that 'alphabetic' is determined by the current locale. For instance, in the

* default "C" locale characters such as umlaut-a will not be converted.

*

* @param string $str - The input string.

*

* @return string - Returns the uppercased string.

*

*/

function strtoupper(HH\string $str);

Macros

hphpd has a feature for recording and replaying sequences of commands. These sequences are called macros. They’re automatically saved to a file called .hphpd.ini in your home directory, so they persist across hphpd sessions. (That file also holds configuration for hphpd; see Configuring hphpd.)

The command for working with macros is &. To start recording a macro, use it with the subcommand start, then just enter the commands you want to record. When you’re finished, use & with the subcommand end:

hphpd> & start

hphpd> @echo "hi"

hi

hphpd> & end

Now, you can replay that sequence with the subcommand replay. In this example, we don’t actually type in the @echo statement; it’s executed automatically by hphpd, and we’re returned to the debugger prompt after it completes.

hphpd> & replay

hphpd> @echo "hi"

hi

hphpd>

You can give a macro a name when you start to record it, by passing an argument to the start subcommand. If you don’t give it a name, it will have the name default. This means that if you record one macro without a name, then record another without a name, the first one will be overwritten.

hphpd> & start some_name

hphpd> @echo "some named macro"

some named macro

hphpd> & end

You can pass a name to the replay subcommand, to replay that named macro. It replays the default macro by default.

hphpd> & replay some_name

hphpd> @echo "some named macro"

some named macro

You can see all existing macros with the subcommand list:

hphpd> & list

1 default

> @echo "hi"

2 some_name

> @echo "some named macro"

The output shows the number, name, and contents of each macro.

Finally, you can delete a macro with the subcommand clear, but using the macro’s number, not its name.

hphpd> & clear 1

Are you sure you want to delete the macro? [y/N] y

hphpd> & list

1 some_name

> @echo "some named macro\n"

Note that, unlike breakpoint numbers, macro numbers are not permanently associated with macros. We deleted macro 1, and the former macro 2 slid up to become the new number 1. This is a quirk of macros’ implementation; if you find it strange and inconsistent, you’re right.

The macro name startup is treated specially. When hphpd launches and loads settings, it will look for a macro called startup, and if it finds one, it will replay that macro immediately, before taking any input from the debugger prompt.

You may find that there’s some collection of utility functions or classes that you often use when debugging. If you put these all in a file, you can include that file from a startup macro, so they’re available every time you start hphpd.

Configuring hphpd

The last major command we haven’t covered yet is set. This allows you to change some of the configuration options that control hphpd’s behavior. Most of them control aspects of hphpd’s output. ??? shows all the available options.

If you run the command with no arguments—that is, just type set and nothing else—hphpd will show all the options and their current values. To set an option, pass two arguments to the set command: first, either the name or the short name of the option; and second, the value to set it to. For example, set bac on or set LogFile off.

Name

Short name

Possible values

Default value

BypassAccessCheck

bac

on, off

off

LogFile

lf

off or a file path

off

PrintLevel

pl

integers

5

ShortPrintCharCount

cc

integers

200

SmallSteps

ss

on, off

off

StackArgs

sa

on, off

on

MaxCodeLines

mcl

integers

-1

BypassAccessCheck

Turning this on will make hphpd ignore the access modifiers protected and private in code that is invoked from the debugger prompt. Code running in web requests won’t be affected.

LogFile

If the value of this option is anything other than off, it will be interpreted as a file path, and hphpd will transcribe all of its output to that file. It doesn’t capture your input.

PrintLevel

This controls the maximum amount of object and array nesting that the print command will print. If it’s zero or negative, there is no maximum; objects and arrays will be printed in full, unless there’s recursive nesting. If there is recursive nesting, printing will still be truncated when the recursion starts, regardless of this option’s value.

ShortPrintCharCount

This controls the maximum number of characters that will be printed in a single = command. If it’s zero or negative, there is no maximum. If a = command would result in more characters than the maximum, hphpd will ask if you want to see the rest; you answer, as is usual with interactive command lines, by typing either y or n.

SmallSteps

This controls the behavior of the step and next commands. If it’s turned off (as it is by default), those commands will take you to (approximately) the next line, whereas if the option is turned on, they will take you to (approximately) the next expression that gets evaluated.

StackArgs

If this option is turned on, the backtraces printed by the where command will show the arguments that were passed to the functions in the backtrace.

MaxCodeLines

When hphpd stops at a breakpoint, or after a step or next command, it will print out the source code where it’s stopped, with the relevant part highlighted. In some cases, this can be a lot of code that spans a lot of lines—like a large array expression with each element on its own line, for example. This option can be used to limit the number of lines that get printed in these situations, to avoid overwhelming your terminal.

If this option is set to zero, no code will be printed when stopping at a breakpoint. If it’s negative (as it is by default), there is no limit.

hphpd saves your settings in a file in your home directory called .hphpd.ini, and will reload them the next time you run hphpd. You can edit this file manually to change your saved settings.

include

converting.asciidoc[]


[37] You can use the @ command to evaluate include statements and the like, though.

[38] This feature was developed specifically to support Facebook’s web development (where multiple developers share a single development machine), and many aspects of it are still quite Facebook-specific and aren’t well-adapted for life in the outside world. This should get better over time.

[39] For clarity, we’ll be using the full commands in this book, but remember that you can shorten them to just the first letter. You can type b test.php:5 instead of break test.php:5, and it will do the same thing.

[40] “PSP” stands for post-send processing, and is what shutdown functions were originally called in HHVM.

[41] For example, in the URI https://www.example.com/something/something.php?key=val, the path part is /something/something.php

[42] A named entity is a function, class, interface, constant, trait, enum, or type alias.