- PVSM.RU - https://www.pvsm.ru -
Копаясь в проектах-победителях IOCCC [1], неожиданно наткнулся на самый маленький эмулятор x86 [2] архитектуры на свете — 4043 байт!
Эта работа [3] — победитель The International Obfuscated C Code Contest (IOCCC) [4] от 2013 года, за авторством Adrian Cable [5]:
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 …
И.. все это правда:
Напоминаю, что весь исходный код эмулятора это лишь 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))
И нет, даже пытаться не буду это разбирать.
На самом деле все уже давно разобрали и тут [6] лежит читаемая и детально откомментированная версия, если вдруг интересно как это работает.
Еще тут [7] лежит образ диска в 40Мб, на котором и установлен весь софт из демо, включая Windows 3.0, которая показана в работе на заглавном скриншоте статьи.
Как это ни странно, но оригинальный проект 2013 года, с обфрускацией для IOCCC собирается и запускается без каких-либо проблем до сих пор:

Сборка производилась на FreeBSD 14, стандартным BSD-шным make.
Запуск осуществляется с помощью скрипта runme, по-умолчанию внутри запускается современный FreeDOS (!)
Но к сожалению обновленная и «осовремененная» версия [6] уже падает при сборке с ошибкой:
ld: error: undefined symbol: ftime
>>> referenced by 8086tiny.c
Происходит это потому, что вызов ftime успел устареть [8]:
This interface is obsoleted by gettimeofday(2).
а его использование ныне требует включения специального ключа -lcompat
Который я и добавил в Makefile:

И.. немедленно обломался, потому что в FreeBSD версии 14.2 и новее внесли такое [9] замечательное исправление:
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:

Либо полностью отказываться от использования устаревшего ftime в коде, что конечно предпочтительно, но актуально это лишь для FreeBSD и например в MacOS никаких проблем с ftime нет.
В корне проекта лежит файл 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 его помощью.
Врядли вы знали что такое [10] на свете вообще существует (автор не знал):
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го это бекпорт.
В качестве иллюстрации, фотография машины [11] одного со мной возраста, с работающим ELKS:

Не стал заморачиваться сборкой ELKS из исходников (про нее все равно будет отдельная статья) и просто взял готовый образ [12] 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).
Вот так это выглядит в работе:

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

К сожалению ни один из найденных образов дисков QNX у меня с этим патчем так и не запустился, если кто‑то из читателей сможет запустить — дайте знать.
P.S.
Это переработанная и обновленная версия статьи прошлого года, оригинал [15] которой доступен в нашем блоге.
Мы небольшая команда ветеранов ИТ‑индустрии, создаем и дорабатываем самое разнообразное программное обеспечение, наш софт автоматизирует бизнес‑процессы на трех континентах, в самых разных отраслях и условиях.
Оживляем давно умершее [16], чиним никогда не работавшее [17] и создаем невозможное [18] — затем рассказываем об этом в своих статьях.
Автор: alex0x08
Источник [19]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/freebsd/412881
Ссылки в тексте:
[1] IOCCC: https://www.ioccc.org/authors.html
[2] самый маленький эмулятор x86: https://www.ioccc.org/2013/cable3/index.html
[3] работа: https://www.ioccc.org/years.html#2013
[4] The International Obfuscated C Code Contest (IOCCC): https://www.ioccc.org/
[5] Adrian Cable: http://adrian.cable@gmail.com
[6] тут: https://github.com/adriancable/8086tiny
[7] тут: http://bitly.com/1bU8URK
[8] успел устареть: https://man.freebsd.org/cgi/man.cgi?query=ftime&sektion=3&manpath=freebsd-release-ports
[9] такое: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=257789
[10] такое: https://github.com/ghaerr/elks
[11] машины: https://en.wikipedia.org/wiki/Olivetti_M24
[12] готовый образ: https://github.com/ghaerr/elks/releases
[13] интересный патч: https://github.com/retrohun/8086tiny/commit/20c3a450d5b2467aca47c56e54011713ff4e6ee0#diff-333d5cdcd3be233e87dc8156e01dcea1
[14] neozeed: https://virtuallyfun.com/2018/12/03/8086tiny-bios-patch-update/
[15] оригинал: https://blog.0x08.ru/8086tiny-smallest-emulator-ever
[16] давно умершее: https://blog.0x08.ru/xerox-alto-bcpl-hello-world
[17] чиним никогда не работавшее: https://blog.0x08.ru/running-from-websphere-esb
[18] невозможное: https://blog.0x08.ru/call-webservice-from-dos
[19] Источник: https://habr.com/ru/articles/888812/?utm_campaign=888812&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.