Last active
December 21, 2018 15:08
-
-
Save tzmfreedom/f12a572426c17a7c05c509d28dce290b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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