
Z80 명령어를 디스어셈블하는 OpenType 글꼴
당신이 가장 좋아하는 디스어셈블러는 무엇인가요? 저는 폰트입니다:
https://github.com/user-attachments/assets/bb6ceb18-c2fd-40a9-be4f-202321a214d9
이 폰트는 OpenType의 Glyph Substitution Table (GSUB) 및 Glyph Positioning Table (GPOS)를 광범위하게 사용하여 16진수 소문자 시퀀스를 디스어셈블된 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 Sans는 다음과 같은 도전 과제가 있습니다:
CALL 같은 니모닉)을 결합한 글리프를 생성할 수 있지만, 이는 다음 사항과도 연결됩니다...65536 * 7 = 458752개의 조합을 가질 수 있음을 의미합니다;0x80..0xff 내의 모든 오프셋은 음의 2의 보수 숫자로 렌더링되어야 합니다;이 모든 것은 프로그래매틱 솔루션을 필요로 합니다. fontcustom과 ImageMagick이 글리프 생성을 처리하지만, 룩업 규칙을 작성하는 편리한 방법은 .fea 형식인 것 같지만, fonttools의 .ttx 형식(기본적으로 xml)과 통합하는 방법을 찾지 못했습니다. 저는 Noto Sans Mono의 .ttx를 직접 편집하는 최소 공통 분모 접근 방식을 취했습니다(글리프 모양은 Droid Sans Mono에서 계산되지만, FontForge에 패치를 적용할 때 시작한 것이기 때문입니다).
재귀 하향 파서를 사용하여 가능한 모든 글리프를 생성하며, 이는 인코딩에서 표현식을 평가하는 데 도움을 줍니다(예: SET b,(IX+o)는 비트와 변위를 사용하며 DD CB o C6+8*b로 인코딩됨). 그런 다음 이러한 인코딩을 피연산자가 가질 수 있는 모든 가능한 값으로 확장한 후, 확장된 명령어를 렌더링하는 데 필요한 각 디스어셈블 글리프에 하나 이상의 16진수 바이트를 연결합니다.
OpenType 기능에 대한 좋은 참고 자료가 있지만, 높은 수준에서 또는 .fea(?) 형식으로 작성되어 있습니다.
.ttx로 변환하는 방법이 명확하지 않아서 결국 Noto Sans 제품군 전체를 변환하고 "예제를 통한 학습"이라는 구식의 무차별 대입 방식을 사용했습니다. 이는 생각보다 더 재미있는데, .ttx에서 .ttf로 변환할 때 많은 무음 실패가 발생하기 때문입니다. 여기서 룩업은 fonttools에서 검증되지 않은 일부 가정(예: 컨텍스트 체인 대체를 위한 클래스 정의에는 class value="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가지 변형을 가지며, 이러한 순서가 다른 경우는 오프셋과 해당 특정 피연산자 중 하나만 관련된다는 것입니다. 따라서 비트나 레지스터를 리터럴로 인코딩할 수 있습니다. 충분한 룩어헤드를 사용하면 마지막 16진수 바이트까지 일치시키고 각 경우에 대한 전용 룩업을 생성할 수 있습니다. 마지막 리터럴은 접미사 글리프와 일치하는 합자를 생성하여 줄일 수 있습니다. 최종 결과는 이러한 경우에 대해 수십 개의 추가 룩업이 생성되었습니다(이는 그룹화하여 이 숫자를 줄일 수 있음).
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 Lesser General Public License 버전 3에 따라 배포됩니다;