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

How to implement breakpoints in Lua using the standard hooks?

这个问题是由练习 25.7 on p. 提出的。 Lua(第 4 版)中的 264,更具体地说,提示中提出的优化(我在下面的引用中强调了它):

Exercise 25.7: Write a library for breakpoints. It should offer at least two functions

setbreakpoint(function, line) --> returns handle

removebreakpoint(handle)

We specify a breakpoint by a function and a line inside that function. When the program hits a breakpoint, the library should call debug.debug. (Hint: for a basic implementation, use a line hook that checks whether it is in a breakpoint; to improve performance, use a call hook to trace program execution and only turn on the line hook when the program is running the target function.)

我不知道如何实现提示中描述的优化。

考虑以下代码(当然,这只是为了这个问题而编造的一个人为示例):

 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。假设发生这种情况。当前开启的line hook可以检测到已经不在tweedledum,重新安装call hook

此时执行可以通过以下两种方式之一从tweedledee切换到tweedledum

  1. tweedledee 可以调用 tweedledum(再一次);
  2. tweedledee 可以 return 到它的调用者,恰好是 tweedledum.

问题是: call hook 可以检测到 (1) 中的事件,但无法检测到 (2) 中的事件。

诚然,这个例子很不自然,但这是我能想到的最简单的说明问题的方法。

我能想到的最好的方法(而且它非常弱!)是在第一次调用[=16]时跟踪堆栈深度N =],仅当堆栈深度低于 N 时,才让 line hook 重新安装 call hook。因此,只要tweedledee在堆栈中,line hook就会生效,无论是否正在执行。

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


1 我的理解是,通过安装 line hook,调用 hook 本质上是卸载自身。 AFAICT,每个协程只能激活一个挂钩。 如有错误请指正

2即:call,line,return,count hooks.

And here's the problem: the call hook can detect the event in (1) but it cannot detect the event in (2).

这就是你错的地方:有三种可能的挂钩事件:l 用于线路,c 用于呼叫,r 用于 return。

在你的钩子函数中,你可以将 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
      -- Check if the line is right and possibly call debug.debug() here
   end
end, 'cr')

都在manual;)


请注意,设置挂钩时,您可能需要检查您当前是否目标函数内;否则你可以跳过一个断点,除非你在到达它之前调用(和 return 从)另一个函数。