phpdbg for fun and profit

Adam Harvey

@LGnome

Command line debugger

aharvey@aharvey-mbp:/tmp$ phpdbg code/fizzbuzz.php [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /tmp/code/fizzbuzz.php] prompt> break fizzbuzz [Breakpoint #0 added at fizzbuzz] prompt> run 15 [Breakpoint #0 at /tmp/code/fizzbuzz.php:3, hits: 1] >00003: if (0 == ($n % 3)) { 00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { prompt>

Debian and Ubuntu


apt-get install php5-phpdbg
apt-get install php7.0-phpdbg
apt-get install php7.1-phpdbg
						

Red Hat and CentOS


yum install php56-php-dbg
yum install php70-php-dbg
yum install php71-php-dbg
dnf install php-dbg
						

Mac OS X


brew tap homebrew/php
brew install php70 --with-phpdbg
						

From source


./configure --enable-phpdbg
						
aharvey@aharvey-mbp:/tmp$ phpdbg code/fizzbuzz.php [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /tmp/code/fizzbuzz.php] prompt>
prompt> help phpdbg is a lightweight, powerful and easy to use debugging platform for PHP5.4+ It supports the following commands: Information list list PHP source info displays information on the debug session print show opcodes frame select a stack frame and print a stack frame summary back shows the current backtrace help provide help on a topic Starting and Stopping Execution exec set execution context run attempt execution step continue execution until other line is reached continue continue execution until continue execution up to the given location next continue execution up to the given location and halt on the first line after it finish continue up to end of the current execution frame leave continue up to end of the current execution frame and halt after the calling instruction break set a breakpoint at the specified target watch set a watchpoint on $variable clear clear one or all breakpoints clean clean the execution environment Miscellaneous set set the phpdbg configuration source execute a phpdbginit script register register a phpdbginit function as a command alias sh shell a command ev evaluate some code quit exit phpdbg Type help <command> or (help alias) to get detailed help on any of the above commands, for example help list or h l Note that help will also match partial commands if unique (and list out options if not unique), so help clea will give help on the clean command, but help cl will list the summary for clean and clear. Type help aliases to show a full alias list, including any registered phpdginit functions Type help syntax for a general introduction to the command syntax. Type help options for a list of phpdbg command line options. Type help phpdbginit to show how to customise the debugger environment.
prompt> help run Command: run Alias: r attempt execution Enter the vm, starting execution. Execution will then continue until the next breakpoint or completion of the script. Add parameters you want to use as $argv Examples prompt> run prompt> r Will cause execution of the context, if it is set prompt> r test Will execute with $argv[1] == "test" Note that the execution context must be set. If not previously compiled, then the script will be compiled before execution. Note that attempting to run a script that is already executing will result in an "execution in progress" error. prompt>
prompt> help run Command: run Alias: r attempt execution Enter the vm, starting execution. Execution will then continue until the next breakpoint or completion of the script. Add parameters you want to use as $argv
Examples prompt> run prompt> r Will cause execution of the context, if it is set prompt> r test Will execute with $argv[1] == "test"
Note that the execution context must be set. If not previously compiled, then the script will be compiled before execution. Note that attempting to run a script that is already executing will result in an "execution in progress" error.
prompt> run 15
prompt> run 15 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz [Script ended normally] prompt>

function fizzbuzz(int $n): string {
  if (0 == ($n % 3)) {
    return 'Fizz';
  } elseif (0 == ($n % 5)) {
    return 'Buzz';
  }
  return (string) $n;
}

foreach (range(1, (int) $_SERVER['argv'][1]) as $n) {
  echo fizzbuzz($n).' ';
}
echo "\n";
						
prompt> help break Command: break Alias: b set breakpoint Breakpoints can be set at a range of targets within the execution environment. Execution will be paused if the program flow hits a breakpoint.
prompt> break 3 [Breakpoint #0 added at /tmp/code/fizzbuzz.php:3] prompt> break fizzbuzz.php:3 [Breakpoint #0 added at /tmp/code/fizzbuzz.php:3] prompt> break fizzbuzz [Breakpoint #1 added at fizzbuzz]
prompt> run 15 [Breakpoint #0 at /tmp/code/fizzbuzz.php:3, hits: 1] >00003: if (0 == ($n % 3)) { 00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { prompt>
prompt> help ev Command: ev Alias: The ev command takes a string expression which it evaluates and then displays. It evaluates in the context of the lowest (that is the executing) frame, unless this has first been explicitly changed by issuing a frame command.
prompt> ev $n 1 prompt>
prompt> continue 1 [Breakpoint #0 at /tmp/code/fizzbuzz.php:3, hits: 2] >00003: if (0 == ($n % 3)) { 00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { prompt> ev $n 2 prompt>
Examples prompt> break at phpdbg::isGreat if $opt == 'S' prompt> break @ phpdbg::isGreat if $opt == 'S' Break at any opcode in phpdbg::isGreat when the condition ($opt == 'S') is true
prompt> break del 0 [Deleted breakpoint #0] prompt> break at fizzbuzz if $n == 15 [Conditional breakpoint #1 added $n == 15/0x10a668300] prompt>
prompt> continue 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 [Conditional breakpoint #1: at fizzbuzz if $n == 15 at /tmp/code/fizzbuzz.php:3, hits: 1] >00003: if (0 == ($n % 3)) { 00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { prompt> ev $n 15 prompt>
prompt> help step Command: step Alias: s step through execution Execute opcodes until next line Examples prompt> s Will continue and break again in the next encountered line
prompt> step [L3 0x10a67a040 IS_EQUAL 0 ~0 ~1 /tmp/code/fizzbuzz.php] [Conditional breakpoint #1: at fizzbuzz if $n == 15 at /tmp/code/fizzbuzz.php:3, hits: 2] >00003: if (0 == ($n % 3)) { 00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { prompt>
prompt> step [L4 0x10a67a080 RETURN "Fizz" /tmp/code/fizzbuzz.php] >00004: return 'Fizz'; 00005: } elseif (0 == ($n % 5)) { 00006: return 'Buzz'; prompt>
prompt> step [L12 0x10a6801c0 CONCAT @6 " " ~7 /tmp/code/fizzbuzz.php] >00012: echo fizzbuzz($n).' '; 00013: } 00014: echo "\n"; prompt>

class TableFlipException extends Exception {}

function (╯°□°)╯︵ ┻━┻() {
  throw new TableFlipException('ruh roh');
}

(╯°□°)╯︵ ┻━┻();
						
aharvey@aharvey-mbp:/tmp$ phpdbg code/exception.php [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /tmp/code/exception.php] prompt> r [Uncaught TableFlipException in /tmp/code/exception.php on line 5: ruh roh] >00005: throw new TableFlipException('ruh roh'); 00006: } 00007: prompt>

class TableFlipException extends Exception {}

function (╯°□°)╯︵ ┻━┻() {
  throw new TableFlipException('ruh roh');
}

set_exception_handler(function ($e) { echo $e; });

(╯°□°)╯︵ ┻━┻();
						
aharvey@aharvey-mbp:/tmp$ phpdbg code/exception.php [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /tmp/code/exception.php] prompt> r [Uncaught TableFlipException in /tmp/code/exception.php on line 5: ruh roh] >00005: throw new TableFlipException('ruh roh'); 00006: } 00007: prompt>

function (╯°□°)╯︵ ┻━┻() {
  throw new TableFlipException('ruh roh');
}
						
prompt> print [Stack in (╯°□°)╯︵ ┻━┻() (5 ops)] L4-6 (╯°□°)╯︵ ┻━┻() /Users/aharvey/Trees/phpdbg-for-fun-and-profit/code/exception.php - 0x11206e180 + 5 ops L5 #0 NEW "TableFlipException" @0 L5 #1 SEND_VAL_EX "ruh roh" 1 L5 #2 DO_FCALL L5 #3 THROW @0 L6 #4 RETURN null prompt>

LawnGnome/confoo-tweeps

root@57d38b20b3cb:/src# php artisan tweeps:update Setting the name of LGnome to 'Adam Harvey'... Setting the name of adamculp to Adam Culp... Setting the name of afilina to Anna Filina... Setting the name of serialseb to SerialSeb 🇪🇺🏳️‍🌈... Setting the name of hannelita to Hanneli Tavante... Setting the name of confooca to ConFoo Conference... Setting the name of EliW to EliW... Setting the name of beausimensen to Beau D. Simensen... Setting the name of pjf to Paul Fenwick... Muahahahahaha!
root@57d38b20b3cb:/src# php artisan tweeps:update Setting the name of LGnome to 'Adam Harvey'...
[Illuminate\Database\QueryException] SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for th e right syntax to use near '; --')' at line 1 (SQL: REP LACE INTO tweeps (account, name) VALUES ('LGnome', ''Ad am Harvey''))
root@57d38b20b3cb:/src# phpdbg artisan [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /src/artisan] prompt> run tweeps:update Setting the name of LGnome to 'Adam Harvey'...
[Illuminate\Database\QueryException] ...
[Script ended normally] prompt>
prompt> break ZEND_ADD prompt> b ZEND_ADD Break on any occurrence of the opcode ZEND_ADD

#define ZEND_THROW                           108
						
prompt> break ZEND_THROW [Breakpoint #0 added at ZEND_THROW] prompt> run tweeps:update Setting the name of LGnome to 'Adam Harvey'... [Breakpoint #0 in ZEND_THROW at /src/vendor/laravel/framework/src/Illuminate/Database/Connection.php:714, hits: 1] 00713: throw new QueryException( >00714: $query, $this->prepareBindings($bindings), $e 00715: ); 00716: } prompt>
prompt> help back Command: back Alias: t show trace Provide a formatted backtrace using the standard debug_backtrace() functionality. An optional unsigned integer argument specifying the maximum number of frames to be traced; if omitted then a complete backtrace is given.
prompt> back ... frame #5: App\Console\Commands\UpdateTweeps->updateTweep(account="LGnome", name="'Adam Harvey'") at /src/app/Console/Commands/UpdateTweeps.php:62 ... prompt>
prompt> help frame Command: frame Alias: f switch to a frame The frame takes an optional integer argument. If omitted, then the current frame is displayed If specified then the current scope is set to the corresponding frame listed in a back trace. This can be used to allowing access to the variables in a higher stack frame than that currently being executed.
prompt> frame 5 [Switched to frame #5] >00062: DB::statement("REPLACE INTO tweeps (account, name) VALUES ('$account', '$name')"); 00063: } 00064: } prompt>
prompt> ev $name 'Adam Harvey' prompt> ev $_ = "REPLACE INTO tweeps (account, name) VALUES ('$account', '$name')" REPLACE INTO tweeps (account, name) VALUES ('LGnome', ''Adam Harvey'') prompt>

$_SERVER = [
  'HTTP_HOST' => 'localhost',
  'HTTP_ACCEPT' => '...',
  ...
];
$_GET = [];
$_REQUEST = [];
$_POST = [];
$_COOKIE = [];
$_FILES = [];
chdir('public'); include 'index.php';
						
Source: http://phpdbg.com/docs/mocking-webserver

LawnGnome/phpdbg-fake-request
Packagist lawngnome/phpdbg-fake-request

I PWNED U NOOB LOLX0RZ

<tr>
<td>nefarioushax0r</td>
<td><script>alert("I PWNED U NOOB LOLX0RZ")</script></td>
</tr>
						

http://127.0.0.1:9000/?page=2

root@57d38b20b3cb:/src# phpdbg ./vendor/bin/fake-request GET / public/index.php -g page=2 [Welcome to phpdbg, the interactive PHP debugger, v0.5.0] To get help using phpdbg type "help" and press enter [Please report bugs to <http://bugs.php.net/report.php>] [Successful compilation of /src/vendor/lawngnome/phpdbg-fake-request/bin/fake-request] prompt>
prompt> break routes.php:15 [Pending breakpoint #0 added at routes.php:15] prompt>
prompt> run [Breakpoint #0 at /src/app/Http/routes.php:15, hits: 1] >00015: $tweeps = DB::table('tweeps')->paginate(15); 00016: ob_start(); 00017: prompt>

Route::get('/', function () {
    $tweeps = DB::table('tweeps')->paginate(15);
    ob_start();
?>
...
<?php foreach ($tweeps as $tweep): ?><tr>
	<td><?= $tweep->account ?></td>
	<td><?= $tweep->name ?></td>
</tr><?php endforeach ?>
...
<?php
    return ob_get_clean();
});
						
prompt> break if strpos($tweep->name, 'alert') [Conditional breakpoint #1 added strpos($tweep->name, 'alert')/0x7f46430a8460] prompt> continue [Conditional breakpoint #1: on strpos($tweep->name, 'alert') == true at /src/app/Http/routes.php:46, hits: 1] >00046: <td><?= $tweep->account ?></td> 00047: <td><?= $tweep->name ?></td> 00048: </tr> prompt>
prompt> ev $tweep stdClass Object ( [account] => nefarioushax0r [name] => <script>alert("I PWNED U NOOB LOLX0RZ")</script> ) prompt>
root@de345107bc84:/src# phpdbg -qrr vendor/bin/phpunit --color --coverage-html coverage PHPUnit 5.3.4 by Sebastian Bergmann and contributors. ....................... 23 / 23 (100%) Time: 1.45 seconds, Memory: 6.00MB OK (23 tests, 29 assertions) Generating code coverage report in HTML format ... done

Thank you!

Questions?

@LGnome

Slides: https://lawngnome.github.io/phpdbg-for-fun-and-profit/

Image credits