Самый маленький эмулятор x86

в 9:12, , рубрики: freebsd, IOCCC, системное программирование, эмулятор

Копаясь в проектах-победителях IOCCC, неожиданно наткнулся на самый маленький эмулятор x86 архитектуры на свете — 4043 байт!

Внутри самый настоящий Windows 3.0, как видите даже с графикой (!) Справа в редакторе - исходный код. Весь исходный код.

Внутри самый настоящий Windows 3.0, как видите даже с графикой (!) Справа в редакторе — исходный код. Весь исходный код.

Largest small system emulator

Эта работа — победитель The International Obfuscated C Code Contest (IOCCC) от 2013 года, за авторством Adrian Cable:

This entry weighs in at a magical 4043 bytes (8086 nibbles, 28,301 bits). It manages to implement most of the hardware in a 1980’s era IBM-PC using a few hundred fewer bits than the total number of transistors used to implement the original 8086 CPU.

Про IOCCC уже неоднократно упоминал, поскольку этот конкурс — бесконечный источник вдохновения для программистов, а публикуемые работы — верх инженерного мастерства.

Но вернемся к эмулятору, вот как его описывает автор:

The author hereby presents, for the delectation (?) of the judges, a portable PC emulator/VM written specifically for the IOCCC which runs DOS, Windows 3.0, Excel, MS Flight Simulator, AutoCAD, Lotus 1-2-3 …

И.. все это правда:

Sim CIty (а вы знали что это игра корнями из 80х?)
Sim CIty (а вы знали что это игра корнями из 80х?)
QBasic, достаточно свежий

QBasic, достаточно свежий
Microsoft Flight Simulator

Microsoft Flight Simulator
Microsoft Excel, узнали?

Microsoft Excel, узнали?

Напоминаю, что весь исходный код эмулятора это лишь 4043 байт на С (29 строк).

Вот список эмулируемого железа:

  • Intel 8086/186 CPU

  • 1MB RAM

  • 8072A 3.5" floppy disk controller (1.44MB/720KB)

  • Fixed disk controller (supports a single hard drive up to 528MB)

  • Hercules graphics card with 720x348 2-color graphics (64KB video RAM), and CGA 80x25 16-color text mode support

  • 8253 programmable interval timer (PIT)

  • 8259 programmable interrupt controller (PIC)

  • 8042 keyboard controller with 83-key XT-style keyboard

  • MC146818 real-time clock

  • PC speaker

Поддержка FreeBSD разумеется не заявлена, но все собирается и работает:

The emulator uses the SDL graphics library for portability, and compiles for Windows, Mac OS X, Linux and probably most other 32-bit/64-bit systems too.

Вот так выглядит полный исходный код эмулятора:

#include "SDL.h"

#define $ for(O=9
#define CX M+=(T%3+2*!(!T*t-6))
#define x ,A=4*!T,O=t,W=h=T<3?u(Q?p:D(A+3),D(A),D(A+1)[i]+D(A+2)*g+):K(t),U=V=K(a),o?U=h,W=V:V,
#define C 8*-~L
#define Z short
#define y a(Z)Y[++O]
#define B ),a--||(
#define _ ),e--||(

#define V(I,D,E)(O=a(I)h[r])&&!(A=(D)(V=(1[E+L]<<16)+*i)/O,A-(I)A)?1[E+L]=V-O*(*E=A):H(0)
#define i(B,M)B(o){return M;}
#define R(O,M,_)(S=L?a(I Z)O:O,N=L?a(I Z)O M(f=a(I Z)_):(O M(f=a(I n)_)))
#define T(_)R(r[u(10,L=4,--)],=,_)
#define u(a,r,T)16*i[a]+(I Z)(T i[r])
#define a(_)*(_*)&
#define L(_)M(W,_,U)

#define M(S,F,T)R(r[S],F,r[T])
#define A(_)(i[L=4]+=2,R(_,=,r[u(10,4,-2+)]))
#define c(R,T)(1[u=19,L+T]=(N=a(R)h[r]*(R)*T)>>16,*i=N,G(F(N-(R)N)))
#define h(_)(1&(L?a(Z)_:_)>>C-1)
#define I unsigned
#define n char
#define e(_)v(F(40[L(_##=40[E]+),E]&N==S|_ N<_(int)S))

