16位实模式程序的调试器如何生成堆栈跟踪?

3
我正在使用模拟器运行旧的DOS程序,并且已经达到了想要跟踪程序堆栈的程度。但是,我遇到了一个问题,具体来说是如何检测近调用和远调用。一些背景信息:
- 近调用仅将IP推送到堆栈上,并且预计与仅弹出IP以返回的ret成对出现。 - 远调用将CS和IP都推送到堆栈上,并且预计与弹出CS和IP以返回的retf成对出现。 - 除了知道调用它的指令类型或使用哪种返回之外,没有办法知道调用是近调用还是远调用。
幸运的是,在开发该程序的时期,基于BP的堆栈帧非常普遍,因此似乎走过堆栈并不是问题:只需按照BP链即可。不幸的是,获取CS和/或IP很困难,因为似乎没有任何方法可以仅通过查看堆栈就确定调用是近调用还是远调用。
我有有关函数的元数据可用,因此如果我已经知道实际的CS和IP,我可以告诉函数是近调用还是远调用,但是除非我已经知道它是远调用还是近调用,否则我无法弄清楚IP和CS。
我通过猜测并查看我的猜测是否导致有效的函数查找来取得了一些成功,但是我认为这种方法会产生很多误报。
因此,我的问题是:DOS时代的调试器如何解决这个问题并生成堆栈跟踪?我是否错过了某种算法,还是他们只是在堆栈中编码调试信息?(如果是这种情况,那么我将不得不想出其他办法。)

1
如果你在这里没有得到答案,考虑加入[reverseengineering.se]并在那里提问。 (但不要同时这样做,未经通知。这只会导致知识分散。) - Jongware
1
[SP] 不是有效的16位寻址模式,只有以[BP]作为基址才意味着SS作为段寄存器,因此,在访问堆栈时使用BP进行随机访问是唯一的好选择(不仅仅是用于临时推入/弹出)。没有理由不先保存/恢复它,以创建传统的遗留堆栈帧。 - Peter Cordes
我们的附属网站Retro Computing 可能更适合这个问题。我从1985年开始广泛使用16位x86环境(包括低级代码),但我不记得当时调试器是如何工作的。 - njuffa
1个回答

2

仅仅是猜测,我从未使用16位x86开发工具(无论是现代还是过去):

你知道当前函数的CS:IP值(或者一个触发异常或其他问题的函数的CS:IP值)。

你可能有元数据告诉你这是一个“远程”函数,它是通过远程调用调用的,或者你可以尝试解码直到找到retnretf,然后用它来决定返回地址是近的IP还是远的CS:IP

(假设这是一个正常的函数,它以某种形式的ret返回。或者如果它以jmp尾调用另一个函数结束,那么返回地址可能匹配,但这是另一层的假设。而且想要弄清楚一个近的jmp是否是函数的结尾,而不仅仅是大函数内部的跳转,是一个模糊的问题,没有任何符号元数据。)

但是无论如何,将同样的东西应用于父函数:在成功回溯一级之后,你现在拥有父函数中call后面指令的CS:IP值和BP链接列表的SS:BP值。


顺便说一句,遗留BP堆栈帧被广泛使用的原因非常好: [SP]不是有效的16位寻址模式,只有以[BP]作为基址时才意味着SS作为段,所以使用BP访问堆栈是随机访问(不仅仅是用于临时存储的push/pop)的唯一良好选项。没有理由不先保存/恢复它(在任何其他寄存器或保留堆栈空间之前)以创建传统的堆栈帧。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接