A quine is a program whose only job it is to reproduce the source file that was used to create the executable in the first place.
This stackoverflow post provides some reasoning about its usefulness.
Below I present 5 different quines.
The nice quine
This program has an embedded copy of its complete source (minus that copy of course).
For simplicity I've substituted an asterisk for the carriage return and linefeed codes. I've also avoided the use of embedded dollar characters ($) and single quote characters (') because they would throw off the DOS PrintString function and the FASM parser respectively.
org 256 mov ah, 02h ; DOS.PrintChar mov si, text lodsb next: mov dl, al cmp al, 42 ; Asterisk -> CRLF jne char mov dl, 13 int 21h mov dl, 10 char: int 21h lodsb cmp al, 36 ; Dollar jne next mov dl, 39 ; SingleQuote int 21h mov dx, text mov ah, 09h ; DOS.PrintString int 21h mov ah, 02h ; DOS.PrintChar mov dl, 36 ; Dollar int 21h mov dl, 39 ; SingleQuote int 21h mov dl, 13 ; CR int 21h mov dl, 10 ; LF int 21h mov ax, 4C00h ; DOS.Terminate int 21h text: db ' org 256** mov ah, 02h ; DOS.PrintChar* mov si, text* lodsb*next: mov dl, al* cmp al, 42 ; Asterisk -> CRLF* jne char* mov dl, 13* int 21h* mov dl, 10*char: int 21h* lodsb* cmp al, 36 ; Dollar* jne next* mov dl, 39 ; SingleQuote* int 21h* mov dx, text* mov ah, 09h ; DOS.PrintString* int 21h* mov ah, 02h ; DOS.PrintChar* mov dl, 36 ; Dollar* int 21h* mov dl, 39 ; SingleQuote* int 21h* mov dl, 13 ; CR* int 21h* mov dl, 10 ; LF* int 21h* mov ax, 4C00h ; DOS.Terminate* int 21h**text: db $'
Entering challenge mode, but not really participating one
It was this codegolf post that I found, that triggered me to investigate if I could write a very small quine.
I started by removing from the above program everything that wasn't crucial for it to be assembled correctly.
- I removed all of the indentation, the optional whitespace, and the tail comments.
- I chose the number representation that was shortest. e.g.
- As long as there are no labels for which FASM has to know the origin, there's no need for this .COM program to start with an
- I stopped using labels. I wrote the address instead and because FASM at the time of compilation now thinks that the program runs at address 0 these are very short numbers.
- Instead of processing the text string in 2 different ways using 2 different DOS functions, I now traverse the string character by character and do it twice.
- I dismissed the carriage return and linefeed codes for the very last line of the program because FASM can do without them nicely.
- Provided the stack is untampered with, a .COM program can end with a mere
mov ah,2 mov dh,42 call 7 mov cx,150 mov si,292 mov dl,[si] inc si cmp dl,dh jne 26 mov dl,13 int 33 mov dl,10 int 33 loop 13 mov dx,39 int 33 ret db 'mov ah,2*mov dh,42*call 7*mov cx,150*mov si,292*mov dl,[si]*inc si*cmp dl,dh*jne 26*mov dl,13*int 33*mov dl,10*int 33*loop 13*mov dx,39*int 33*ret*db '
At some point an assembly programmer might come up with the idea to assemble the program from a series of
db directives. That's reminiscent of the old days when programmers punched-in numbers directly instead of using nice mnemonics.
I've tried several versions but I found the hexadecimal dump to be shorter than the decimal dump.
To mark the end of a line, FASM only requires the linefeed code. The carriage return code is optional and so I've left it out. Without the carriage returns everything in the file looks out of place on the screen. Considering what was shaved off, that ugliness was but a small price to pay.
db BEh db 00h db 01h db BAh db 22h db 01h db B9h db 2Ah db 00h db ACh db D4h db 10h db 3Ch db 0Ah db 1Ch db 69h db 2Fh db 86h db C4h db 3Ch db 0Ah db 1Ch db 69h db 2Fh db A3h db 25h db 01h db B4h db 09h db CDh db 21h db E2h db E8h db C3h db 64h db 62h db 20h db 32h db 32h db 68h db 0Ah db 24h
The equivalent program:
mov si,256 mov dx,290 mov cx,42 lodsb aam 16 cmp al,10 sbb al,69h das xchg al,ah cmp al,10 sbb al,69h das mov ,ax mov ah,09h int 21h loop 9 ret db 'db 22h',10,'$'
Q3 Again shorter (judged by source length)
I really should have put all of those numbers in a single
This time the decimal version proved to be the shorter one. And just for the fun of it, I've iterated it backwards. Efforts to remove the redundant leading zeroes amounted to a longer quine, so no.
The equivalent program:
mov di,475 mov si,299 mov dx,297 mov cx,44 mov al,'$' std stosb lodsb aam add al,'0' stosb mov al,ah aam add ax,'00' stosb mov al,ah stosb mov ax,092Ch loop 15 int 21h ret db 'db '
Still putting the whole program in a single
db, but using a quote, thereby avoiding the need for a conversion routine.
This required a lot of puzzling but it worked and produced a quine of just 30 bytes. By carefully choosing the instructions, I could avoid using character codes that the editor would have trouble displaying. The Norton Editor chokes on character codes [0,31] and 255.
db '1Ҳ�NH��E"�db�� &�ī����!�'
The character codes involved:
31 D2 B2 FC 89 D7 B8 4E 48 D1 E8 89 45 22 B8 64 62 AB B8 20 26 FE C4 AB 80 C4 E2 CD 21 C3
The equivalent program:
xor dx,dx mov dl,252 mov di,dx mov ax,487Eh shr ax,1 mov [di+34],ax mov ax,6264h stosw mov ax,2620h inc ah stosw add ah,226 int 21h ret
Next table shows how the quines' sourcefiles gradually became smaller.
Q0 Q1 Q2 Q3 Q4 ------------------------------ .ASM 1837 319 294 178 35 .COM 960 186 42 44 30
Every program was tested using FASM 1.01 in MS-DOS 6.20
C:\FASM1>fasm q4.asm q4.com
flat assembler version 1.0
1 passes, 30 bytes.
C:\FASM1>q4 > q4_.asm
C:\FASM1>fc q4.asm q4_.asm
Comparaison des fichiers en cours : Q4.ASM et Q4_.ASM
FC: aucune différence trouvée
1In case you wonder why I use an old version of FASM. I'm checking out FASM 1.0 in preparation for the upcoming celebration of the 20th anniversary of the first official release.
Because this is Code Review, you are invited to suggest any improvements that I can make to any or all of these 5 little programs.
I leave it up to you to decide if a smaller quine should refer to a smaller source file or to a smaller executable file.