I n t,e,l[80186],*E,m,u,L,a,T,o,r[1<<21],X,*Y,b,Q,R;I Z*i,M,p,q=3;I*localtime(),f,S,kb,h,W,U,c,g,d,V,A;N,O,P=983040,j[5];SDL_Surface*k;i(F,40[E]=!!o)i(z,42[E]=!!o)i(D,r[a(I)E[259+4*o]+O])i(w,i[o]+=~(-2*47[E])*~L)i(v,G(N-S&&1&(40[z((f^=S^N)&16),E]^f>>C-1)))J(){V=61442;$;O--;)V+=40[E+O]<<D(25);}i(H,(46[u=76,J(),T(V),T(9[i]),T(M),M(P+18,=,4*o+2),R(M,=,r[4*o]),E]=0))s(o){$;O--;)40[E+O]=1&&1<<D(25)&o;}i(BP,(*i+=262*o*z(F((*E&15)>9|42[E])),*E&=15))i(SP,(w(7),R&&--1[i]&&o?R++,Q&&Q++,M--:0))DX(){$,O*=27840;O--;)O[(I*)k->pixels]=-!!(1<<7-O%8&r[O/2880*90+O%720/8+(88+952[l]/128*4+O/720%4<<13)]);SDL_Flip(k);}main(BX,nE)n**nE;{9[i=E=r+P]=P>>4;$;q;)j[--q]=*++nE?open(*nE,32898):0;read(2[a(I)*i=*j?lseek(*j,0,2)>>9:0,j],E+(M=256),P);$;Y=r+16*9[i]+M,Y-r;Q|R||kb&46[E]&&KB)--64[T=1[O=32[L=(X=*Y&7)&1,o=X/2&1,l]=0,t=(c=y)&7,a=c/8&7,Y]>>6,g=~-T?y:(n)y,d=BX=y,l],!T*t-6&&T-2?T-1?d=g:0:(d=y),Q&&Q--,R&&R--x(O=*Y,O=u=D(51),e=D(8),m=D(14)_ O=*Y/2&7,M+=(n)c*(L^(D(m)[E]|D(22)[E]|D(23)[E]^D(24)[E]))_ L=*Y&8,R(K(X)[r],=,c)_ L=e+=3,o=0,a=X x a=m _ T(X[i])_ A(X[i])_ a<2?M(U,+=1-2*a+,P+24),v(f=1),G(S+1-a==1<<C-1),u=u&4?19:57:a-6?CX+2,a-3||T(9[i]),a&2&&T(M),a&1&&M(P+18,=,U+2),R(M,=,U[r]),u=67:T(h[r])_(W=U B u=m,M-=~L,R(W[r],&,d)B 0 B L(=~)B L(=-),S=0,u=22,F(N>S)B L?c(I Z,i):c(I n,E)B/**/L?c(Z,i):c(n,E)B L?V(I Z,I,i):V(I n,I Z,E)B L?V(Z,int,i):V(n,Z,E))_++e,h=P,d=c,T=3,a=m,M--_++e,13[W=h,i]=(o|=!L)?(n)d:d,U=P+26,M-=~!o,u=17+(m=a)_(a=m B L(+=),F(N<S)B L(|=)B e(+)B e(-)B L(&=)B L(-=),F(N>S)B L(^=)B L(-),F(N>S)B L(=))_!L?L=a+=8 x L(=):!o?Q=1,R(r[p=m x V],=,h):A(h[r])_ T=a=0,t=6,g=c x M(U,=,W)_(A=h(h[r]),V=m?++M,(n)g:o?31&2[E]:1)&&(a<4?V%=a/2+C,R(A,=,h[r]):0,a&1?R(h[r],>>=,V):R(h[r],<<=,V),a>3?u=19:0,a<5?0:F(S>>V-1&1)B R(h[r],+=,A>>C-V),G(h(N)^F(N&1))B A&=(1<<V)-1,R(h[r],+=,A<<C-V),G(h(N*2)^F(h(N)))B R(h[r],+=(40[E]<<V-1)+,A>>1+C-V),G(h(N)^F(A&1<<C-V))B R(h[r],+=(40[E]<<C-V)+,A<<1+C-V),F(A&1<<V-1),G(h(N)^h(N*2))B G(h(N)^F(h(S<<V-1)))B G(h(S))B 0 B V<C||F(A),G(0),R(h[r],+=,A*=~((1<<C)-1>>V)))_(V=!!--1[a=X,i]B V&=!m[E]B V&=m[E]B 0 B V=!++1[i]),M+=V*(n)c _ M+=3-o,L?0:o?9[M=0,i]=BX:T(M),M+=o*L?(n)c:c _ M(U,&,W)_ L=e+=8,W=P,U=K(X)_!R||1[i]?M(m<2?u(8,7,):P,=,m&1?P:u(Q?p:11,6,)),m&1||w(6),m&2||SP(1):0 _!R||1[i]?M(m?P:u(Q?p:11,6,),-,u(8,7,)),43[u=92,E]=!N,F(N>S),m||w(6),SP(!N==b):0 _ o=L,A(M),m&&A(9[i]),m&2?s(A(V)):o||(4[i]+=c)_ R(U[r],=,d)_ 986[l]^=9,R(*E,=,l[m?2[i]:(n)c])_ R(l[m?2[i]:(n)c],=,*E)_ R=2,b=L,Q&&Q++_ W-U?L(^=),M(U,^=,W),L(^=):0 _ T(m[i])_ A(m[i])_ Q=2,p=m,R&&R++_ L=0,O=*E,F(D(m+=3*42[E]+6*40[E])),z(D(1+m)),N=*E=D(m-1)_ N=BP(m-1)_ 1[E]=-h(*E)_ 2[i]=-h(*i)_ 9[T(9[i]),T(M+5),i]=BX,M=c _ J(),T(V)_ s(A(V))_ J(),s((V&~m)+1[E])_ J(),1[E]=V _ L=o=1 x L(=),M(P+m,=,h+2)_++M,H(3)_ M+=2,H(c&m)_++M,m[E]&&H(4)_(c&=m)?1[E]=*E/c,N=*E%=c:H(0)_*i=N=m&E[L=0]+c*1[E]_*E=-m[E]_*E=r[u(Q?p:m,3,*E+)]_ m[E]^=1 _ E[m/2]=m&1 _ R(*E,&,c)_(a=c B write(1,E,1)B time(j+3),memcpy(r+u(8,3,),localtime(j+3),m)),a<2?*E=~lseek(O=4[E][j],a(I)5[i]<<9,0)?((I(*)())(a?write:read))(O,r+u(8,3,),*i):0:0),O=u,D(16)?v(0):D(17)&&G(F(0)),CX*D(20)+D(18)-D(19)*~!!L,D(15)?O=m=N,41[43[44[E]=h(N),E]=!N,E]=D(50):0,!++q?kb=1,*l?SDL_PumpEvents(),k=k?k:SDL_SetVideoMode(720,348,32,0),DX():k?SDL_Quit(),k=0:0:0;}i(G,48[E]=o)i(K,P+(L?2*o:2*o+o/4&7))

