如何在Lua中使用标准钩子实现断点?

这个问题受到《Lua编程》(第四版)264页上练习25.7的启发,更具体地说,是提示中提出的优化(我在下面的引文中加重了它):

练习25.7:编写一个断点库。它应该提供至少两个函数

setbreakpoint(function, line) --> 返回句柄

removebreakpoint(handle)

我们用一个函数和一个该函数内的行来指定断点。当程序命中断点时,库应调用debug.debug。(提示:对于基本实现,使用一行钩子来检查是否在断点内;为了提高性能,使用调用钩子来跟踪程序执行,并仅在程序运行目标函数时打开行钩子。)

我无法弄清楚如何实现提示中描述的优化。

考虑以下代码(当然,这只是为了本问题而人为制造的人造例子):

 function tweedledum ()
   while true do
     local ticket = math.random(1000)
     if ticket % 5  == 0 then tweedledee() end
     if ticket % 17 == 0 then break end
   end
 end

 function tweedledee ()
   while true do
     local ticket = math.random(1000)
     if ticket % 5  == 0 then tweedledum() end
     if ticket % 17 == 0 then break end
   end
 end

 function main ()
   tweedledum()
 end

函数main应该代表程序的入口点。函数tweedledumtweedledee几乎相同,并且几乎仅仅是反复调用彼此。

假设我在tweedledum的赋值行上设置了断点。我可以实现一个调用钩子,可以检查是否已调用tweedledum,然后设置一个行钩子,该行钩子将在调用所需行时检查1。

很可能tweedledum在中断循环之前会调用tweedledee。假设发生这种情况。当前启用的行钩子可以检测到不再在tweedledum中,并重新安装调用钩子。

此时,执行可以以两种方式之一从tweedledee切换到tweedledum:

  1. tweedledee可以再次调用tweedledum
  2. tweedledee可以_返回_其调用者,该调用者恰好是tweedledum

_问题在于:_调用钩子可以检测到事件(1)中的事件,但无法检测到事件(2)中的事件。

当然,这个例子非常人为,但它是我能想到的说明问题的最简单的方法。

我能想到的最好方法(非常弱!)是在第一次调用tweedledum时跟踪堆栈深度N,只有当堆栈深度下降到N以下时,行钩子才重新安装调用钩子。因此,只要tweedledee在堆栈中,无论它是否正在执行,行钩子都将生效。

是否有可能仅使用Lua中可用的标准钩子来实现提示中描述的优化2?


1 我的理解是,通过安装行钩子,调用钩子本质上卸载了它自己。AFAICT,每个协程只能有一个活动钩子。**如果我错了,请正确我。**

2 即:调用、行、返回和计数钩子。

原文链接 https://stackoverflow.com/questions/57289019

点赞
stackoverflow用户4984564
stackoverflow用户4984564

以下是翻译后的中文,保留了原本的 markdown 格式:

问题在于:call 钩子能够检测到 (1) 中的事件,但不能检测到 (2) 中的事件。

你的想法不对:其实有三种不同的钩子事件:l 表示行,c 表示函数调用,r 表示函数返回。

在钩子函数中,你可以把 returncall 事件看作几乎是一样的,唯一的不同就是当 return 事件触发时,你仍然在被调用的函数内部,这时目标函数在堆栈中会高出一层。

debug.sethook(function(event, line)
   if event == "call" or event == "return" then
      if debug.getinfo(event=='call' and 2 or 3).func == target then
         debug.sethook(debug.gethook(), 'crl')
      else
         debug.sethook(debug.gethook(), 'cr')
      end
   elseif event == 'line' then
      -- 检查是否是正确的行,并在此处可能调用 debug.debug()
   end
end, 'cr')

所有的细节都在手册中有说明 ;)


请注意,在设置钩子时,你可能需要检查当前是否在目标函数内部;否则你可能会错过一个断点,除非你在到达它之前调用(并返回)另一个函数。

2019-07-31 11:06:43