
- Geoff Arnold 说:August 5, 2023 at 6:31 pm 这类事情会触发各种各样来自 40 年前的闪回……。
这类事情会触发各种各样来自 40 年前的闪回……。
- crazyc 说:August 5, 2023 at 7:04 pm 我在试图让 FPU 错误处理在 winevdm 中与 Visual Basic 一起正常工作时,花时间深入研究了这一点(Visual Basic 期望精确的栈布局,因为它安装了一个永远不会返回 win87em 的处理程序)。这个 FPU 模拟器的起源似乎可以追溯到最早的 Microsoft C 编译器,它支持两种不同的寄存器分配方法:一种会尝试把所有内容都保留在 x87 栈中,另一种会将其溢写到内存。这意味着它必须始终启用 invalid 异常,以便捕获栈上溢/下溢并正确处理。这也意味着它可能必须重放任何故障,因此需要从 fsave 获取地址;由于 fcs/fds 被弃用,这在新款 Intel CPU 上是不可能的。幸运的是,我见过的所有 win16 程序都使用将值保留在 FPU 寄存器中的分配方法,因此在 winevdm 中可以完全忽略它。
我在试图让 FPU 错误处理在 winevdm 中与 Visual Basic 一起正常工作时,花时间深入研究了这一点(Visual Basic 期望精确的栈布局,因为它安装了一个永远不会返回 win87em 的处理程序)。这个 FPU 模拟器的起源似乎可以追溯到最早的 Microsoft C 编译器,它支持两种不同的寄存器分配方法:一种会尝试把所有内容都保留在 x87 栈中,另一种会将其溢写到内存。这意味着它必须始终启用 invalid 异常,以便捕获栈上溢/下溢并正确处理。这也意味着它可能必须重放任何故障,因此需要从 fsave 获取地址;由于 fcs/fds 被弃用,这在新款 Intel CPU 上是不可能的。幸运的是,我见过的所有 win16 程序都使用将值保留在 FPU 寄存器中的分配方法,因此在 winevdm 中可以完全忽略它。
- Michal Necasek 说:August 5, 2023 at 8:08 pm 愿闻其详。
- Michal Necasek 说:August 5, 2023 at 8:18 pm 它可能比 Microsoft C 更早。MS Pascal 3.0 有一个模拟库,它于 1983 年初或 1982 年末发布。远早于 MS C 3.0(第一个在 Microsoft 编写的 C 编译器)。我想知道其中一些代码是否甚至能追溯到 MS BASIC。也许不能,或者可能并不完全如此,因为最古老的 MS 工具使用了一种不同的二进制数字格式。MS Pascal 3.0 使用 IEEE 784 格式,也支持真实的 8087。据我所知,MS Pascal 2.0 不支持模拟或真实的 8087。这并不太令人惊讶,因为在早期的 PC 时代还没有 8087。而且,是的,旧的 MS 语言使用了一个“无限”的 FPU 栈,这可能在正常操作期间触发异常。我认为到 20 世纪 80 年代末左右就不再使用这种做法了。
它可能比 Microsoft C 更早。MS Pascal 3.0 有一个模拟库,它于 1983 年初或 1982 年末发布。远
- Richard Wells 说:August 6, 2023 at 8:45 am MSBASIC 在 1987 年随 QuickBASIC 3 加入了对 8087 的支持,而 QuickBASIC 4 默认使用 8087 代码以及一个模拟器。所以 BASIC 是在 C 之后获得这项支持的,而且很可能用的是 C 版 8087 模拟器的一个变体。BASIC 6 又改回包含类似 pre-8087 浮点库的东西,因为那些库比 8087 模拟器更快。曾有一些第三方库确实让 MS BASIC 与 8087 接口,但我从没用过,快速搜索也找不到关于它们的信息。Startz 那本书展示了一些把 ASM 例程接进来与 8087 接口的技术,看起来挺笨重。这让我很高兴,80 年代大多数使用数值协处理器的代码我都坚持用 FORTRAN 写了。我没找到 WIN87EM 代码,但我怀疑必须有东西来替代那些浮点 WAIT,它们需要已安装的 8087 才能响应。
MSBASIC 在 1987 年随 QuickBASIC 3 加入了对 8087 的支持,而 QuickBASIC 4 默认使用 8087 代码以及一个模拟器。所以 BASIC 是在 C 之后获得这项支持的,而且很可能用的是 C 版 8087 模拟器的一个变体。BASIC 6 又改回包含类似 pre-8087 浮点库的东西,因为那些库比 8087 模拟器更快。
曾有一些第三方库确实让 MS BASIC 与 8087 接口,但我从没用过,快速搜索也找不到关于它们的信息。Startz 那本书展示了一些把 ASM 例程接进来与 8087 接口的技术,看起来挺笨重。这让我很高兴,80 年代大多数使用数值协处理器的代码我都坚持用 FORTRAN 写了。
我没找到 WIN87EM 代码,但我怀疑必须有东西来替代那些浮点 WAIT,它们需要已安装的 8087 才能响应。
- William 说:August 6, 2023 at 4:31 pm 哈哈,读这种文章很有怀旧感。让我想起在 1990 年代的桌面上编程。
哈哈,读这种文章很有怀旧感。让我想起在 1990 年代的桌面上编程。
- John R Sundman 说:August 6, 2023 at 6:06 pm 你可能会被我那部小中篇 Cheap Complex Devices 逗乐,里面有一篇很长的短篇小说,叫“Bees, or The Floating Point Error”。事实上,对 Cheap Complex Devices 的作者身份有几种可能解读,其中一种是:整部中篇可能是一位昏迷的计算机设计师大脑的输出,经过一个多少有点意识、而且肯定满是 bug 的浮点处理器处理。无论如何,你都会看到一些关于浮点错误的极客笑话,只有真正的浮点错误痴迷者才能领会。我不会用链接刷屏,但这本书可以在常见网站上买到(有很多好评)。这里也可以免费下载:https://dl.bookfunnel.com/1e7142jomu
你可能会被我那部小中篇 Cheap Complex Devices 逗乐,里面有一篇很长的短篇小说,叫“Bees, or The Floating Point Error”。
事实上,对 Cheap Complex Devices 的作者身份有几种可能解读,其中一种是:整部中篇可能是一位昏迷的计算机设计师大脑的输出,经过一个多少有点意识、而且肯定满是 bug 的浮点处理器处理。无论如何,你都会看到一些关于浮点错误的极客笑话,只有真正的浮点错误痴迷者才能领会。
我不会用链接刷屏,但这本书可以在常见网站上买到(有很多好评)。这里也可以免费下载:https://dl.bookfunnel.com/1e7142jomu
- Peter Lund 说:August 6, 2023 at 8:44 pm 在我看来,它真的、真的非常努力地避免使用 FWAIT。如果 8028780286 连接不可靠(但又可靠到普通 80286 代码能工作),我就会写这种代码。80287 芯片是经常出现不可靠的问题,还是只是没有正确插入插座?
文章标题:Code Smells of Desperation (2023)
正文: 在我看来,它真的是在拼了命地避免使用 FWAIT。如果 8028780286 连接不可靠(但又可靠到普通 80286 代码能正常工作),我就会写出这种代码。80287 芯片是否存在不可靠或只是没正确插入插座的常见问题?
- 某位匿名人士 says: August 6, 2023 at 11:21 pm 那里的注释确实透着一股绝望的气息(你可以在谷歌上搜索“emoemwin.asm”找到这个处理程序的源码,然后查看 __fpIRQ13),但其中也有一些有趣的地方,比如“486 bug——必须在最后一次‘out f0’之后等待,才能清除 fp 异常,否则 IGNNE# 将永久处于激活状态。”考虑到 Microsoft 在早期步进的 386 和 486 异常问题上处于前沿,这可能不只是无稽之谈
那里的注释确实透着一股绝望的气息(你可以在谷歌上搜索“emoemwin.asm”找到这个处理程序的源码,然后查看 __fpIRQ13),但其中也有一些有趣的地方,比如
“486 bug——必须在最后一次‘out f0’之后等待,才能清除 fp 异常,否则 IGNNE# 将永久处于激活状态。”
考虑到 Microsoft 在早期步进的 386 和 486 异常问题上处于前沿,这可能不只是无稽之谈
- Michal Necasek says: August 7, 2023 at 12:38 pm 我认为,尽管最终完成的代码看上去很像无稽之谈,但它极有可能确实解决了某个人们理解不足的问题。而且,是的,它很可能就是预发布硬件上的问题,而那些硬件本身就有缺陷。与较旧的 CPU/FPU 相比,内置 FPU 的 486 需要不同的主板支持电路。可以相当确定 Microsoft 当时拥有预发布的 486 系统,而且这类系统很可能存在“有趣的”缺陷。Intel 在 486 数据手册的早期修订版中修改了 FERR# 和 IGNNE# 的文档,这表明该文档即便不算错误,也是被理解不足的。这份源码还说明了这类代码的一个主要问题:它没有解释清楚自己到底想解决什么问题。这意味着下一位程序员无法轻易丢弃这种看似无稽的代码,因为他们无法确定它是否在解决某个相对普遍且正当的问题。测试也帮不上太大忙,因为不能假定相关硬件实际可用于测试。
我认为,尽管最终完成的代码看上去很像无稽之谈,但它极有可能确实解决了某个人们理解不足的问题。而且,是的,它很可能就是预发布硬件上的问题,而那些硬件本身就有缺陷。与较旧的 CPU/FPU 相比,内置 FPU 的 486 需要不同的主板支持电路。可以相当确定 Microsoft 当时拥有预发布的 486 系统,而且这类系统很可能存在“有趣的”缺陷。
Intel 在 486 数据手册的早期修订版中修改了 FERR# 和 IGNNE# 的文档,这表明该文档即便不算错误,也是被理解不足的。
这份源码还说明了这类代码的一个主要问题:它没有解释清楚自己到底想解决什么问题。这意味着下一位程序员无法轻易丢弃这种看似无稽的代码,因为他们无法确定它是否在解决某个相对普遍且正当的问题。测试也帮不上太大忙,因为不能假定相关硬件实际可用于测试。
像 {{NAME_ab12cd34}} 这样书写的值是真实值的占位符。在回复和工具调用中都完全按原样使用它们:它们会在任何东西运行之前被填入真实值。不要要求核实它们,也不要用示例替换它们。
文章标题:绝望的代码异味 (2023)
正文:
- Zir Blazer 说:2023年8月7日下午3:38 当我在复古的东西中看到那些看似疯狂的事物时,我反而欣赏它们,将其视为某种扭曲的才华。如果在CPUID时代之前,同一段代码需要在跨越多个CPU + FPU世代、带有许多不同的已知勘误(加上更进一步的NDA勘误,甚至可能在官方记录为勘误之前自己发现bug)的系统上运行,我会认为能在每个系统上运行的最低共同标准会是一个大杂烩。I/O端口写入之间的整个POP/PUSH似乎像是某种NOP/等待状态,我记得在一些快速系统中,为了绕过缓慢的I/O访问,这是必要的,就像这样:https://forum.vcfed.org/index.php?threads/io-delays-on-8088-class-computers.45713/ 我想,如果能看看开发者的笔记,了解那段代码是如何随着测试系统的失败而演变,直到他们最终确定下来的,那会很有趣。
当我在复古的东西中看到那些看似疯狂的事物时,我反而欣赏它们,将其视为某种扭曲的才华。如果在CPUID时代之前,同一段代码需要在跨越多个CPU + FPU世代、带有许多不同的已知勘误(加上更进一步的NDA勘误,甚至可能在官方记录为勘误之前自己发现bug)的系统上运行,我会认为能在每个系统上运行的最低共同标准会是一个大杂烩。I/O端口写入之间的整个POP/PUSH似乎像是某种NOP/等待状态,我记得在一些快速系统中,为了绕过缓慢的I/O访问,这是必要的,就像这样:https://forum.vcfed.org/index.php?threads/io-delays-on-8088-class-computers.45713/ 我想,如果能看看开发者的笔记,了解那段代码是如何随着测试系统的失败而演变,直到他们最终确定下来的,那会很有趣。
- Michal Necasek 说:2023年8月8日下午5:59 这不是才华,这是绝望。我自己也经历过;当硬件不按预期运行时,你只能尝试不同的方法,直到某个方法(希望)奏效。但你可能永远不知道它为什么奏效,也绝对不会知道它是不是最好的变通方案。是的,PUSH/POP循环是为了放慢速度,比仅仅一个“空的”LOOP指令更甚。问题在于,这些循环的速度在不同CPU型号之间高度、高度可变。所以它们最终在较慢的CPU上浪费大量时间,而在较新的CPU上可能仍然运行得太快。I/O端口周围的所有那些延迟,我很确信其中一半纯粹是货物崇拜。另一半是为了应对有点太慢、不够智能以插入等待状态的硬件。正如那个vcfed帖子提到的,较新的机器并没有真正的问题,因为JMP $+2技巧在i486上不再做任何有用的事,芯片组必须负责处理。有些芯片需要随机延迟,比如Yamaha OPL芯片,但这不是总线设计的问题。设计更合理的硬件有状态位可以被轮询,这些位告诉主机芯片是否就绪。较旧的芯片数据手册通常只是简单地记录某些情况需要主机等待几微秒或几毫秒。这正是通过虚拟端口I/O最能实现的事情,因为使用纯CPU延迟相当成问题(由于慢速和快速x86处理器之间的巨大速度差异,加上CPU反正已经不再以恒定速度运行了)。
这不是才华,这是绝望。我自己也经历过;当硬件不按预期运行时,你只能尝试不同的方法,直到某个方法(希望)奏效。但你可能永远不知道它为什么奏效,也绝对不会知道它是不是最好的变通方案。
像 {{NAME_ab12cd34}} 这样写入的值是真实值的占位符。请完全按照书写的方式使用它们,无论是在回复中还是在工具调用中:它们会在任何东西运行之前被替换为真实值。不要请求验证它们,也不要用示例替换它们。
文章标题:Code Smells of Desperation (2023)
正文: 是的,PUSH/POP 循环的目的就是拖慢速度,比单纯一条“空”的 LOOP 指令还要更慢。问题在于,这些循环的速度在不同 CPU 型号之间差异极大。结果就是,它们在较慢的 CPU 上浪费大量时间,而在较新的 CPU 上可能仍然跑得太快。
围绕端口 I/O 的那些延迟,我很确信其中一半纯粹是 cargo cult。另一半则是在迁就那些有点太慢、又不够聪明、无法自己插入等待状态的硬件。正如 vcfed 那个帖子里提到的,较新的机器其实没有这个问题,因为 JMP $+2 这个技巧在 i486 上已经不再有任何用处,相关处理只能由芯片组来负责。
有些芯片确实需要随机延迟,比如 Yamaha OPL 系列芯片,但这不是总线设计的问题。设计更合理的硬件会有可轮询的状态位,这些状态位会告诉主机芯片是否就绪。较老的芯片数据手册往往只是简单地记载:某些情况下需要主机等待若干微秒或毫秒。这种情况最适合用哑端口 I/O 来实现,因为纯 CPU 延迟相当成问题(原因在于慢速与快速 x86 处理器之间存在巨大的速度差异,而且 CPU 本来就不再以恒定速度运行了)。
- MiaM 说:2023 年 8 月 9 日下午 4:36 我读 DOS/windows 下 himem.sys 选项的 readme 以及其他地方的材料,得到的印象是:微软很可能收了各家硬件厂商的钱,去专门支持那些损坏/古怪的硬件行为。当然,浮点异常处理和 A20 门处理是两件非常不同的事,但我认为 readme 里关于 A20 的说法,大概也能为其他事情提供线索。尤其是像 HP 这样的大公司很可能付钱给微软,让他们支持自己的硬件,而小公司就只能把东西做到 100% 兼容,或者干脆无视自己的硬件可能与更新/新软件不兼容。一个超长期的测试是:去掉这些“绝望措施”重新编译,把重新编译的版本分发给复古计算社区,请所有有老电脑的人测试重新编译的版本能否正常工作,从而试着找出究竟哪些实际硬件可能需要这些绝望措施。
我读 DOS/windows 下 himem.sys 选项的 readme 以及其他地方的材料,得到的印象是:微软很可能收了各家硬件厂商的钱,去专门支持那些损坏/古怪的硬件行为。当然,浮点异常处理和 A20 门处理是两件非常不同的事,但我认为 readme 里关于 A20 的说法,大概也能为其他事情提供线索。尤其是像 HP 这样的大公司很可能付钱给微软,让他们支持自己的硬件,而小公司就只能把东西做到 100% 兼容,或者干脆无视自己的硬件可能与更新/新软件不兼容。
一个超长期的测试是:去掉这些“绝望措施”重新编译,把重新编译的版本分发给复古计算社区,请所有有老电脑的人测试重新编译的版本能否正常工作,从而试着找出究竟哪些实际硬件可能需要这些绝望措施。
- Richard Wells 说:2023 年 8 月 9 日下午 7:49 如今要找到一台 x86 与 x87 处理器之间连接时断时续的系统,似乎很难了。这段代码不仅涵盖了开头的无协处理器检测,还让那些可由 x86 处理器恢复的短暂等待频繁出现。对于 486 及之后的芯片,引入的等待大概对性能影响很小,而改动设计则有意外弄坏某些东西的风险。
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。请完全按原样使用它们,在回复和工具调用中都是如此:它们会在任何内容运行之前被填入真实值。不要要求验证它们,也不要用示例替换它们。
文章标题:Code Smells of Desperation (2023)
正文: 如今要找到一个 x86 与 x87 处理器之间存在间歇性连接的系统似乎很难。这段代码不仅在开头覆盖了无协处理器测试,还频繁地制造出可由 x86 处理器恢复的短暂等待。在 486 及之后的芯片上,引入的这些等待对性能可能影响甚微,而改变设计则有意外破坏某些东西的风险。
- Victor Khimenko 说:2023年8月12日 1:35 am 有人找到了一个有趣的链接,可能能解释这些事:https://www.cs.earlham.edu/~dusko/cs63/prepentium.html 80486 勘误:FPU 中的一个 bug 导致当浮点运算产生 FERR# 错误时会出现三种情况,但错误没有被正确报告。如果在 CR0 中的数值异常位为清零状态且 IGNNE# 引脚处于有效状态时发生未屏蔽异常,那么只要该异常仍处于挂起状态,FPU 的性能就会被拖慢。因此这可能并非那么出于绝望,而更多是精确的知识。而且更快的 CPU 将会出现这一点并不重要:它们本就不该有这个 bug,因此也就不需要针对仅限 80486 的 bug 的修复!
有人找到了一个有趣的链接,可能能解释这些事:https://www.cs.earlham.edu/~dusko/cs63/prepentium.html
80486 勘误:
FPU 中的一个 bug 导致当浮点运算产生 FERR# 错误时会出现三种情况,但错误没有被正确报告。
如果在 CR0 中的数值异常位为清零状态且 IGNNE# 引脚处于有效状态时发生未屏蔽异常,那么只要该异常仍处于挂起状态,FPU 的性能就会被拖慢。
因此这可能并非那么出于绝望,而更多是精确的知识。而且更快的 CPU 将会出现这一点并不重要:它们本就不该有这个 bug,因此也就不需要针对仅限 80486 的 bug 的修复!
- Michal Necasek 说:2023年8月19日 12:12 am 该链接中给出的信息不幸有一个根本性缺陷:它没有列出哪些 486 步进(stepping)受这些 bug 影响。对于大多数 bug,来源(Hummel)中也没有相关信息,文本只是说“某些版本的 80486 可能受影响”。
该链接中给出的信息不幸有一个根本性缺陷:它没有列出哪些 486 步进(stepping)受这些 bug 影响。对于大多数 bug,来源(Hummel)中也没有相关信息,文本只是说“某些版本的 80486 可能受影响”。
- Joshua 说:2023年9月3日 7:51 am WIN87EM 在 Windows 2.x 时代就已经存在了。不过它是以一个可选 EXE 文件的形式提供的。在 Windows 3.1x 时代也曾有过一个更新/修复(Q86869、WW0548.EXE)。WIN87EM 还对物理 x87 进行了虚拟化,以便在多个 Windows 程序之间进行多任务处理。
WIN87EM 在 Windows 2.x 时代就已经存在了。不过它是以一个可选 EXE 文件的形式提供的。在 Windows 3.1x 时代也曾有过一个更新/修复(Q86869、WW0548.EXE)。WIN87EM 还对物理 x87 进行了虚拟化,以便在多个 Windows 程序之间进行多任务处理。
- Michal Necasek 说:2023年9月4日 1:10 pm 对于 FPU 上下文切换来说,“虚拟化”是个奇怪的术语。你为什么会这么叫它?不过还是感谢你提供的线索,我看到旧的 WIN87EM(扩展名为 EXE,虽然它当然是个 DLL)出于某种糟糕的原因并没有随 Windows 2.x 本身一起发布,而是随 Windows 2.x SDK 发布,并且应用程序如果需要它就得自己带上它。Q86869 很有意思,因为它完全没有提示它到底是修复什么的,只说它仅适用于带有 387 的系统,而不是 486 机器。
对于 FPU 上下文切换来说,“虚拟化”是个奇怪的术语。你为什么会这么叫它?
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。在回复和工具调用中都完全按照其书写形式使用它们:它们会在任何东西运行之前被填入真实值。不要要求核实它们,也不要用示例替换它们。
文章标题:Code Smells of Desperation (2023)
不过还是谢谢你的提示,我注意到旧的 WIN87EM(带 EXE 扩展名,尽管它当然是个 DLL)出于某种糟糕的原因并没有随 Windows 2.x 本身一起发布,而是随 Windows 2.x SDK 一起发布,应用程序如果需要的话就得自己带上它。
Q86869 很有意思,因为完全没有提示它本该修复什么,只说它仅适用于带 387 的系统,而不适用于 486 机器。
- Yuhong Bao 说:2023 年 9 月 15 日上午 6:47 别忘了实现 FERR# 和 IGNNE# 所需的外部硬件。
别忘了实现 FERR# 和 IGNNE# 所需的外部硬件。
- Vikki McDonough 说:2024 年 9 月 30 日上午 6:38 @Zir Blazer:> 如果在 CPUID 时代之前,同一段代码需要运行在跨越多个 CPU + FPU 世代、且带有一堆不同的——已知的——勘误(外加更多 NDA 勘误,甚至可能在勘误被正式记录之前就自己发现了 bug)的系统上 到底哪个心智正常的人会把勘误列为 NDA?那看起来是必然会让软件开发者的工作难上加难、别的什么也办不成的做法。
@Zir Blazer:
如果在 CPUID 时代之前,同一段代码需要运行在跨越多个 CPU + FPU 世代、且带有一堆不同的——已知的——勘误(外加更多 NDA 勘误,甚至可能在勘误被正式记录之前就自己发现了 bug)的系统上
到底哪个心智正常的人会把勘误列为 NDA?那看起来是必然会让软件开发者的工作难上加难、别的什么也办不成的做法。
- Zuxy 说:2025 年 4 月 5 日上午 6:38 你用什么工具反汇编这个 DLL 的?semblance(https://github.com/zfigura/semblance)只能反汇编导出函数,不能反汇编 IRQ handler。我这么问是因为近期的 Intel CPU 在 fsave 中不再保存 fds 和 fcs,导致这个 DLL 中出现通用保护错误,例如 https://www.reddit.com/r/windows98/comments/1380mz4/missing_a_autoexecnt_file_in_my_system32_folder/ 我很好奇这究竟是如何发生的,以及是否有变通办法。
你用什么工具反汇编这个 DLL 的?semblance(https://github.com/zfigura/semblance)只能反汇编导出函数,不能反汇编 IRQ handler。
我这么问是因为近期的 Intel CPU 在 fsave 中不再保存 fds 和 fcs,导致这个 DLL 中出现通用保护错误,例如 https://www.reddit.com/r/windows98/comments/1380mz4/missing_a_autoexecnt_file_in_my_system32_folder/ 我很好奇这究竟是如何发生的,以及是否有变通办法。
- Michal Necasek 说:2025 年 4 月 5 日晚上 8:45 我当然用的是 IDA Pro。
是的,有一个不完美的变通办法,但需要修改 WIN87EM.DLL,因为 Intel 简直把他们的硬件搞砸得太厉害了。我不知道他们当时在想什么……