Skip to content

Instantly share code, notes, and snippets.

@tzmfreedom
Last active December 21, 2018 15:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tzmfreedom/f12a572426c17a7c05c509d28dce290b to your computer and use it in GitHub Desktop.
Save tzmfreedom/f12a572426c17a7c05c509d28dce290b to your computer and use it in GitHub Desktop.
<?php
class Debugger {
private $transaction_id;
private $debug;
function __construct(bool $debug = false)
{
$this->transaction_id = 1;
$this->debug = $debug;
}
public function run(int $port = 9000) {
$socket = stream_socket_server("tcp://0.0.0.0:${port}", $errno, $errstr);
if (!$socket) {
echo "${errstr} (${errno})\n";
die('Could not create socket');
}
while (true) {
while ($conn = stream_socket_accept($socket, -1)) {
$this->getMessage($conn); // get initial
$this->sendCommand($conn, "stdout");
$this->getMessage($conn); // get strem
$this->sendCommand($conn, "step");
while(true) {
$message = $this->getMessage($conn);
$response = $this->getResponse($message);
if (isset($response['status']) && $response['status'] == 'stopping') {
break;
}
$this->handleResponse($response);
if ($response['_type'] === 'stream') {
continue;
}
$input = readline(">> ");
if ($input == "exit") {
break;
}
$this->sendCommand($conn, $input);
}
fclose($conn);
}
}
fclose($socket);
}
private function getMessage($conn) {
$message = "";
while (true) {
$chunk = stream_socket_recvfrom($conn, 1024, 0);
$message .= $chunk;
$len = strlen($message);
if ($message[$len - 1] == "\0") {
break;
}
}
if ($this->debug) {
echo $message;
}
return $message;
}
private function getResponse(string $message) {
preg_match('/^(\d+)\0([\s\S]+)/', $message, $matches);
$dom = DOMDocument::loadXML($matches[2]);
$init = $dom->getElementsByTagName("init");
if ($init->length != 0) {
$obj = ["_type" => "init"];
foreach ($init[0]->attributes as $attribute) {
$obj[$attribute->name] = $attribute->value;
}
return $obj;
}
$stream = $dom->getElementsByTagName('stream');
if ($stream->length != 0) {
$obj = ['_type' => 'stream'];
foreach ($stream[0]->attributes as $attribute) {
$obj[$attribute->name] = $attribute->value;
}
if (isset($obj['encoding']) && $obj['encoding'] === 'base64') {
$obj['body'] = base64_decode($stream[0]->nodeValue);
} else {
$obj['body'] = $obj->nodeValue;
}
return $obj;
}
$response = $dom->getElementsByTagName("response");
$obj = ["_type" => "response"];
foreach ($response[0]->attributes as $attribute) {
$obj[$attribute->name] = $attribute->value;
}
if ($obj['command'] == 'eval') {
$properties = [];
$property = $dom->getElementsByTagName("property");
if ($property->length != 0) {
foreach ($property[0]->attributes as $attribute) {
$properties[$attribute->name] = $attribute->value;
}
if (isset($properties['encoding']) && $properties['encoding'] === 'base64') {
$properties['body'] = base64_decode($property[0]->nodeValue);
} else if ($properties['type'] === 'object') {
$properties['body'] = "<".$properties['classname'].">";
} else {
$properties['body'] = $property[0]->nodeValue;
}
$obj["property"] = $properties;
}
} else {
$debugMessages = [];
$messages = $dom->getElementsByTagName("message");
if ($messages->length !== 0) {
foreach ($messages[0]->attributes as $attribute) {
$debugMessages[$attribute->name] = $attribute->value;
}
$obj["xdebug:message"] = $debugMessages;
}
}
return $obj;
}
private function handleResponse(array $obj) {
switch($obj["_type"]){
case 'init':
// $file = $obj['fileuri'];
// $lineno = $obj['lineno'];
return;
case 'stream':
echo $obj['body'] . PHP_EOL;
break;
case 'response':
if (isset($obj['property'])) {
echo $obj['property']['body'] . PHP_EOL;
}
if (isset($obj['xdebug:message'])) {
$file = $obj['xdebug:message']['filename'];
$lineno = $obj['xdebug:message']['lineno'];
break;
}
}
if (isset($file) && $file != "" && $file != NULL) {
echo "$file at $lineno\n";
$this->showFile($file, (int)$lineno);
}
}
private function showFile(string $file, int $lineno) {
$fp = fopen($file, 'r');
$size = 15;
$cnt = $lineno < 7 ? 0 : $lineno - 7;
$current = 1;
while ($cnt > 0) {
fgets($fp);
$cnt--;
$current++;
}
while ($line = fgets($fp)) {
if ($current == $lineno) {
echo ">> $line";
} else {
echo " $line";
}
$size--;
$current++;
if ($size == 0) {
break;
}
}
fclose($fp);
}
private function sendCommand($conn, string $input) {
if (in_array($input, ["n", "next"])) {
$command = "step_over -i $this->transaction_id";
} else if (in_array($input, ["c", "continue"])) {
$command = "run -i $$this->transaction_id";
} else if (in_array($input, ["s", "step"])) {
$command = "step_into -i $$this->transaction_id";
} else if (in_array($input, ["status"])) {
$command = "status -i $$this->transaction_id";
} else if (in_array($input, ["source"])) {
$command = "source -i $$this->transaction_id";
} else if (in_array($input, ["stdout"])) {
$command = "stdout -i $$this->transaction_id -c 1";
} else if (in_array($input, ["w", "whereami"])) {
return;
} else {
$b64encoded = base64_encode($input);
$command = "eval -i $$this->transaction_id -- ${b64encoded}";
}
stream_socket_sendto($conn, "${command}\0");
$this->transaction_id++;
}
}
$options = getopt('d');
$debugger = new Debugger(isset($options['d']));
$debugger->run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment