diff --git a/README.md b/README.md index e53d20c..ae06164 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Assembly Language Programming Made Not So Scary -This text book provides a fairly thorough examination of the ARM V8 ISA (Instruction Set Architecture). +This text book provides a fairly thorough examination of the ARM V8 ISA (Instruction Set Architecture). It begins from the perspective of a person knowledgeable in the C or C++ programming languages (or similar languages, of which there are many). Early chapters bridge your knowledge of C or C++ backwards into assembly language driving home a very sharp @@ -51,6 +51,7 @@ the 64 bit ARM Instruction Set Architecture (ISA). | 5 | [Interlude - Registers](./section_1/regs/README.md) | | 6 | [Interlude - Load and Store](./section_1/regs/ldr.md) | | 7 | [Calling and Returning From Functions](./section_1/funcs/README.md) | +| 8 | [Passing Parameters To Functions](./section_1/funcs/README2.md) | ## Section 2 - Stuff diff --git a/section_1/funcs/README.md b/section_1/funcs/README.md index 66a364d..003c071 100644 --- a/section_1/funcs/README.md +++ b/section_1/funcs/README.md @@ -4,7 +4,7 @@ Calling functions, passing parameters to them and receiving back return values i ## Bottom Line Concept -The name of a (non-inline) function is a label to which a branch with link ('bl') can be made. +The name of a (non-inline) function is a label to which a branch with link ('bl') can be made. The `bl` instruction is stands for **B**ranch with **L**ink. The **link** concept is what enables a function (or method) to **return** to the instruction after the function call. @@ -49,136 +49,6 @@ bl func Notice that calling a function **is** a branch. But it is a special branch instruction - *branch with link*. It is the *link* that allows the function to `ret`urn. -## Passing Parameters +## Returning Values -Up to 8 parameters can be passed directly via registers. Each parameter can be up to the size of an address, long or double (8 bytes). If you need to pass more than 8 parameters or you need to pass parameters which are larger than 8 bytes or are `structs`, you would use a different technique described later. - -For the purposes of the present discussion, we assume all parameters are `long int` and are therefore stored in `x` registers. - -Up to 8 parameters are passed in the scratch registers (of which there are 8). These are `x0` through `x7`. *Scratch* means the value of the register can be changed at will without any need to backup or restore their values. This also means that you cannot count on the contents of the scratch registers maintaining their value if your function makes any function calls itself. - -For example: - -```c -long func(long p1, long p2) // 1 -{ // 2 - return p1 + p2; // 3 -} // 4 -``` - -is implemented as: - -```asm -func: add x0, x0, x1 // 1 - ret // 2 -``` - -The first parameter (`p1`) goes in the first scratch register (`x0`). It's an `x` because the parameter's type is `long int`. It is the `0` register, because that is the first scratch register. - -The second parameter (`p2`) goes in the second scratch register (the `1` register) because it is the second argument, and so on. - -`Line 1` of the assembly language provides the label `func` to which a `bl` can be made. - -`Line 1` also provides the full body of the function - the third argument to `add` is added to the second and the result is put in the first. Thus it is: `x0 = x0 + x1`. - -Just as scratch registers are used for passing (up to 8) parameters, the `0` register is used for function returns. -In the case of the current code, the result of the addition is already sitting in `x0` so all we do is `ret` on `Line 2`. - -## Passing Pointers - -A pointer is an address. The word *pointer* is scary. The words *address of* are not as scary. They mean **exactly** the same thing. - -Here is a function which *also* adds two parameters together but this time using pointers to `long int` rather than the values themselves. - -```c -void func(long * p1, long * p2) // 1 -{ // 2 - *p1 = *p1 + *p2; // 3 -} // 4 -``` - -`Line 1` passes the *address of* `p1` and `p2` as parameters. That is, the addresses of `p1` and `p2` are passed in registers `x0` and `x1` rather than their contents. The contents of the underlying -longs still reside in memory. That is: - -* The address of `p1` arrives in `x0`. -* The value of `p1` is found in memory at the address specified by the parameter. - -`Line 3` *dereferences* the addresses to fetch their underlying values. -The values are added together and the result overwrites the value pointed to by `p1`. - -Here it is in assembly language: - -```asm -func: ldr x2, [x0] // 1 - ldr x3, [x1] // 2 - add x2, x2, x3 // 3 - str x2, [x0] // 4 - ret // 5 -``` - -The `add` instruction cannot operate on values in memory. With little exception, all the *action* takes place in registers, not memory. Therefore, the underlying values pointed to by the parameters must be fetched from memory. - -`Line 1` provides the label to which `bl` can branch with link. - -Remember that up to the first 8 parameters are passed in the 8 scratch registers. Thus, the address of `p1` and the address of `p2` are stored in `x0` and `x1` respectively. `0` and `1` because these are the first two parameters. The -`x` form of the `0` and `1` registers are used because the parameters' type is an address. - -* Addresses (pointers) to any type are 64 bits wide and therefore must use `x` registers. -* `long` and `unsigned long` integers are 64 bits wide and ... -* `double` floats are 64 bits wide and ... - -`Line 1` also dereferences the address held in `x0` going out to memory and loading (`ldr`) the value found there into `x2`, another scratch register. It's scratch so it doesn't need backing up and restoring. - -`Line 2` does the same for `p2`, putting its value in `x3`. - -Why didn't we reuse `x0` and `x1` as in: - -```asm - ldr x0, [x0] - ldr x1, [x1] -``` - -Doing so would be legal but would end in tears. Doing so would blow away the address of `p1` (and `p2` too -but this doesn't matter). Destroying the address of `p1` would prevent us from copying the result of the -addition back into memory since the address to which we would want to store the result of the addition -would be gone. Can't have that. - -So, as the smart *human*, we decided to use `x2` and `x3` because, well, they're scratch. - -`Line 3` performs the addition. - -`Line 4` stored the value in `x2` at the address in memory still sitting in `x0`. - -### `const` - -Suppose we had: - -```c++ -long func(const long p1, const long p2) // 1 -{ // 2 - return p1 + p2; // 3 -} // 4 -``` - -how would the assembly language change? - -Answer: no change at all! - -`const` is an instruction to the compiler ordering it to prohibit changing the values of `p1` and `p2`. We're smart humans and realize that our assembly language makes no attempt to change `p1` and `p2` so no changes are warranted. - -### Passing by Reference - -Suppose we had: - -```c++ -long func(long & p1, long & p2) // 1 -{ // 2 - return p1 + p2; // 3 -} // 4 -``` - -how would the assembly language change? - -Answer: no change at all! - -Passing by reference is also an instruction to the compiler to treat pointers a little differently - the differences don't show up here so there is no change needed to the assembly language we wrote to handle passing pointers. +LEFT OFF HERE diff --git a/section_1/funcs/README2.md b/section_1/funcs/README2.md new file mode 100644 index 0000000..b37b7ea --- /dev/null +++ b/section_1/funcs/README2.md @@ -0,0 +1,235 @@ +# Section 1 / Chapter 8 / Passing Parameters To Functions + +Up to 8 parameters can be passed directly via registers. Each parameter can be up to the size of an address, long or double (8 bytes). If you need to pass more than 8 parameters or you need to pass parameters which are larger than 8 bytes or are `structs`, you would use a different technique described later. + +For the purposes of the present discussion, we assume all parameters are `long int` and are therefore stored in `x` registers. + +Up to 8 parameters are passed in the scratch registers (of which there are 8). These are `x0` through `x7`. *Scratch* means the value of the register can be changed at will without any need to backup or restore their values. + +**This also means that you cannot count on the contents of the scratch registers maintaining their value if your function makes any function calls itself.** + +For example: + +```c +long func(long p1, long p2) // 1 +{ // 2 + return p1 + p2; // 3 +} // 4 +``` + +is implemented as: + +```asm +func: add x0, x0, x1 // 1 + ret // 2 +``` + +The first parameter (`p1`) goes in the first scratch register (`x0`). It's an `x` because the parameter's type is `long int`. It is the `0` register, because that is the first scratch register. + +The second parameter (`p2`) goes in the second scratch register (the `1` register) because it is the second argument, and so on. + +`Line 1` of the assembly language provides the label `func` to which a `bl` can be made. + +`Line 1` also provides the full body of the function - the third argument to `add` is added to the second and the result is put in the first. Thus it is: `x0 = x0 + x1`. + +Just as scratch registers are used for passing (up to 8) parameters, the `0` register is used for function returns. +In the case of the current code, the result of the addition is already sitting in `x0` so all we do is `ret` on `Line 2`. + +## Passing Pointers + +A pointer is an address of something. The word *pointer* is scary. The words *address of* are not as scary. They mean **exactly** the same thing. + +Here is a function which *also* adds two parameters together but this time using pointers to `long int` rather than the values themselves. + +```c +void func(long * p1, long * p2) // 1 +{ // 2 + *p1 = *p1 + *p2; // 3 +} // 4 +``` + +`Line 1` passes the *address of* `p1` and `p2` as parameters. That is, the addresses of `p1` and `p2` are passed in registers `x0` and `x1` rather than their contents. The contents of the underlying +longs still reside in memory. That is: + +* The address of `p1` arrives in `x0`. +* The value of `p1` is found in memory at the address specified by the parameter. + +`Line 3` *dereferences* the addresses to fetch their underlying values. +The values are added together and the result overwrites the value pointed to by `p1`. + +Here it is in assembly language: + +```asm +func: ldr x2, [x0] // 1 + ldr x3, [x1] // 2 + add x2, x2, x3 // 3 + str x2, [x0] // 4 + ret // 5 +``` + +The `add` instruction cannot operate on values in memory. With little exception, all the *action* takes place in registers, not memory. Therefore, the underlying values pointed to by the parameters must be fetched from memory. + +`Line 1` provides the label to which a use of `bl` can branch with link register. + +Remember that up to the first 8 parameters are passed in the 8 scratch registers. Thus, the address of `p1` and the address of `p2` are stored in `x0` and `x1` respectively. `0` and `1` because these are the first two parameters. The +`x` form of the `0` and `1` registers are used because the parameters' type are addresses. + +* Addresses (pointers) to any type are 64 bits wide and therefore must use `x` registers. +* `long` and `unsigned long` integers are 64 bits wide and ... +* `double` floats are 64 bits wide + +`Line 1` also dereferences the address held in `x0` going out to memory and loading (`ldr`) the value found there into `x2`, another scratch register. It's scratch so it doesn't need backing up and restoring. + +`Line 2` does the same for `p2`, putting its value in `x3`. + +Why didn't we reuse `x0` and `x1` as in: + +```asm + ldr x0, [x0] + ldr x1, [x1] +``` + +Doing so would be legal but would end in tears. Doing so would blow away the address of `p1` (and `p2` too +but this doesn't matter). Destroying the address of `p1` would prevent us from copying the result of the +addition back into memory since the address to which we would want to store the result of the addition +would be gone. Can't have that! + +So, as the smart *human*, we decided to use `x2` and `x3` because, well, they're scratch. + +`Line 3` performs the addition. + +`Line 4` stored the value in `x2` at the address in memory still sitting in `x0`. + +### `const` + +Suppose we had: + +```c++ +long func(const long p1, const long p2) // 1 +{ // 2 + return p1 + p2; // 3 +} // 4 +``` + +how would the assembly language change? + +Answer: no change at all! + +`const` is an instruction to the compiler ordering it to prohibit changing the values of `p1` and `p2`. We're smart humans and realize that our assembly language makes no attempt to change `p1` and `p2` so no changes are warranted. + +### Passing by Reference + +Suppose we had: + +```c++ +long func(long & p1, long & p2) // 1 +{ // 2 + return p1 + p2; // 3 +} // 4 +``` + +how would the assembly language change? + +Answer: no change at all! + +Passing by reference is also an instruction to the compiler to treat pointers a little differently - the differences don't show up here so there is no change needed to the assembly language we wrote to handle passing pointers. + +## What If We Need More Than Eight Parameters? + +First, do you **really** need to pass more than 8 parameters? **REALLY?** + +If for some reason you do, you can pass the first 8 in registers are described above. Beginning with the ninth parameters, these would be passed on the stack. + +**REMEMBER THAT ANY ADJUSTMENT TO THE STACK MUST BE DONE IN MULTIPLES OF 16!** + +* If you need just 1 byte of stack, the stack pointer must be changed by 16. +* If you need 17 bytes of stack, the stack pointer must be changed by 32 and so on. + +Here is a sample function that requires 9 parameters (for who knows what reason): + +```c++ +#include + +void SillyFunction(long p1, long p2, long p3, long p4, long p5, long p6, + long p7, long p8, long p9) { + printf("This example hurts my brain: %ld %ld\n", p8, p9); +} + +int main() { + SillyFunction(1, 2, 3, 4, 5, 6, 7, 8, 9); +} +``` + +This prints: + +```text +This example hurts my brain: 8 9 +``` + +In assembly language, this program could be written as: + +```text + .text // 1 + .global main // 2 + // 3 +SillyFunction: // 4 + str x30, [sp, -16]! // 5 + ldr x0, =fmt // 6 + mov x1, x7 // 7 + ldr x2, [sp, 16] // 8 + bl printf // 9 + ldr x30, [sp], 32 // 10 + ret // 11 + // 12 +main: // 13 + str x30, [sp, -16]! // 14 + mov x0, 9 // 15 + str x0, [sp, -16]! // 16 + mov x0, 1 // 17 + mov x1, 2 // 18 + mov x2, 3 // 19 + mov x3, 4 // 20 + mov x4, 5 // 21 + mov x5, 6 // 22 + mov x6, 7 // 23 + mov x7, 8 // 24 + bl SillyFunction // 25 + ldr x30, [sp], 32 // 26 + ret // 27 + // 28 + .data // 29 +fmt: .asciz "This example hurts my brain: %ld %ld\n" // 30 + // 31 + .end +``` + +Notice how `main()` puts the first 8 parameters into the scratch registers `x0` through `x7` using `Lines 17` to `24`. But first, it put the ninth parameter on the stack. It did the stack parameter first so that the stack pointer could be manipulated in a scratch register. + +After executing `Line 14`, the stack will have: + +```text +sp + 0 return address for main +sp + 8 zero +``` + +After executing `Line 16`, the stack will have: + +```text +sp + 0 9 +sp + 8 garbage +sp + 16 return address for main +sp + 24 zero +``` + +After executing `Line 5`, the stack will have: + +```text +sp + 0 return address for SillyFunction +sp + 8 garbage +sp + 16 9 +sp + 24 garbage +sp + 32 return address for main +sp + 40 zero +``` + +This means that `Line 8` fetches `p9` from memory and puts its value into x2 (where it becomes the third argument to `printf()`). diff --git a/section_1/funcs/nine_args.c b/section_1/funcs/nine_args.c new file mode 100644 index 0000000..bf15793 --- /dev/null +++ b/section_1/funcs/nine_args.c @@ -0,0 +1,10 @@ +#include + +void SillyFunction(long p1, long p2, long p3, long p4, long p5, long p6, + long p7, long p8, long p9) { + printf("This example hurts my brain: %ld %ld\n", p8, p9); +} + +int main() { + SillyFunction(1, 2, 3, 4, 5, 6, 7, 8, 9); +} diff --git a/section_1/funcs/nine_args_asm.s b/section_1/funcs/nine_args_asm.s new file mode 100644 index 0000000..a60129f --- /dev/null +++ b/section_1/funcs/nine_args_asm.s @@ -0,0 +1,32 @@ + .text + .global main + +SillyFunction: + str x30, [sp, -16]! + ldr x0, =fmt + mov x1, x7 + ldr x2, [sp, 16] + bl printf + ldr x30, [sp], 32 + ret + +main: + str x30, [sp, -16]! + mov x0, 9 + str x0, [sp, -16]! + mov x0, 1 + mov x1, 2 + mov x2, 3 + mov x3, 4 + mov x4, 5 + mov x5, 6 + mov x6, 7 + mov x7, 8 + bl SillyFunction + ldr x30, [sp], 32 + ret + + .data +fmt: .asciz "This example hurts my brain: %ld %ld\n" + + .end