Assembly code can appear mysterious to the uninitiated. Aspects of the physical machine abstracted away in higher-level languages are referred to directly in its terse commands.
This chapbook of minimal assembly code poems (its author refers to them as "snippets") offers no solutions, context, or explanations to ease this gap, leaving the work of deciphering to the reader. For xorpd, its pseudonymous author, this is exactly the point: to immerse the reader in the machine level of code.
When working inside a computer, everything is eventually made of assembly, so this is where you go if you really want to understand how things work.
There is no extraneous word of text in the book to break that context. The title,
xchg rax, rax, is a NOP instruction (pronounced no-op, for no operation): xchg exchanges the data of two operands, in this case of rax with itself. The author's name, xorpd, is a bitwise logical XOR (exclusive or) of packed double-precision floating-point values.
The xor logical operation is one of my favorites. It is the foundation for most of our modern cryptography.
The "xorpd" instruction allows xoring 128 bits at a time. It is really the kind of instruction you want to be when you grow up.
Initially I wanted to publish the book through a publisher, but no publisher wanted to go with the "minimalistic" design. One publisher said he could publish the book if I added explanations for every code snippet, but I wasn't willing to do so.
Another friend recommended adding QR codes to the pages, linking to explanations, but I wasn't willing to do this either. I remember trying to decipher each of those code snippets myself, and I felt that I could not let my readers down by handing them an easy solution.
That lack of explanation, along with the monospaced font, gives the book the feel of conceptual poetry, perhaps an early Vito Acconci piece.
My father opened one of the books and looked at it in amazement. He said something like "There is nothing inside the book, it's all strange short English words, who would want to buy something like this?"
However, much of the joy of the book comes from discovering the nuances of these tiny programs; many rely on assembly-specific tricks that do not really translate "up" into the virtual machines defined by higher-level programming languages.
Those who have trouble deciphering can view xorpd's video series Assembly Language Adventures, which teaches assembly programming beginning with the very basics, reaching a level of expertise through 29 hours of instruction. Xorpd created the book as a companion piece while working on the video, pulling his favorite assembly snippets together.
One of my favorites is:cmp al,0x0a sbb al,0x69 das
This snippet converts a nibble (4 bits) into a hex digit with only three instructions. It always takes me a few mintues to understand how it works. From what I know the
das instruction doesn't work anymore on the x86_64 architecture, but it is a nice reminder of what life used to be.
How is this accomplished in three assembly instructions? Where is the conversion happening, and how is the 0x0a value used? The author has provided a complete explanation of the code below.
The code converts a hex number (0x0 ... 0xf) into a hex character
How it works
This is the relevant part of the ASCII table:0x30 0 0x31 1 0x32 2 0x33 3 0x34 4 0x35 5 0x36 6 0x37 7 0x38 8 0x39 9 0x3a : 0x3b ; 0x3c < 0x3d = 0x3e > 0x3f ? 0x40 @ 0x41 A 0x42 B 0x43 C 0x44 D ...
There is an unfortunate gap between the numeric digits and the caps English
letters. The code above is meant to overcome that gap.
We want to map a number x to its relevant ASCII number, in the following
fashion: If 0 <= x <= 9, then map x to x + 0x30 If 0xa <= x <= 0xf, then map
x to x + 0x37
Note that the difference is 0x7. The idea for the code comes from the fact
that 7 = (16 - 10) + 1 We get the (16 - 10) difference from the DAS
instruction, and the 1 difference from the SBB and CMP instructions.
Intro to binary coded arithmetic
Most of the magic of this snippet comes from the `das` instruction, so we
better start by exploring what it does.
`das` means decimal adjust (al) after subtraction. Together with the rest of
the binary coded decimals instructions family, it allows us to perform binary
coded decimals arithemtic.
Binary coded decimals are decimal numbers represented in base 16. For
example, 0x16 would mean the decimal number 16.
Internally, the `das` instruction observes the flag register, and alters `al`
to represent the correct binary coded decimal.
Subtraction example:mov al, 0x31 sub al, 0x08 das
Will result in al = 0x23. Interpreted in the world of decimals, 31 - 8 = 23.
Overflow will also happen in the world of decimals (Modulo 100).
Overflow example:mov al, 0x15 sub al, 0x16 das
Will result in al = 0x99, because 15 - 16 = -1, which equals to 99 modulo
And finally, an example equivalent to the first subtraction example:mov al, 0x31 add al, 0x92 daa
Which will also result in al = 0x23. This time we add instead of subtract.
and we use the instruction `daa`, decimal adjust after addition, instead of
There is one more delicate point about `das` and `daa` that we will need for
our solution. Take a look at this example:mov al, 0x31 add al, 0x0a daa
The result is al = 0x41. This means that from the point of view of `daa`,
0x0a and 0x10 are the same. It also means that 0x0f behaves just like 0x15.
Converting to hex
We want to use the 16 - 10 difference to our advantage, somehow bridging the
gap between the '9' and 'A' characters.
Here is a first attempt that is close to what we want to achieve:add al, 0x30 daa
For every digit between '0' and '9', we will get the correct result.
As an example, assigning al = 0x5 will give us the result of al = 0x35, which
is the digit '5'. Assigning al = 0x9 will give us the result of al = 0x39.
What about hex digits? When we try to assign al = 0xa, we will get al = 0x40,
which means that we missed our target by 1. The character 'A' has the ASCII
code 0x41. But we did achieve something interesting here. We managed to take
a jump from 0x9 -> 0x39 to 0xa -> 0x40. Our input increased by 1, but our
output increased by 7!
We need to somehow add 1 in the case hex digits 'a' - 'f', but not in the
case of '0' - '9'. A classic way to achieve this is using the carry flag.
Here is a fixed solution:cmp al, 0xa ; Check if al is an 'a' - 'f' digit cmc ; Complement the carry adc al, 0x30 daa
So, if we have a numeric digit ('0' - '9') in al, the first two
instructions will have no effect. But if our digit is in the 'a' - 'f' range,
we will get a carry flag equals 1, and so `adc al, 0x30` is equivalent to
`add al, 0x31`. Hence for example, an input of al = 0xa will give an al =
0x41 output, as we wanted.
But we could make our code shorter if we could somehow avoid using `cmc`.
In order to do this, we could turn to the dual side, knowing that given
wraparound, subtraction is equivalent to addition. In other words,
`add al, 0x30 // daa` is equivalent to `sub al, 0x70 // das`.
Combining this with the carry flag trick, we finally get:cmp al, 0xa sbb al, 0x69 das
This gives an idea of how much is packed into these short programs, but don't count on explanations for the other pieces. While the code may have seemed daunting early on, it has been intriguing enough for the book to build an audience slowly over the years.
When "xchg rax, rax" was released in 2014, there was no real interest in the book. I hate marketing work, so I never really did anything to attempt to market the book. It is only recently, 6 years later, that people started paying attention.