9
\$\begingroup\$

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

Q1 Short

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. 21h becomes 33.
  • 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 ORG 256 directive.
  • 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 ret instruction.
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 '

Q2 Shorter

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  [293],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 db.
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.

db 191,219,001,190,043,001,186,041,001,185,044,000,176,036,253,170,172,212,010,004,048,170,136,224,212,010,005,048,048,170,136,224,170,184,044,009,226,233,205,033,195,100,098,032

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 '

Q4 Shortest

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

Summary

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.

And finally

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.

\$\endgroup\$
3
\$\begingroup\$

My equivalent to your nice quine is a bit nicer, and differs in other ways.

It is nicer in that the payload at the end of the source is split into several lines. It still uses over-wide lines because the indentation and the blanks before comments are repeated verbatim, and each line is prefixed with a db " directive (the prefix) that is itself indented.

It differs a bit in that I only escape any " as @, and don't use any literal @ characters in the program code or its comments. Instead, in the comments I refer to "code 40h characters", and in the code compare al to 40h. When displaying the payload, I scan for LFs (code 10) and wrap the individual lines (excluding the literal linebreak characters) in the prefix and suffix messages.

My program also uses interrupt 21h function 40h instead of your mix of functions 09h and 02h. This allows me to use dollar characters $ as literals in both the program code and the payload, which are needed to calculate string lengths in NASM without adding labels at the end of the strings.


I also evolved my nice quine (q.asm), first modifying only the payload to create halfqt.asm, and then running that to create the shorter qt.asm.

Like your Q1:

  • Dropped indentation, and most comments.

  • Used shorter number bases (int 33 etc).

Unlike your Q1:

  • Kept org 256 (and cpu 8086).

  • Kept using labels, though all just one letter now.

  • Kept same program logic, including the process termination call.

  • Kept the linebreak at the end of file.


Finally, I modified the program code (but not payload) of qt.asm to create the annotated variant. You can read this to learn in more detail about my decisions for the qt.asm variant.


Sizes:

  • 7535 q.asm

  • 3003 q.com

  • 4948 halfqt.asm

  • 838 halfqt.com

  • 2218 qt.asm

  • 838 qt.com

  • 4072 annotqt.asm

\$\endgroup\$
2
\$\begingroup\$

a. Shortest minus 1

The Q4 program contains a 2-byte xor dx,dx that you can rapidly replace by the 1-byte cwd instruction. Just bring that mov ax,484Eh on top. The positive number in AX will make cwd clear DX.

B8 4E 48   mov   ax,484Eh (*)
99         cwd
B2 FC      mov   dl,252
89 D7      mov   di,dx
D1 E8      shr   ax,1
89 45 21   mov   [di+33],ax
B8 64 62   mov   ax,6264h
AB         stosw
B8 20 26   mov   ax,2620h
FE C4      inc   ah
AB         stosw
80 C4 E2   add   ah,226
CD 21      int   21h
C3         ret

(*) I've had to correct a typo! You erroneously wrote 487Eh.


b. Shortest minus 3

When DOS starts a .COM program the general purpose registers have a certain value and you can take advantage of this fact.

Please note that the values that these general purpose registers have when the program is loaded by DOS are not officially documented. I myself would certainly never rely on it for any serious program, but since this Quine project is almost always some kind of challenge (even though you say that it is not!), I believe this is a genuine opportunity to shorten the code.

Here's the list (DX equals CS=DS=ES=SS):

AX=0000  BX=0000  CX=00FF  SI=0100  DI=FFFE  BP=091C

This is also true for emulators like DOSBox 0.74 and vDOS 2016.10.01. They show the exact same numbers!

This is how I would write your Q4 program and bring it down to just 27 bytes:

01 F7      add   di,si      ;This sets DI=254
FD         std
B8 40 4E   mov   ax,4E40h
D1 E8      shr   ax,1
AB         stosw            ;Space and SingleQuote
89 FA      mov   dx,di      ;Here DX=252
B8 64 62   mov   ax,6264h
AB         stosw            ;Characters d and b
B8 4E 48   mov   ax,484Eh
D1 E8      shr   ax,1
89 45 21   mov   [di+33],ax ;SingleQuote and DollarSign (*)
95         xchg  ax,bp      ;This sets AH=09h
CD 21      int   21h
C3         ret

(*) +33 is because DI points to 6 bytes before a program of 27 bytes.

\$\endgroup\$
  • 1
    \$\begingroup\$ It is an annoyingly persistent myth that non-segment registers other than IP and SP have particular values at startup. I wrote and published this assembly language program 24 years ago to demonstrate that very point. \$\endgroup\$ – Edward Dec 12 '19 at 13:55
  • 1
    \$\begingroup\$ @Edward I'm well aware of the fact that these general purpose registers don't have a well defined documented value at startup. I would certainly never rely on it for any serious program, but since this Quine business is almost always some kind of challenge, I believe this to be a valid exception. As a side note, I'm happy to see that the values that you got from your program back then, seem not too different from those that I got from 3 different execution environments. \$\endgroup\$ – Fifoernik Dec 12 '19 at 15:59
  • \$\begingroup\$ I'm glad to hear that. I'm hoping you see that your first sentence in that comment and the first sentence under "shortest minus 3" are contradictory. Please clarify that part of your answer to avoid misleading others who might not be as experienced as you. \$\endgroup\$ – Edward Dec 12 '19 at 16:26
  • 1
    \$\begingroup\$ @Edward Added a short comment. Thank you for drawing my attention to it. \$\endgroup\$ – Fifoernik Dec 13 '19 at 15:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.