Update
[less_retarded_wiki.git] / assembly.md
blob25618b0707e4c9b160a34563683f8a04c59411c0
1 # Assembly
3 Assembly (also ASM) is, for any given [hardware](hw.md) computing platform ([ISA](isa.md), basically a [CPU](cpu.md) architecture), the lowest level [programming language](programming_language.md) that expresses typically a linear, unstructured (i.e. without nesting blocks of code) sequence of CPU instructions -- it maps (mostly) 1:1 to [machine code](machine_code.md) (the actual [binary](binary.md) CPU instructions) and basically only differs from the actual machine code by utilizing a more human readable form (it gives human friendly nicknames, or mnemonics, to different combinations of 1s and 0s). Assembly is converted by [assembler](assembler.md) into the the machine code, something akin a computer equivalent of the "[DNA](dna.md)", the lowest level instructions for the computer. Assembly is similar to [bytecode](bytecode.md), but bytecode is meant to be [interpreted](interpreter.md) or used as an intermediate representation in [compilers](compiler.md) while assembly represents actual native code run by hardware. In ancient times when there were no higher level languages (like [C](c.md) or [Fortran](fortran.md)) assembly was used to write computer programs -- nowadays most programmers no longer write in assembly (majority of [zoomer](zoomer.md) "[coders](coding.md)" probably never even touch anything close to it) because it's hard (takes a long time) and not [portable](portability.md), however programs written in assembly are known to be extremely fast as the programmer has absolute control over every single instruction (of course that is not to say you can't fuck up and write a slow program in assembly).
5 { see this meme lol :D http://lolwut.info/images/4chan-g1.png ~drummyfish }
7 **Assembly is NOT a single language**, it differs for every architecture, i.e. every model of CPU has potentially different architecture, understands a different machine code and hence has a different assembly (though there are some standardized families of assembly like x86 that work on wide range of CPUs); therefore **assembly is not [portable](portability.md)** (i.e. the program won't generally work on a different type of CPU or under a different [OS](os.md))! And even the same kind of assembly language may have several different [syntax](syntax.md) formats that also create basically slightly different languages which differ e.g. in comment style, order of writing arguments and even instruction abbreviations (e.g. x86 can be written in [Intel](intel.md) or [AT&T](at_and_t.md) syntax). For the reason of non-portability (and also for the fact that "assembly is hard") you mostly shouldn't write your programs directly in assembly but rather in a bit higher level language such as [C](c.md) (which can be compiled to any CPU's assembly). However you should know at least the very basics of programming in assembly as a good programmer will come in contact with it sometimes, for example during hardcore [optimization](optimization.md) (many languages offer an option to embed inline assembly in specific places), debugging, reverse engineering, when writing a C compiler for a completely new platform or even when designing one's own new platform (you'll probably want to make your compiler generate native assembly, so you have to understand it). **You should write at least one program in assembly** -- it gives you a great insight into how a computer actually works and you'll get a better idea of how your high level programs translate to machine code (which may help you write better [optimized](optimization.md) code) and WHY your high level language looks the way it does.
9 **OK, but why doesn't anyone make a portable assembly?** Well, people do, they just usually call it a [bytecode](bytecode.md) -- take a look at that. [C](c.md) is portable and low level, so it is often called a "portable assembly", though it still IS significantly higher in abstraction and won't usually give you the real assembly vibes. [Forth](forth.md) may also be seen as close to such concept. ACTUALLY [Dusk OS](duskos.md) has something yet closer, called [Harmonized Assembly Layer](hal.md) (see https://git.sr.ht/~vdupras/duskos/tree/master/fs/doc/hal.txt). [Web assembly](web_assembly.md) would also probably fit the definition.
11 The most common assembly languages you'll encounter nowadays are **[x86](x86.md)** (used by most desktop [CPUs](cpu.md)) and **[ARM](arm.md)** (used by most mobile CPUs) -- both are used by [proprietary](proprietary.md) hardware and though an assembly language itself cannot (as of yet) be [copyrighted](copyright.md), the associated architectures may be "protected" (restricted) e.g. by [patents](patent.md) (see also [IP cores](ip_core.md)). **[RISC-V](risc_v.md)** on the other hand is an "[open](open.md)" alternative, though not yet so wide spread. Other assembly languages include e.g. [AVR](avr.md) (8bit CPUs used e.g. by some [Arduinos](arduino.md)) and [PowerPC](ppc.md).
13 To be precise, a typical assembly language is actually more than a set of nicknames for machine code instructions, it may offer helpers such as [macros](macro.md) (something akin the C preprocessor), pseudoinstructions (commands that look like instructions but actually translate to e.g. multiple instructions), [comments](comment.md), directives, automatic inference of opcode from operands, named labels for jumps (as writing literal jump addresses would be extremely tedious) etc. I.e. it is still much easier to write in assembly than to write pure machine code even if you knew all opcodes from memory. For the same reason remember that just replacing assembly mnemonics with binary machine code instructions is not yet enough to make an executable program! More things have to be done such as [linking](linking.md) [libraries](library.md) and converting the result to some [executable format](executable_format.md) such as [elf](elf.md) which contains things like header with metainformation about the program etc.
15 **How will programming in assembly differ from your mainstream high-level programming?** Quite a lot, assembly is extremely low level, so you get no handholding or much programming "safety" (apart from e.g. CPU operation modes), you have to do everything yourself -- you'll be dealing with things such as function [call conventions](call_convention.md), [interrupts](interrupt.md), [syscalls](syscall.md) and their conventions, counting CPU cycles of individual instructions, looking up exact hexadecimal memory addresses, opcodes, defining memory segments, dealing with [endianness](endianness.md), raw [goto](goto.md) jumps, [call frames](call_frame.md) etc. You have no branching (if-then-else), loops or functions, you make these yourself with gotos. You can't write expressions like `(a + 3 * b) / 10`, no, you have to write every step of how to evaluate this expression using registers, i.e. something like: load *a* to register *A*, load *b* to register *B*, multiply *B* by 3, add register *B* to *A*, divide *A* by 10. You don't have any [data types](data_type.md), you have to know yourself that your variables really represent signed values so when you're dividing, you have to use signed divide instruction instead of unsigned divide -- if you mess this up, no one will tell you, your program simply won't work. And so on.
17 ## Typical Assembly Language
19 Assembly languages are usually unstructured, i.e. there are no control structures such as `if` or `while` statements: these have to be manually implemented using labels and jump ([goto](goto.md), branch) instructions. There may exist macros that mimic control structures. The typical look of an assembly program is however still a single column of instructions with arguments, one per line, each representing one machine instruction.
21 In assembly it is also common to blend program instructions and data, i.e. sometimes you create a label after which you just put bytes that will represent e.g. text strings or images and after that you start to write program instructions that work with these data, which will likely physically be placed this way (after the data) in the final program. This may cause quite nasty bugs if you by mistake jump to a place where data reside and try to treat them as instructions.
23 The working of the language reflects the actual [hardware](hardware.md) architecture -- most architectures are based on [registers](register.md) so usually there is a small number (something like 16) of registers which may be called something like R0 to R15, or A, B, C etc. Sometimes registers may even be subdivided (e.g. in x86 there is an *eax* 32bit register and half of it can be used as the *ax* 16bit register). These registers are the fastest available memory (faster than the main RAM memory, they are literally INSIDE the CPU, even in front of the [cache](cache.md)) and are used to perform calculations. Some registers are general purpose and some are special: typically there will be e.g. the FLAGS register which holds various 1bit results of performed operations (e.g. [overflow](overflow.md), zero result etc.). Some instructions may only work with some registers (e.g. there may be kind of a "[pointer](pointer.md)" register used to hold addresses along with instructions that work with this register, which is meant to implement [arrays](array.md)). Values can be moved between registers and the main memory (with instructions called something like *move*, *load* or *store*).
25 Writing instructions works similarly to how you call a [function](function.md) in high level language: you write its name and then its [arguments](argument.md), but in assembly things are more complicated because an instruction may for example only allow certain kinds of arguments -- it may e.g. allow a register and immediate constant (kind of a number literal/constant), but not e.g. two registers. You have to read the documentation for each instruction. While in high level language you may write general [expressions](expression.md) as arguments (like `myFunc(x + 2 * y,myFunc2())`), here you can only pass specific values.
27 There are also no complex [data types](data_type.md), assembly only works with numbers of different size, e.g. 16 bit integer, 32 bit integer etc. Strings are just sequences of numbers representing [ASCII](ascii.md) values, it is up to you whether you implement null terminated strings or Pascal style strings. [Pointers](pointer.md) are just numbers representing addresses. It is up to you whether you interpret a number as signed or unsigned (some instructions treat numbers as unsigned, some as signed, some don't care because it doesn't matter).
29 Instructions are typically written as three-letter abbreviations and follow some unwritten naming conventions so that different assembly languages at least look similar. Common instructions found in most assembly languages are for example:
31 - **MOV** (move): move a number between registers and/or main memory (RAM).
32 - **JMP** (jump, also e.g. BRA for branch): unconditional jump to far away instruction.
33 - **JEQ** (jump if equal, also BEQ etc.): jump if result of previous comparison was equality.
34 - **ADD** (add): add two numbers.
35 - **NOP** (no operation): do nothing (used e.g. for delays or as placeholders).
36 - **CMP** (compare): compare two numbers and set relevant flags (typically for a subsequent conditional jump).
37 - ...
39 [Fun](fun.md) note: `HCF` -- *halt and catch fire* -- is a humorous nickname for instructions that just stop the CPU and wait for restart.
41 ## How To
43 *For specific assembly language how tos see their own articles: [x86](x86.md), [Arm](arm.md) etc.*
45 On [Unices](unix.md) the [objdump](objdump.md) utility from GNU binutils can be used to **disassemble** compiled programs, i.e view the instructions of the program in assembly (other tools like ndisasm can also be used). Use it e.g. as:
47 ```
48 objdump -d my_compiled_program
49 ```
51 Let's now write a simple Unix program in 64bit [x86](x86.md) assembly -- we'll be using AT&T syntax that's used by [GNU](gnu.md). Write the following source code into a file named e.g. `program.s`:
53 ```
54 .global   _start         # include the symbol in object file
56 str:
57 .ascii    "it works\n"   # the string data
59 .text 
60 _start:                  # execution starts here
61   mov     $5,   %rbx     # store loop counter in rbx
63 .loop:
64   # make a Linux "write" syscall:
65                          # args to syscall will be passed in regs.
66   mov     $1,   %rax     # says syscalls type (1 = write)
67   mov     $1,   %rdi     # says file to write to (1 = stdout)
68   mov     $str, %rsi     # says the address of the string to write
69   mov     $9,   %rdx     # says how many bytes to write
70   syscall                # makes the syscall
72   sub     $1,   %rbx     # decrement loop counter
73   cmp     $0,   %rbx     # compare it to 0
74   jne     .loop          # if not equal, jump to start of the loop
76   # make an "exit" syscall to properly terminate:
77   mov     $60,  %rax     # says syscall type (60 = exit)
78   mov     $0,   %rdi     # says return value (0 = success)
79   syscall                # makes the syscall
80 ```
82 The program just writes out `it works` five times: it uses a simple loop and a [Unix](unix.md) [system call](syscall.md) for writing a string to standard output (i.e. it won't work on [Windows](windows.md) and similar shit).
84 Now assembly source code can be manually assembled into executable by running assemblers like `as` or `nasm` to obtain the intermediate [object file](obj.md) and then [linking](linking.md) it with `ld`, but to assemble the above written code simply we may just use the `gcc` compiler which does everything for us:
86 ```
87 gcc -nostdlib -no-pie -o program program.s
88 ```
90 Now we can run the program with
92 ```
93 ./program
94 ```
96 And we should see
98 ```
99 it works
100 it works
101 it works
102 it works
103 it works
106 As an exercise you can objdump the final executable and see that the output basically matches the original source code. Furthermore try to disassemble some primitive C programs and see how a compiler e.g. makes if statements or functions into assembly.
108 ## Example
110 Let's take the following [C](c.md) code:
113 #include <stdio.h>
115 char incrementDigit(char d)
117   return // remember this is basically an if statement
118     d >= '0' && d < '9' ?
119     d + 1 :
120     '?';
123 int main(void)
125   char c = getchar();
126   putchar(incrementDigit(c));
127   return 0;
131 We will now compile it to different assembly languages (you can do this e.g. with `gcc -S my_program.c`). This assembly will be pretty long as it will contain [boilerplate](boilerplate.md) and implementations of `getchar` and `putchar` from standard library, but we'll only be looking at the assembly corresponding to the above written code. Also note that the generated assembly will probably differ between compilers, their versions, flags such as [optimization](optimization.md) level etc. The code will be manually commented.
133 { I used this online tool: https://godbolt.org. ~drummyfish }
135 { Also not sure the comments are 100% correct, let me know if not. ~drummyfish }
137 The [x86](x86.md) assembly may look like this (to understand the weird juggling of values between registers see [calling conventions](calling_convention.md)):
140 incrementDigit:
141   pushq   %rbp                   # save base pointer
142   movq    %rsp, %rbp             # move base pointer to stack top
143   movl    %edi, %eax             # move argument to eax
144   movb    %al, -4(%rbp)          # and move it to local var.
145   cmpb    $47, -4(%rbp)          # compare it to '0'
146   jle     .L2                    # if <=, jump to .L2
147   cmpb    $56, -4(%rbp)          # else compare to '9'
148   jg      .L2                    # if >, jump to .L4
149   movzbl  -4(%rbp), %eax         # else get the argument
150   addl    $1, %eax               # add 1 to it
151   jmp     .L4                    # jump to .L4
152 .L2:
153   movl    $63, %eax              # move '?' to eax (return val.)
154 .L4:
155   popq    %rbp                   # restore base pointer
156   ret
157   
158 main:
159   pushq   %rbp                   # save base pointer
160   movq    %rsp, %rbp             # move base pointer to stack top
161   subq    $16, %rsp              # make space on stack
162   call    getchar                # push ret. addr. and jump to func.
163   movb    %al, -1(%rbp)          # store return val. to local var.
164   movsbl  -1(%rbp), %eax         # move with sign extension
165   movl    %eax, %edi             # arg. will be passed in edi
166   call    incrementDigit
167   movsbl  %al, %eax              # sign extend return val.
168   movl    %eax, %edi             # pass arg. in edi again
169   call    putchar
170   movl    $0, %eax               # values are returned in eax
171   leave
172   ret
175 The [ARM](arm.md) assembly may look like this:
178 incrementDigit:
179   sub   sp, sp, #16              // make room on stack
180   strb  w0, [sp, 15]             // load argument from w0 to local var.
181   ldrb  w0, [sp, 15]             // load back to w0
182   cmp   w0, 47                   // compare to '0'
183   bls   .L2                      // branch to .L2 if <
184   ldrb  w0, [sp, 15]             // load argument again to w0
185   cmp   w0, 56                   // compare to '9'
186   bhi   .L2                      // branch to .L2 if >=
187   ldrb  w0, [sp, 15]             // load argument again to w0
188   add   w0, w0, 1                // add 1 to it
189   and   w0, w0, 255              // mask out lowest byte
190   b     .L3                      // branch to .L3
191 .L2:
192   mov   w0, 63                   // set w0 (ret. value) to '?'
193 .L3:
194   add   sp, sp, 16               // shift stack pointer back
195   ret
196   
197 main:
198   stp   x29, x30, [sp, -32]!     // shift stack and store x regs
199   mov   x29, sp
200   bl    getchar
201   strb  w0, [sp, 31]             // store w0 (ret. val.) to local var. 
202   ldrb  w0, [sp, 31]             // load it back to w0
203   bl    incrementDigit
204   and   w0, w0, 255              // mask out lowest byte
205   bl    putchar
206   mov   w0, 0                    // set ret. val. to 0
207   ldp   x29, x30, [sp], 32       // restore x regs
208   ret
211 The [RISC-V](risc_v.md) assembly may look like this:
214 incrementDigit:
215   addi    sp,sp,-32              # shift stack (make room)
216   sw      s0,28(sp)              # save frame pointer
217   addi    s0,sp,32               # shift frame pointer
218   mv      a5,a0                  # get arg. from a0 to a5
219   sb      a5,-17(s0)             # save to to local var.
220   lbu     a4,-17(s0)             # get it to a4
221   li      a5,47                  # load '0' to a4
222   bleu    a4,a5,.L2              # branch to .L2 if a4 <= a5
223   lbu     a4,-17(s0)             # load arg. again
224   li      a5,56                  # load '9' to a5
225   bgtu    a4,a5,.L2              # branch to .L2 if a4 > a5
226   lbu     a5,-17(s0)             # load arg. again
227   addi    a5,a5,1                # add 1 to it
228   andi    a5,a5,0xff             # mask out the lowest byte
229   j       .L3                    # jump to .L3
230 .L2:
231   li      a5,63                  # load '?'
232 .L3:
233   mv      a0,a5                  # move result to ret. val.
234   lw      s0,28(sp)              # restore frame pointer
235   addi    sp,sp,32               # pop stack
236   jr      ra                     # jump to addr in ra
237   
238 main:
239   addi    sp,sp,-32              # shift stack (make room)
240   sw      ra,28(sp)              # store ret. addr on stack
241   sw      s0,24(sp)              # store stack frame pointer on stack
242   addi    s0,sp,32               # shift frame pointer
243   call    getchar
244   mv      a5,a0                  # copy return val. to a5
245   sb      a5,-17(s0)             # move a5 to local var
246   lbu     a5,-17(s0)             # load it again to a5
247   mv      a0,a5                  # move it to a0 (func. arg.)
248   call    incrementDigit
249   mv      a5,a0                  # copy return val. to a5
250   mv      a0,a5                  # get it back to a0 (func. arg.)
251   call    putchar
252   li      a5,0                   # load 0 to a5
253   mv      a0,a5                  # move it to a0 (ret. val.)
254   lw      ra,28(sp)              # restore return addr.
255   lw      s0,24(sp)              # restore frame pointer
256   addi    sp,sp,32               # pop stack
257   jr      ra                     # jump to addr in ra