И нет, даже пытаться не буду это разбирать.

На самом деле все уже давно разобрали и тут лежит читаемая и детально откомментированная версия, если вдруг интересно как это работает.

Еще тут лежит образ диска в 40Мб, на котором и установлен весь софт из демо, включая Windows 3.0, которая показана в работе на заглавном скриншоте статьи.

Сборка

Как это ни странно, но оригинальный проект 2013 года, с обфрускацией для IOCCC собирается и запускается без каких-либо проблем до сих пор:

Самый маленький эмулятор x86 - 6

Сборка производилась на FreeBSD 14, стандартным BSD-шным make.

Запуск осуществляется с помощью скрипта runme, по-умолчанию внутри запускается современный FreeDOS (!)

Но к сожалению обновленная и «осовремененная» версия уже падает при сборке с ошибкой:

ld: error: undefined symbol: ftime
>>> referenced by 8086tiny.c

Происходит это потому, что вызов ftime успел устареть:

 This interface is obsoleted by gettimeofday(2).

а его использование ныне требует включения специального ключа -lcompat

Который я и добавил в Makefile:

Самый маленький эмулятор x86 - 7

И.. немедленно обломался, потому что в FreeBSD версии 14.2 и новее внесли такое замечательное исправление:

 lib/libcompat/Makefile                 | 7 +------
 lib/libutil/Makefile                   | 6 +++---
 lib/{libcompat/4.1 => libutil}/ftime.3 | 4 ++--
 lib/{libcompat/4.1 => libutil}/ftime.c | 2 ++
 sys/sys/timeb.h                        | 2 +-
 5 files changed, 9 insertions(+), 12 deletions(-)

