Disassembly of Farbrausch's "fr-016: bytes" =========================================== In 2001, Farbrausch released a [graphics demo that was 16 bytes long](http://www.pouet.net/prod.php?which=4766), called "fr-016: bytes". Here's my disassembly of it: kragen@thrifty:~/pkgs/fr-016$ objdump -m i8086 -b binary -D fr-016.com fr-016.com: file format binary Disassembly of section .data: 0000000000000000 <.data>: 0: b0 13 mov $0x13,%al 2: cd 10 int $0x10 4: c4 2f les (%bx),%bp 6: aa stos %al,%es:(%di) 7: 11 f8 adc %di,%ax 9: 64 13 06 6c 04 adc %fs:1132,%ax e: eb f6 jmp 0x6 (Don't try to disassemble it in 386 mode; the results will be mysteriously wrong.) It runs in DOSBOX successfully and draws a pretty awesome animated pattern, but in QEMU the pattern draws successfully but is static. (Until I twiddled memory with GDB; see below.) Here's the pattern, blown up from 320x200 to 640x400: It's a little bit mysterious to me how this works. In particular, the `fs:` prefix on the `adc` (hex `64`) looks suspicious (the default segment would be `%ds` which has the same value as `%fs` by default, `0x22e4` in FreeDOS running in QEMU). Probing and disassembling it in QEMU with GDB --------------------------------------------- In order to understand it better, I ran the program inside QEMU, IIRC more or less as follows: kragen@thrifty:~/pkgs/fr-016$ dd if=/dev/zero bs=1k count=1440 of=diskimage kragen@thrifty:~/pkgs/fr-016$ mkfs -t msdos diskimage kragen@thrifty:~/pkgs/fr-016$ mkdir mnt kragen@thrifty:~/pkgs/fr-016$ sudo mount -o loop diskimage mnt kragen@thrifty:~/pkgs/fr-016$ sudo cp fr-016.com mnt kragen@thrifty:~/pkgs/fr-016$ sudo umount mnt kragen@thrifty:~/pkgs/fr-016$ qemu -snapshot -fda diskimage \ ~/devel/qemu/freedos.qcow2 (and inside QEMU:) C:\> A: A:\> fr-016 (It would have been a lot easier to use `qemu -fda fat:floppy:.` instead of making the disk image. Oh well.) Then, while it was still running (easy, since there's no way to exit it as far as I can tell) I ran the `gdbserver` command in the QEMU console (ctrl-alt-2). Then I attached GDB to the running QEMU to see what's going on. Here's a transcript of starting this out, minus all the false starts: kragen@thrifty:~/pkgs/fr-016$ gdb GNU gdb 6.4.90-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu". (gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00000106 in ?? () (gdb) info registers eax 0x40c57e 4244862 ecx 0xff 255 edx 0x22e4 8932 ebx 0x0 0 esp 0xfffe 0xfffe ebp 0x20cd 0x20cd esi 0xffff0100 -65280 edi 0x85c9a 547994 eip 0x106 0x106 eflags 0x286 [ PF SF IF ] cs 0x22e4 8932 ss 0x22e4 8932 ds 0x22e4 8932 es 0x9f7f 40831 fs 0x22e4 8932 gs 0x1f49 8009 (gdb) set architecture i8086 The target architecture is assumed to be i8086 (gdb) x/10i $cs*16+0x100 0x22f40: mov $0x13,%al 0x22f42: int $0x10 0x22f44: les (%bx),%bp 0x22f46: stos %al,%es:(%di) 0x22f47: adc %di,%ax 0x22f49: adc %fs:1132,%ax 0x22f4e: jmp 0x22f46 0x22f50: add %al,(%bx,%si) 0x22f52: add %al,(%bx,%si) 0x22f54: add %al,(%bx,%si) (gdb) x/16cx $cs*16+0x100 0x22f40: 0xb0 0x13 0xcd 0x10 0xc4 0x2f 0xaa 0x11 0x22f48: 0xf8 0x64 0x13 0x06 0x6c 0x04 0xeb 0xf6 (MS-DOS COM files load into memory at address 0x100.) So I guess when we start the program, `%ah` is 0; so we stick a video mode in `%al` and call interrupt 10h to set the video mode. [Video mode 13h](http://en.wikipedia.org/wiki/Mode_13h) is 320x200, 8-bit pseudocolor; and video memory starts at A0000h, just above the MS-DOS 640K limit. According to Intel document 253666, `les` is "load far pointer into ES segment register"; it apparently loads a 32-bit segment:offset "far pointer" from the place pointed to in memory by `%bx` into the ES register and the `%bp` register specified as the destination. (`%bp` is never used again as far as I can tell.) Now, in the four-instruction loop, `%bx` doesn't change; so presumably the value it has at the point where I interrupted it, 0, is the same value it had at the time this instruction was executed. Presumably `%bx` will be interpreted relative to `%ds`; so what's at `%ds:(%bx)`? `%ds`, like the other segment registers, gets initialized to point at the 64kiB segment where the `.COM` file is loaded; the first 256 bytes are the "PSP" or "program segment prefix". (gdb) x/4cx $ds*16+(int)$ebx 0x22e40: 0xcd 0x20 0x7f 0x9f Well, surprise surprise, there's a little-endian `9f7fh`, just like we see in `%es` above, plus an offset. I guess some value close to that must always be in those bytes, but I didn't remember enough about how DOS launches `.COM` files to know why; Randall Hyde's [Art of Assembly Language Programming says that's the program ending address](http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_13/CH13-8.html#HEADING8-103). (I don't know why that isn't `ffff`.) `9f7fh` is just `81h` less than `A000h`, so by indexing off of it (as `stos` does by default) gives us access to video RAM. So that's the setup for the loop. Here's the loop again: 6: aa stos %al,%es:(%di) 7: 11 f8 adc %di,%ax 9: 64 13 06 6c 04 adc %fs:1132,%ax e: eb f6 jmp 0x6 So, each time through the loop, we store `%al` at `%es:(%di)`, then add `%di` and `%fs:1132` to `%ax`. With carry. 1132 is 046Ch; I'm kind of mystified about what that's supposed to be. Looks like there happen to be zeroes at that address: (gdb) x/4x $fs*16+1132 0x232ac: 0x00 0x00 0x00 0x00 So, anyway, the `stos` increments `%di` to point to the next byte; this will run all over the 65536-byte segment pointed to by `%es`, including the 64000 bytes that comprise video memory, and also 129 bytes before them and 1407 bytes after them, which hopefully will be harmless. For reasons I don't understand, the pattern you get from repeatedly adding `%di` to `%ax` in this way contains a bunch of circular waveplates. (Is that what you call them? Colors according to x² + y² modulo some base.) This is probably the really interesting part of the program, so I'm sad that I'm not able to throw any light on this. It looks like the purpose of the extra `adc` is to perturb the pattern so that it changes in phase each frame. I did this and I started getting ripples in QEMU: (gdb) p *(int*)($fs*16+1132) = -10 $3 = -10 (gdb) c Continuing. Interestingly neither 0 nor -1 gives any motion: I guess adding -1 results in a carry that gets added back in on the next loop. Without `fs:` ------------- I edited the file with Emacs and made the following version without the `fs:` prefix: 0: b0 13 mov $0x13,%al 2: cd 10 int $0x10 4: c4 2f les (%bx),%bp 6: aa stos %al,%es:(%di) 7: 11 f8 adc %di,%ax 9: 13 06 6c 04 adc 1132,%ax d: eb f7 jmp 0x6 f: 20 .byte 0x20 In Dosbox, this runs and produces the same pattern --- but it doesn't animate! So maybe FreeDOS is setting `%fs` to point to the same segment as the other segment registers, and consequently it points at zeroes, but Dosbox (and presumably MS-DOS) leaves it pointing somewhere else. Say, to the bottom of memory. Without the second `adc` ------------------------ I also made a version where the loop is only three instructions: kragen@thrifty:~/pkgs/fr-016$ objdump -m i8086 -b binary -D fr-016-static.com fr-016-static.com: file format binary Disassembly of section .data: 0000000000000000 <.data>: 0: b0 13 mov $0x13,%al 2: cd 10 int $0x10 4: c4 2f les (%bx),%bp 6: aa stos %al,%es:(%di) 7: 11 f8 adc %di,%ax 9: eb fb jmp 0x6 As expected, this generates the same pattern, but it doesn't animate.