Xdebug与Vscode通信过程

xdebug初次连接vscode的之后,会调用xdebug_dbgp_init函数,构造完xml之后,通过send_message_ex函数发送数据给vscode

vscode收到了xdebug发送的初始化xml之后,如果vscode设置了断点,那么vscode就会把断点信息(那个文件,哪一行)发送给xdebug。实际上是发生了dbgpbreakpoint_set命令。(还会发送transaction_id,即事务id

然后,xdebug就会调用xdebug_dbgp_cmdloop来读取vscode发送给xdebug的命令。

然后,xdebug就会调用xdebug_dbgp_parse_option来解析vscode发送给xdebug的命令。把解析道的命令参数放在xdebug_dbgp_arg结构里面。

然后,xdebug开始组装xml响应,设置xmlcommand属性为解析出来的那个command。例如,如果vscode发来的command是设置断点,那么,xdebug就会把xmlcommand属性设置为breakpoint_set;设置xml响应transaction_idvscode发来的transaction_id

然后检查xdebug是否支持vscode发来的command,通过函数lookup_cmd来检查。xdebug支持的所有command都放在了变量dbgp_commands里面(这个变量存了command对应的handler)。

然后调用command对应的handlerDBGP_FUNC(breakpoint_set)。这些handler都在文件handler_dbgp.c里面。这个handler会判断断点的类型,例如line断点、conditional断点、call断点等等。xdebug支持的断点类型在变量xdebug_breakpoint_types里面。最后,把断点信息保存在结构xdebug_brk_info里面。然后把xdebug_brk_info类型转化为xdebug_llist_element存放在context->line_breakpoints链表里面。

然后,xdebug发送设置断点成功的响应给vscode

然后,vscode发送setFunctionBreakpointsRequestxdebug,实际上就是发送breakpoint_list commandxdebug

然后xdebug触发breakpoint_list commandhandlerDBGP_FUNC(breakpoint_list)来处理断点。DBGP_FUNC(breakpoint_list)会调用xdebug_hash_apply来处理所有的断点,对这些断点信息执行回调函数breakpoint_list_helper来组装breakpoint_list commandxml响应。

同理,vscode发送setExceptionBreakpointsRequestxdebug,也是发送breakpoint_list commandxdebug。然后,xdebug调用breakpoint_list_helper函数来组装breakpoint_list commandxml响应。

最后,vscode发送configurationDoneRequestxdebug,告诉xdebug我已经发送完了所有的断点请求。此时,vscode会发送run commandxdebug

xdebug接收到run命令之后,xdebug_dbgp_cmdloop函数跳出执行。

然后,xdebug调用xdebug_debugger_handle_breakpoints函数来检查当前执行到的代码是否有断点。

然后,xdebug调用old_execute_ex来执行代码。此时,会调用xdebug hook后的handelr ZEND_EXT_STMT_SPEC_HANDLER来执行opcode。例如,xdebug_debugger_statement_call就是表达式对应的hook函数。如果这个函数发现当前op_array有断点信息,那么就会调用XG_DBG(context).handler->break_on_line函数来进行处理(XG_DBG(context).handler->break_on_line只是记录一些信息到日志文件里面)。然后调用xdebug_handle_hit_value。这个函数会记录xdebug触发了多少次断点。然后调用XG_DBG(context).handler->remote_breakpoint,这个函数会发送组建xml响应给vscode,把触发的断点信息发送给vscode。(此时,vscodeui并没有停在断点处)

接着,vscode发送stackTraceRequest,实际上是stack_get commandXdebug

然后xdebug调用DBGP_FUNC(stack_get) handler来处理stack_get command请求。xdebug会调用return_stackframe函数来获取栈帧信息。然后组装成xml响应发送给vscodevscode收到栈帧响应之后,就会在UI上面停住,给我们一种断点触发的视觉。但是,此时,vscode的变量板块是没有任何信息的,因为xdebug还没有把变量的信息返回给vscode

接着,vscode发送scopesRequestxdebug,实际上是context_names command。然后,xdebug调用DBGP_FUNC(context_names)来处理命令。这个context_names主要是用来告诉vscodexdebug支持返回哪些变量类型,例如Locals变量,Superglobals变量,User defined constants常量。

接着,vscode发送variablesRequestxdebug,实际上是context_get command。然后,xdebug组装变量当前的值,以xml响应给vscode。此时,vscode的变量板块就有变量的值信息了。

然后,我们点击vscode调试器的下一步,就会发送nextRequestxdebug,实际上是step_over command

然后,vscodexdebug就一直重复上面的过程了。

需要注意的一点就是,如果vscode不给xdebug发送命令的话,xdebug就会在xdebug_dbgp_cmdloop函数里面的recv函数阻塞住。

如果是调试SwooleServer,那么如果Server还没有收到数据,是不会回调PHP函数的。并且,PHP解释器也会因为Swoole的事件驱动而停止住,也就意味着,xdebug此时收不到数据。也就意味着,即使我们点击了下一步,给vscode发送了请求,xdebug也无法作出响应。并且,如果我们在Swoole Server事件没有到来时多次点击,那么,当Server事件到来的时候,xdebug会发送对应的多个reponsevscode。这也算是xdebug的一个缺陷吧。