OP 20 April, 2020 - 05:00 PM
Two notes before we start:
The Simplest Computer in The World
Despite of how people things technology has advance in the last decades, the reality is that computers haven’t change much in nearly 50 years. They are faster, they are smaller, they consume less power… but they still work the same way. Indeed, this is a simplification, but at the level at which we are going to work this is actually the case.
For illustration purposes, let’s introduce the SCTW-2000. This is a fictional computer that will let us introduce different concepts in a generic way. It could easily be one of the popular home microcomputers from the 80s. We will match the SCTW-2000 with real examples, but we will avoid using real computers, at least at the beginning and for the sake of simplicity.
The SCTW-2000, as many other computers out there is basically composed of two main parts: CPU and memory
Let’s dive into each of these elements to get the big picture.
The Memory
There are a lot of different types of memories: RAM, ROM, SRAM, DRAM, SDRAM, PROM, FLASH, Serial FLASH, … From a SW point of view, unless we go really low level (something we are not going to do in this course), we do not care about the type of memory our computer have. That is important for the HW guys but not for us. At least not now. What is important for us is how does this element fit within the overall computer architecture.
For the time being, let’s imagine the computer’s memory as a huge bunch of drawers, one on top of the other. Inside each drawer we can store one number. An 8 bits number, or in other words, a number between 0 and 255 (that is 2^8 values… 8 bits). Finally, let’s number the drawers, starting for the one on the bottom (number 0), and giving consecutive numbers to the drawers on top. Something like this
The higher the memory in our computer, the taller the drawer pile will be. For instance, if our computer have 64KB of RAM… yes it sounds ridiculous nowadays, it is just an example. So a computer with 64 KB of memory will have 65536 drawers one on top of the other.
Each drawer is known as a memory address, and the number inside the drawer is the content of that memory address.
This is it about the memory for now. Let’s look to the CPU
The CPU
The CPU is the other main component of the computer. It is composed of the following parts:
A set of registers
A Arithmetic-Logic Unit (ALU)
Some control logic.
Registers
Let’s start with the registers. You can see the CPU registers as a small piece of memory that is inside the CPU and therefore it is super fast, and can hold numbers bigger than the 8 bits stored in each of our fictional memory addresses. On the other hand, this is a very small memory, something in the range of 4 to 32 positions (drawers). Compare to the 65536 drawers for our ridiculous small 64KB memory… Each of this positions are known as a register. Registers can be numbered starting from 0 like the memory or they can be named.
For instance the Z-80 processor, named their registers as: AF, BC, DE, HL, IX, IY. The Intel processors traditionally named their registers as: AX, BX, CX, DX, SI, DI,… Motorola processor used more systematic naming schema for their registers: A0-A7 (address registers) D0-D7 (data registers) for its 68K.
As the number of available registers grows, it is usual to follow the Motorola schema and name the registers with a consecutive number. This way, X86-64 (64 bits intel/amd processors) name their registers (the general purpose ones) r0-r15 (lower register have names compatible with the i386 processor). Same with MIPS and ARM processor.
Any way. Most of the processors used to build computers have a set of registers. A very fast and very small memory area inside the processor itself.
The ALU
The second element of the processor is the Arithmetic-Logic Unit. And yes, it basically performs arithmetic and logic operations. You can add, substract, multiply, and, or, xor values using this processor element… and that is what a computer actually does most of the time.
The ALU performs this operations either using the values stored in the registers or values taken from the memory, and stores the results, again, either in a register or a memory address. The available options actually depends on the processor. Old processor only could perform operation on registers and some only on certain registers. Nowadays, this is no longer the case.
This is really it for the ALU
The Control Unit
The last part of the processor we are interested on is the Cotrol Unit. In all honesty, in this simplified computer model, the control unit is anything else inside the processor except the registers and the ALU (branch prediction unit, cache management, bus signaling, pipeline management, memory management unit…). We won’t go in details about most of those circuitries in this course, however, there are a couple of basic functions that we have to be aware of.
One of these things is controlling the CPU external interface. In other words, the state of all those little pins that go out of the CPU chip. Using these pins, the processor can talk to the memory to read and write values in those drawers and can also interface to different types of hardware…
So, when the ALU needs some data from the memory to perform some operation, it asks the control unit to activate the right pins on the processor to command the memory chip to read or write a given memory position. The memory chip will get the value to write from some CPU pines or put the value to read in some other pins (or multiplex these two pin sets, that is using the same pins for reading/writing and even addressing the memory). OK, it does not really work like this, but otherwise we will have a very, very long introductory chapter :wink:
The control unit is also in charge of reading the program from memory, parsing it and executing the machine code…
Machine Code
We will not talk much about machine code… we will be talking about ASM, but, you at least need to know what this machine code is. For that, we need to understand how the CPU runs a program and how does a program looks like.
The first thing we have to introduce is the Program Counter (PC) or Instruction Pointer (IP). The name depends on the processor family you are working with. This is a special register in the CPU that indicates which memory address in the main memory contains the next instruction to run. Whenever the processor executes an instruction, the IP is increased by one (actually it may be more than one, if the current instruction takes several bytes… more on this in a sec), unless the instruction is a branch/jump. In that case, a new value is assigned to the IP so the next instruction will be taken from a new position.
But, you may be wondering… what are those instructions?. OK, no panic. Let’s go back for a second to our fictional SCTW-2000. Now that we know a lot about processors, I can tell you about the specs for the awesome SCTW-2000:
It has 2 registers named EBX and EBP (you will see later why we have chosen these names), plus an instruction pointer named RIP
The ALU can perform the following operation: ADD two registers
It has an instruction MOV to assign values to registers.
After reset (or on power on) all registers are set to 0 (including RIP)
Now, to fully specify the SCTW-2000 we have to define the instruction it can run. The numbers the control unit will be able to understand… that is the machine code. For the SCTW-2000 it is something like this:
As you can see we have used three columns for this table. The first column contains only numbers (1, 2 or 3 numbers depending on the instruction). Yes, instruction may take, and actually take more than one single memory address. This is the machine code, the sequence of numbers stored in memory that the CPU can understand. We have chose some arbitrary opcodes (not so arbitrary but for the time being, it does not really matter which OPCODES we’ve chosen).
Remembering those number is hard for humans. Sure, we can manage for the SCTW-2000, but a real processor may easily have hundreds of those OPCODEs. For that reason, we use something a bit easier to remember for us (that’s why this is named mnemonics 24 - from the greek “memory related”, yes a bit of a free translation)… That is actually the assembly language that you are trying to learn. The ASM language has to be converted into machine code… that is the task of the program know as assembler.
Our first program
To illustrate all this, let’s write a simple program that adds two numbers: 10 and 20. The assembly code for the SCTW-2000 and for such a stupid program will be:
Now let’s do the work of the assembler by hand using the opcode/mnemonic table above:
Now we can put the program in our memory:
Our program requires 8 bytes of memory. We copy the program starting at position 0, and after resetting the SCTW-2000 (sure, we are using SDRAM for the SCTW-2000), the RIP will point to address 0 and will start reading the machine code and executing the instructions.
Not all processor starts execution at address 0. You have to check the datasheet for the processor to figure out the boot process and conditions
C as a Low-Level Programing Language
Before finishing this first part, we have to quickly introduce the other language we are going to work with in this course and we also have to justify why we shall consider C a low level programming language.
For doing that, we are going to try to produce exactly the same code we have generated by hand in the previous section, using a C program. Instead of the SCTW-2000, we are going to use an Intel 64bits… You will see how similar these two processors are :slight_smile:
Let’s write this program in a text file. Call it ph1.c
The program looks a bit weird. The reason is that we are not working at the bare metal level. We have an operating system and a file format to honour, so the compiler has to create quite some more stuff than just the machine code for our code. Moreover, we have to use everything we put in the code, otherwise, the compiler will realise that, and remove the code detected as dead (the one that will never be used)… which is bad for our didactic purposes.
We will progressively go into all those details. Right now, it is not really needed to completely understand what is going on. You just need to realize a couple of things.
First, concentrate on the function f1, at the beginning of the program. No need to fully understand the syntax, just pay attention to the following:
The register keyword in C, tell the compiler that we want to use a register. By default it will try to use memory for variables (actually the stack, but we haven’t talked about it yet), but, as you can see we can force the compiler to produce machine code that uses the registers… as we did with our assembler version.
Then you can see the equivalent to our assembler program. We store 10 in one variable (that will be hold in a register), 20 in another, and then we add both number and store it in the first variable. This is roughly the ASM code we wrote before!!!
Let’s compile the program and take a look to the machine code:
$ gcc -fomit-frame-pointer -o ph1 ph1.c
The -fomit-frame-pointer is just to remove some code generated by the compiler that is not relevant right now. And what we’ve got out of this compilation is:
I had kindly asked objdump to produce intel assembly because it’s closer to the one we used for our awesome SCTW-2000 (that is the -M intel flag).
Let’s look at 0x4004b6 to 0x4004c0:
So, this looks pretty similar to the machine code, we generated for our fictional SCTW-2000 processor. The main differences:
gcc is generating 32 bits values for our constants. Even if we declare our variables as char, a 32 bits value will be generated… Later in the series we will learn why.
The add instruction is a bit different. Intel machine code encodes the registers in the opcode to save space, that is why the machine code for the add ebx,ebp is just 0x01 0xeb, instead of our 0x01, 0x00, 0x01. Check the intel manual for full details
Well, we have managed to write some C code that almost exactly matches the machine code we want the computer to run… this is why C is considered a low level programming language :).
For the Lulz
Just for fun. Let’s see how the ASM for ARM will look like.
First let’s compile the program for ARM:
$ arm-linux-gnueabi-gcc -fomit-frame-pointer -o ph1-arm ph1.c
And now, let’s look at the code:
$ arm-linux-gnueabi-objdump -M intel -d ph1-arm | grep -A 20 '<f1>'
Unrecognised disassembler option: intel
OK, the OP codes are completely different, but the assembly is pretty accurate. You can see that, for ARM, the add instruction accepts 3 parameters instead of just 2. Other than that… it is pretty similar to our SCTW-2000 ASM and also to the Intel 64bits ASM.
And for MIPS we get:
This is a bit different but still… change li to mov and addu to add… and there you go.
- I’m not a hacker myself. Not even a wannabe. Really I do not have any interest on becoming a hacker. This basically means that you should take this course as a starting point. When you get done with it… then… your journey will really start. so gtfoh and do ur shit after ma nigger lessons
- No. HTML is not a programming language. If you think that… you really need to go through this course
- Some introduction to what a computer is. These are low level languages and you need to get to know low level stuff
- That is it for now
The Simplest Computer in The World
Despite of how people things technology has advance in the last decades, the reality is that computers haven’t change much in nearly 50 years. They are faster, they are smaller, they consume less power… but they still work the same way. Indeed, this is a simplification, but at the level at which we are going to work this is actually the case.
For illustration purposes, let’s introduce the SCTW-2000. This is a fictional computer that will let us introduce different concepts in a generic way. It could easily be one of the popular home microcomputers from the 80s. We will match the SCTW-2000 with real examples, but we will avoid using real computers, at least at the beginning and for the sake of simplicity.
The SCTW-2000, as many other computers out there is basically composed of two main parts: CPU and memory
Let’s dive into each of these elements to get the big picture.
The Memory
There are a lot of different types of memories: RAM, ROM, SRAM, DRAM, SDRAM, PROM, FLASH, Serial FLASH, … From a SW point of view, unless we go really low level (something we are not going to do in this course), we do not care about the type of memory our computer have. That is important for the HW guys but not for us. At least not now. What is important for us is how does this element fit within the overall computer architecture.
For the time being, let’s imagine the computer’s memory as a huge bunch of drawers, one on top of the other. Inside each drawer we can store one number. An 8 bits number, or in other words, a number between 0 and 255 (that is 2^8 values… 8 bits). Finally, let’s number the drawers, starting for the one on the bottom (number 0), and giving consecutive numbers to the drawers on top. Something like this
Code:
+--------+
+ 8 bits | Drawer 3
+--------+
| 8 bits | Drawer 2
+--------+
| 8 bits | Drawer 1
+--------+
| 8 bits | Drawer 0
+--------+
Each drawer is known as a memory address, and the number inside the drawer is the content of that memory address.
This is it about the memory for now. Let’s look to the CPU
The CPU
The CPU is the other main component of the computer. It is composed of the following parts:
A set of registers
A Arithmetic-Logic Unit (ALU)
Some control logic.
Registers
Let’s start with the registers. You can see the CPU registers as a small piece of memory that is inside the CPU and therefore it is super fast, and can hold numbers bigger than the 8 bits stored in each of our fictional memory addresses. On the other hand, this is a very small memory, something in the range of 4 to 32 positions (drawers). Compare to the 65536 drawers for our ridiculous small 64KB memory… Each of this positions are known as a register. Registers can be numbered starting from 0 like the memory or they can be named.
For instance the Z-80 processor, named their registers as: AF, BC, DE, HL, IX, IY. The Intel processors traditionally named their registers as: AX, BX, CX, DX, SI, DI,… Motorola processor used more systematic naming schema for their registers: A0-A7 (address registers) D0-D7 (data registers) for its 68K.
As the number of available registers grows, it is usual to follow the Motorola schema and name the registers with a consecutive number. This way, X86-64 (64 bits intel/amd processors) name their registers (the general purpose ones) r0-r15 (lower register have names compatible with the i386 processor). Same with MIPS and ARM processor.
Any way. Most of the processors used to build computers have a set of registers. A very fast and very small memory area inside the processor itself.
The ALU
The second element of the processor is the Arithmetic-Logic Unit. And yes, it basically performs arithmetic and logic operations. You can add, substract, multiply, and, or, xor values using this processor element… and that is what a computer actually does most of the time.
The ALU performs this operations either using the values stored in the registers or values taken from the memory, and stores the results, again, either in a register or a memory address. The available options actually depends on the processor. Old processor only could perform operation on registers and some only on certain registers. Nowadays, this is no longer the case.
This is really it for the ALU
The Control Unit
The last part of the processor we are interested on is the Cotrol Unit. In all honesty, in this simplified computer model, the control unit is anything else inside the processor except the registers and the ALU (branch prediction unit, cache management, bus signaling, pipeline management, memory management unit…). We won’t go in details about most of those circuitries in this course, however, there are a couple of basic functions that we have to be aware of.
One of these things is controlling the CPU external interface. In other words, the state of all those little pins that go out of the CPU chip. Using these pins, the processor can talk to the memory to read and write values in those drawers and can also interface to different types of hardware…
So, when the ALU needs some data from the memory to perform some operation, it asks the control unit to activate the right pins on the processor to command the memory chip to read or write a given memory position. The memory chip will get the value to write from some CPU pines or put the value to read in some other pins (or multiplex these two pin sets, that is using the same pins for reading/writing and even addressing the memory). OK, it does not really work like this, but otherwise we will have a very, very long introductory chapter :wink:
The control unit is also in charge of reading the program from memory, parsing it and executing the machine code…
Machine Code
We will not talk much about machine code… we will be talking about ASM, but, you at least need to know what this machine code is. For that, we need to understand how the CPU runs a program and how does a program looks like.
The first thing we have to introduce is the Program Counter (PC) or Instruction Pointer (IP). The name depends on the processor family you are working with. This is a special register in the CPU that indicates which memory address in the main memory contains the next instruction to run. Whenever the processor executes an instruction, the IP is increased by one (actually it may be more than one, if the current instruction takes several bytes… more on this in a sec), unless the instruction is a branch/jump. In that case, a new value is assigned to the IP so the next instruction will be taken from a new position.
But, you may be wondering… what are those instructions?. OK, no panic. Let’s go back for a second to our fictional SCTW-2000. Now that we know a lot about processors, I can tell you about the specs for the awesome SCTW-2000:
It has 2 registers named EBX and EBP (you will see later why we have chosen these names), plus an instruction pointer named RIP
The ALU can perform the following operation: ADD two registers
It has an instruction MOV to assign values to registers.
After reset (or on power on) all registers are set to 0 (including RIP)
Now, to fully specify the SCTW-2000 we have to define the instruction it can run. The numbers the control unit will be able to understand… that is the machine code. For the SCTW-2000 it is something like this:
Code:
OPCODE | MNEMONIC | Description
-----------+-------------+-----------------------------------------------
0xbb XX | MOV EBX, XX | Copies the value XX in register R0
0xbd XX | MOV EBP, XX | Copies the value XX in register R0
0x01 XX YY | ADD XX, YY | Adds the values of register XX and register YY
| | ands stores the result on register XX
0x90 | NOP | No Operation. Does nothing
0x00 | HALT | Stops the processor
Remembering those number is hard for humans. Sure, we can manage for the SCTW-2000, but a real processor may easily have hundreds of those OPCODEs. For that reason, we use something a bit easier to remember for us (that’s why this is named mnemonics 24 - from the greek “memory related”, yes a bit of a free translation)… That is actually the assembly language that you are trying to learn. The ASM language has to be converted into machine code… that is the task of the program know as assembler.
Our first program
To illustrate all this, let’s write a simple program that adds two numbers: 10 and 20. The assembly code for the SCTW-2000 and for such a stupid program will be:
Code:
MOV EBX, 10
MOV EBP, 20
ADD EBX, EBP
HALT
Code:
ASM MACHINE CODE
MOV EBX, 10 -> 0xbb 0x0a
MOV EBP, 20 -> 0xbd 0x14
ADD EBX, EBP -> 0x01 0x00 0x01
HALT -> 0xff
Code:
ADDR-07 | 0xff |
ADDR-06 | 0x01 |
ADDR-05 | 0x00 |
ADDR-04 | 0x01 |
ADDR-03 | 0x14 |
ADDR-02 | 0xbd |
ADDR-01 | 0x0a |
ADDR-00 | 0xbb | <= RIP
Not all processor starts execution at address 0. You have to check the datasheet for the processor to figure out the boot process and conditions
C as a Low-Level Programing Language
Before finishing this first part, we have to quickly introduce the other language we are going to work with in this course and we also have to justify why we shall consider C a low level programming language.
For doing that, we are going to try to produce exactly the same code we have generated by hand in the previous section, using a C program. Instead of the SCTW-2000, we are going to use an Intel 64bits… You will see how similar these two processors are :slight_smile:
Let’s write this program in a text file. Call it ph1.c
Code:
int f1 (void)
{
register int a = 10;
register int b = 20;
a += b;
return a;
}
int main (void)
{
int a;
a = f1();
}
We will progressively go into all those details. Right now, it is not really needed to completely understand what is going on. You just need to realize a couple of things.
First, concentrate on the function f1, at the beginning of the program. No need to fully understand the syntax, just pay attention to the following:
The register keyword in C, tell the compiler that we want to use a register. By default it will try to use memory for variables (actually the stack, but we haven’t talked about it yet), but, as you can see we can force the compiler to produce machine code that uses the registers… as we did with our assembler version.
Then you can see the equivalent to our assembler program. We store 10 in one variable (that will be hold in a register), 20 in another, and then we add both number and store it in the first variable. This is roughly the ASM code we wrote before!!!
Let’s compile the program and take a look to the machine code:
$ gcc -fomit-frame-pointer -o ph1 ph1.c
The -fomit-frame-pointer is just to remove some code generated by the compiler that is not relevant right now. And what we’ve got out of this compilation is:
Code:
$ objdump -M intel -d ph1 | grep -A 20 '<f1>'
00000000004004b4 <f1>:
4004b4: 55 push rbp
4004b5: 53 push rbx
4004b6: bb 0a 00 00 00 mov ebx,0xa
4004bb: bd 14 00 00 00 mov ebp,0x14
4004c0: 01 eb add ebx,ebp
4004c2: 89 d8 mov eax,ebx
4004c4: 5b pop rbx
4004c5: 5d pop rbp
4004c6: c3 ret
Let’s look at 0x4004b6 to 0x4004c0:
Code:
4004b6: bb 0a 00 00 00 mov ebx,0xa
4004bb: bd 14 00 00 00 mov ebp,0x14
4004c0: 01 eb add ebx,ebp
gcc is generating 32 bits values for our constants. Even if we declare our variables as char, a 32 bits value will be generated… Later in the series we will learn why.
The add instruction is a bit different. Intel machine code encodes the registers in the opcode to save space, that is why the machine code for the add ebx,ebp is just 0x01 0xeb, instead of our 0x01, 0x00, 0x01. Check the intel manual for full details
Well, we have managed to write some C code that almost exactly matches the machine code we want the computer to run… this is why C is considered a low level programming language :).
For the Lulz
Just for fun. Let’s see how the ASM for ARM will look like.
First let’s compile the program for ARM:
$ arm-linux-gnueabi-gcc -fomit-frame-pointer -o ph1-arm ph1.c
And now, let’s look at the code:
$ arm-linux-gnueabi-objdump -M intel -d ph1-arm | grep -A 20 '<f1>'
Unrecognised disassembler option: intel
Code:
0000840c <f1>:
840c: e92d0030 push {r4, r5}
8410: e3a0400a mov r4, #10
8414: e3a05014 mov r5, #20
8418: e0844005 add r4, r4, r5
841c: e1a03004 mov r3, r4
8420: e1a00003 mov r0, r3
8424: e8bd0030 pop {r4, r5}
8428: e12fff1e bx lr
And for MIPS we get:
Code:
$ mips-linux-gcc -fomit-frame-pointer -o ph1-mips ph1-1.c
$ mips-linux-objdump -d ph1-mips | grep -A 20 '<f1>'
00400720 <f1>:
400720: 2402000a li v0,10
400724: 24030014 li v1,20
400728: 00431021 addu v0,v0,v1
40072c: 03e00008 jr ra
400730: 00000000 nop