Getting some proficiency with frameworks like TensorFlow and Keras has been pretty interesting. I’m continuing my study of machine learning by trying to really understand every detail of all components. In my own mind I had the idea that I wanted to be able to understand the entire technology chain from the lowest levels to the highest, not necessarily in extreme detail but at least a rough understanding. I had the ambition to understand neural network details as well as I understood how software in general worked. Then I wondered… I do know how software works, right? I’d better check that!
I wanted to find the smallest example I could think of where a cogent change of some specific low-level ones and zeros produced a predictable change in high-level software behavior. What I came up with is a nice reminder that computers do work with just ones and zeros. Here’s my demonstration.
I start with a high level program. It is a simple program to be sure, but it is a C program just like the Linux kernel or a compiler or Quake. The way complexity increases from here is relatively well known.
$ echo "int main(int c){return c-1;}" | gcc -o p_sub -xc -
This creates a program, called p_sub
, which should take arguments,
count them, and return the total number of them. This requires that
the argument count variable be decremented by one (c-1
) because the
program name is considered a part of the argument count. Don’t worry
about the details.
So there’s a usable C program in 28 characters. Next I wanted to see
some different behavior by fiddling with the lowest level possible.
Instead of subtracting one, I will add three to the argument count. I
picked through the program and found the bits that would make that
happen. This command line changes the necessary bits and creates a new
executable, p_add
.
$ xxd p_sub | sed '/660:/s/e8 01/c0 03/' | xxd -r > p_add ; chmod 755 p_add
If your hex vision is strong you may object, this is not bits! This is
bytes. It turns out that the xxd
program won’t allow bit level
changes; you do have to organize them by bytes. But I promised ones
and zeros! The xxd
program can show binary. Here is a summary of
the two bytes I changed. This directly shows the changed ones and
zeros that effect this behavior difference.
$ diff <(xxd -b -s1643 -l2 p_sub) <(xxd -b -s1643 -l2 p_add)
1c1
< 0000066b: 11101000 00000001 ..
---
> 0000066b: 11000000 00000011 ..
Let’s consider these binary numbers in the reverse order that they are
listed. Binary 00000011
is a one in the ones place and a one in the
twos place (one one and one two, so three). 11000000
is 128+64 which
is 192. And 192 divided by 16 is 12. If hex 9 is decimal 9 and a is 10
and b is 11, then 12 must be c. Therefore 192 is c0 hex. Binary
00000001
is the same in decimal and hex, just 1. Finally 11101000
is 128+64+32+8 which is 232 or e8 hex (trust me).
So I basically changed e8 01
to c0 03
. The 1 and the 3 are the
ammounts I’m subtracting and adding respectively. But do the op codes
for e8
and c0
mean subtract
and add
respectively? The
objdump
program can tell us.
$ objdump -d ./p_sub | grep 66a:
66a: 83 e8 01 sub $0x1,%eax
$ objdump -d ./p_add | grep 66a:
66a: 83 c0 03 add $0x3,%eax
Looks right! In fact the objdump
program was how I figured out what
bits to change.
Do the programs work as advertised? Let’s try them out!
$ ./p_sub x x x x x ; echo $?
5
$ ./p_add x x x x x ; echo $?
9
The first program counts the arguments (5 x’s and the program name minus one). The second program does almost the same thing but it seems to add 4 to the argument count (5 x’s, the program name, and an additional plus 3).
The point of this exercise is to show that there is no magic or mysterious hidden processes involved in general computing. One could go lower and wonder what the electrical potentials and clock pulses were doing in the chip after these bits are sent to it. But that behavior is literally carved in stone (silicon at least). Understanding that voodoo is very unlikely to have any helpful effect because there’s not much that can be done about the hardware.
Now I need to get back to studying neural networks. I don’t like the feeling that TensorFlow is fooling me into going along with something stupid simply because the details can be a little hard to understand.