你最喜欢的反汇编器是什么?我喜欢的是一个字体:
https://github.com/user-attachments/assets/bb6ceb18-c2fd-40a9-be4f-202321a214d9
这个字体通过大量使用 OpenType 的 字形替换表(GSUB) 和 字形定位表(GPOS),将十六进制小写字符序列转换为反汇编后的 Z80 指令。
如果你只是想试试,可以在 ./test/z80-sans.ttf 找到一份副本。
在 Debian GNU/Linux 12 上测试通过。注意,该 Debian 版本自带 ruby 版本 3,而 fontcustom 是为 ruby 版本 2 编写的,与更高版本不兼容(例如语法错误)。ruby 安装还需要兼容的 OpenSSL 版本。因此,可以使用 RVM 来管理 ruby 和本地安装的 OpenSSL。
apt install imagemagick potrace
pip install fonttools
git submodule update --init --recursive
# fontforge
(
cd ./modules/fontforge/
git checkout 4f4907d9541857b135bd0b361099e778325b4e28
git apply ../../resources/fontforge.diff
mkdir -p build
cd build
cmake -GNinja ..
ninja
ninja install
)
# woff2
(
cd ./modules/woff2/
make clean all
)
# fontcustom
rvm use 2.7
rvm pkg install openssl
rvm install 2.4 --with-openssl-dir=$HOME/.rvm/usr
gem update --system 3.3.22
(
export PATH=$PWD/modules/woff2/build:$PATH
cd ./modules/fontcustom/
git apply ../../resources/fontcustom.diff
gem build fontcustom.gemspec
gem install ./fontcustom-2.0.0.gem
)
cp ./resources/droid-sans-mono.ttf /tmp/base.ttf
./gen.py ./resources/instructions.json
生成的 .ttf 字体文件会复制到 ~/.local/share/fonts/,该目录被 LibreOffice 等软件使用。
与其他“诅咒字体”相比,Z80 无衬线面临以下挑战:
CALL 等助记符),但这又引出了下一点……65536 * 7 = 458752 种可能组合;0x80..0xff 内的所有偏移都需要渲染为负的补码数;这一切都呼唤一种程序化解决方案。虽然 fontcustom 和 ImageMagick 负责生成字形,但编写查找规则的一种方便格式似乎是 .fea,不过我找不到将其与 fonttools 的 .ttx 格式(基本上是 xml)集成的方法。我采用了最低公分母的方法,直接编辑 Noto Sans Mono 的 .ttx(尽管字形形状是从 Droid Sans Mono 计算得出的,因为修补 FontForge 时我从它开始)。
使用递归下降解析器生成所有可能的字形,这有助于评估编码中的表达式(例如 SET b,(IX+o) 接受一个位和一个偏移量,编码为表达式 DD CB o C6+8*b)。然后将这些编码展开为操作数可能取的所有值,最后将 1 个或多个十六进制字节关联到每个扩展指令所需的反汇编字形。
关于 OpenType 特性有一些不错的参考资料,但它们通常写得比较高层,或是 .fea 格式:
如何将它们转换为 .ttx 总是不太清楚,所以最后我直接转换了整个 Noto Sans 系列,并使用了老式的“通过例子学习”的暴力方法。这比听起来更有趣,因为从 .ttx 转换为 .ttf 时会有大量无声的失败,查找规则由于某些未被 fonttools 验证的假设(例如上下文链式替换的类定义必须至少有一个覆盖字形且 class 值为 "1")而无法匹配。
大多数挑战都是通过上下文链式规则解决的。为了处理地址,每个在 0..f 范围内的半字节都用不同的字形编码,并使用间距字符来逐字符创建多个替换。位移还有额外的有符号变体。这使数字的总字形数达到 (4 + 2) * 16 个。这样已经足够将字体文件保持在 65536 个字形的限制以下。
最糟糕的部分当然是操作数乱序。不过,由于它们在指令中的变体数量有限,可以通过与编码前缀模糊的指令相同的策略来处理,例如
["SET b,(IX+o)", "DD CB o C6+8*b"],
["SET b,(IY+o)", "FD CB o C6+8*b"],
可以由与以下指令相同的查找规则覆盖:
["SRA (IX+o)", "DD CB o 2E"],
["SRA (IY+o)", "FD CB o 2E"],
["SRL (IX+o)", "DD CB o 3E"],
["SRL (IY+o)", "FD CB o 3E"],
Z80 ISA 中一个有趣的特性是位和寄存器最多有 8 种变体,而这些乱序情况只涉及偏移量和这些特定操作数之一。因此,我们可以将位或寄存器编码为字面量。通过足够的向前查找,我们可以匹配到最后一个十六进制字节,并为每种情况创建专门的查找规则。最后的字面量可以通过生成一个匹配后缀字形的连字来减少。最终结果是为这些情况额外生成了几十个查找规则(可能可以分组以减少数量)。
LD (IX+o),r 渲染为 LD (IX+o r),;SET b,(IX+o) 渲染为 SET b,(IX+o));FontForge 支持使用命令 GenerateFeatureFile() 和 MergeFeature() 以脚本方式修改特性(在 The Terrible Secret of OpenType Glyph Substitution - Ansuz - mskala's home page 中简要介绍)。在完成基于 .ttx 的实现后我才知道它,但它可能可以避免摆弄 .ttx 文件。
对于更复杂的指令集,另一种约束较少的方法是使用字体整形器。一些例子:
./resources/instructions.json 改编自 maziac/z80-instruction-set;./resources/instructions.json 采用 GNU 较宽松通用公共许可证第3版;