Так что вместо lcompat теперь необходимо использовать -lutil:

Самый маленький эмулятор x86 - 8

Либо полностью отказываться от использования устаревшего ftime в коде, что конечно предпочтительно, но актуально это лишь для FreeBSD и например в MacOS никаких проблем с ftime нет.

BIOS

В корне проекта лежит файл bios — собранный и готовый к использованию образ биоса, исходник которого находится в каталоге bios_source:

Like a real PC, the emulator needs a BIOS to do anything useful. Here we use a custom BIOS, written from scratch specifically for the emulator

Собирается биос с помощью nasm:

nasm bios.asm

Самое интересное, связанное с эмуляцией оборудования это (наверное) работа с клавиатурой:

The emulator simulates an XT-style keyboard controlled by an Intel 8042 chip on I/O port 0x60, generating IRQ1 and then interrupt 9 on each keypress. This is harder than it sounds because a real 8042 returns scan codes rather than the ASCII characters which the C standard I/O functions return. Rather than make the emulator less portable and use ioctl or platform-dependent equivalents to obtain real scan codes from the keyboard, the emulator BIOS does the reverse of a real PC BIOS and converts ASCII characters to scancodes, simulating press/release of the modifier keys (e.g. shift) as necessary to work like a “real” keyboard. The OS (DOS/Windows) then converts them back to ASCII characters and normally this process works seamlessly (although don’t be surprised if there are issues, for example, with non-QWERTY e.g. international keyboards).

Классический «грязный хак», из палаты мер и весов, в лучших традициях IOCCC.

В качестве иллюстрации возможностей столь миниатюрного эмулятора, покажу что еще можно запустить c его помощью.

ELKS

Врядли вы знали что такое на свете вообще существует (автор не знал):

ELKS is a project providing a Linux-like OS for systems based on the Intel IA16 architecture (16-bit processors: 8086, 8088, 80188, 80186, 80286, NEC V20, V30 and compatibles). Such systems are ancient computers (IBM-PC XT / AT and clones) as well as more recent SBCs, SoCs, and FPGAs. ELKS supports networking and installation to HDD using both MINIX and FAT file systems.

Официальная история Linux начинается с 386, на котором Торвальдс и начинал разработку, поэтому поддержка всего что проще 386го это бекпорт.

В качестве иллюстрации, фотография машины одного со мной возраста, с работающим ELKS:

Самый маленький эмулятор x86 - 9

Не стал заморачиваться сборкой ELKS из исходников (про нее все равно будет отдельная статья) и просто взял готовый образ 1.44 дискеты:

Emulates a 3.5" high-density floppy drive. Can read, write and format 1.44MB disks (18 sectors per track, 2 heads) and 720KB disks (9 sectors per track, 2 heads).

Вот так это выглядит в работе:

Самый маленький эмулятор x86 - 10

QNX2

Существует интересный патч от небезизвестного neozeed, с которым можно запускать QNX2 в этом маленьком эмуляторе:

Самый маленький эмулятор x86 - 11

К сожалению ни один из найденных образов дисков QNX у меня с этим патчем так и не запустился, если кто‑то из читателей сможет запустить — дайте знать.

P.S.

Это переработанная и обновленная версия статьи прошлого года, оригинал которой доступен в нашем блоге.

0x08 Software

Мы небольшая команда ветеранов ИТ‑индустрии, создаем и дорабатываем самое разнообразное программное обеспечение, наш софт автоматизирует бизнес‑процессы на трех континентах, в самых разных отраслях и условиях.

Оживляем давно умершеечиним никогда не работавшее и создаем невозможное — затем рассказываем об этом в своих статьях.

Автор: alex0x08

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js