当xdebug
初次连接vscode的之后,会调用xdebug_dbgp_init
函数,构造完xml
之后,通过send_message_ex
函数发送数据给vscode
。
当vscode
收到了xdebug
发送的初始化xml
之后,如果vscode
设置了断点,那么vscode
就会把断点信息(那个文件,哪一行)发送给xdebug
。实际上是发生了dbgp
的breakpoint_set
命令。(还会发送transaction_id
,即事务id
)
然后,xdebug
就会调用xdebug_dbgp_cmdloop
来读取vscode
发送给xdebug
的命令。
然后,xdebug
就会调用xdebug_dbgp_parse_option
来解析vscode
发送给xdebug
的命令。把解析道的命令参数放在xdebug_dbgp_arg
结构里面。
然后,xdebug
开始组装xml
响应,设置xml
的command
属性为解析出来的那个command
。例如,如果vscode
发来的command
是设置断点,那么,xdebug
就会把xml
的command
属性设置为breakpoint_set
;设置xml
响应transaction_id
为vscode
发来的transaction_id
。
然后检查xdebug
是否支持vscode
发来的command
,通过函数lookup_cmd
来检查。xdebug
支持的所有command
都放在了变量dbgp_commands
里面(这个变量存了command
对应的handler
)。
然后调用command
对应的handler
,DBGP_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
发送setFunctionBreakpointsRequest
给xdebug
,实际上就是发送breakpoint_list
command
给xdebug
。
然后xdebug
触发breakpoint_list
command
的handler
,DBGP_FUNC(breakpoint_list)
来处理断点。DBGP_FUNC(breakpoint_list)
会调用xdebug_hash_apply
来处理所有的断点,对这些断点信息执行回调函数breakpoint_list_helper
来组装breakpoint_list
command
的xml
响应。
同理,vscode
发送setExceptionBreakpointsRequest
给xdebug
,也是发送breakpoint_list
command
给xdebug
。然后,xdebug
调用breakpoint_list_helper
函数来组装breakpoint_list
command
的xml
响应。
最后,vscode
发送configurationDoneRequest
给xdebug
,告诉xdebug
我已经发送完了所有的断点请求。此时,vscode
会发送run
command
给xdebug
。
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
。(此时,vscode
的ui
并没有停在断点处)
接着,vscode
发送stackTraceRequest
,实际上是stack_get
command
给Xdebug
。
然后xdebug
调用DBGP_FUNC(stack_get) handler
来处理stack_get command
请求。xdebug
会调用return_stackframe
函数来获取栈帧信息。然后组装成xml
响应发送给vscode
。vscode
收到栈帧响应之后,就会在UI
上面停住,给我们一种断点触发的视觉。但是,此时,vscode
的变量板块是没有任何信息的,因为xdebug
还没有把变量的信息返回给vscode
。
接着,vscode
发送scopesRequest
给xdebug
,实际上是context_names command
。然后,xdebug
调用DBGP_FUNC(context_names)
来处理命令。这个context_names
主要是用来告诉vscode
,xdebug
支持返回哪些变量类型,例如Locals
变量,Superglobals
变量,User defined constants
常量。
接着,vscode
发送variablesRequest
给xdebug
,实际上是context_get command
。然后,xdebug
组装变量当前的值,以xml
响应给vscode
。此时,vscode
的变量板块就有变量的值信息了。
然后,我们点击vscode
调试器的下一步,就会发送nextRequest
给xdebug
,实际上是step_over command
。
然后,vscode
和xdebug
就一直重复上面的过程了。
需要注意的一点就是,如果vscode
不给xdebug
发送命令的话,xdebug
就会在xdebug_dbgp_cmdloop
函数里面的recv
函数阻塞住。
如果是调试Swoole
的Server
,那么如果Server
还没有收到数据,是不会回调PHP
函数的。并且,PHP
解释器也会因为Swoole
的事件驱动而停止住,也就意味着,xdebug
此时收不到数据。也就意味着,即使我们点击了下一步,给vscode
发送了请求,xdebug
也无法作出响应。并且,如果我们在Swoole
Server
事件没有到来时多次点击,那么,当Server
事件到来的时候,xdebug
会发送对应的多个reponse
给vscode
。这也算是xdebug
的一个缺陷吧。