1
0
mirror of https://github.com/0xAX/linux-insides.git synced 2024-12-22 14:48:08 +00:00

use 64-bit registers everywher in Theory/asm.md

This commit is contained in:
Alexander Kuleshov 2017-06-08 00:19:40 +06:00
parent a5105b4c8d
commit 896411ad17

View File

@ -18,7 +18,7 @@ If you are familiar with the [assembly](https://en.wikipedia.org/wiki/Assembly_l
__asm__("movq %rax, %rsp"); __asm__("movq %rax, %rsp");
``` ```
or or:
```C ```C
__asm__("hlt"); __asm__("hlt");
@ -92,12 +92,12 @@ Consider the following simple example:
int main(void) int main(void)
{ {
int a = 5; unsigned long a = 5;
int b = 10; unsigned long b = 10;
int sum = 0; unsigned long sum = 0;
__asm__("addl %1,%2" : "=r" (sum) : "r" (a), "0" (b)); __asm__("addq %1,%2" : "=r" (sum) : "r" (a), "0" (b));
printf("a + b = %d\n", sum); printf("a + b = %lu\n", sum);
return 0; return 0;
} }
``` ```
@ -113,13 +113,13 @@ a + b = 15
Ok, great. It works. Now let's look at this example in detail. Here we see a simple `C` program which calculates the sum of two variables placing the result into the `sum` variable and in the end we print the result. This example consists of three parts. The first is the assembly statement with the [add](http://x86.renejeschke.de/html/file_module_x86_id_5.html) instruction. It adds the value of the source operand together with the value of the destination operand and stores the result in the destination operand. In our case: Ok, great. It works. Now let's look at this example in detail. Here we see a simple `C` program which calculates the sum of two variables placing the result into the `sum` variable and in the end we print the result. This example consists of three parts. The first is the assembly statement with the [add](http://x86.renejeschke.de/html/file_module_x86_id_5.html) instruction. It adds the value of the source operand together with the value of the destination operand and stores the result in the destination operand. In our case:
```assembly ```assembly
addl %1, %2 addq %1, %2
``` ```
will be expanded to the: will be expanded to the:
```assembly ```assembly
addl a, b addq a, b
``` ```
Variables and expressions which are listed in the `OutputOperands` and `InputOperands` may be matched in the `AssemblerTemplate`. An input/output operand is designated as `%N` where the `N` is the number of operand from left to right beginning from `zero`. The second part of the our assembly statement is located after the first `:` symbol and contains the definition of the output value: Variables and expressions which are listed in the `OutputOperands` and `InputOperands` may be matched in the `AssemblerTemplate`. An input/output operand is designated as `%N` where the `N` is the number of operand from left to right beginning from `zero`. The second part of the our assembly statement is located after the first `:` symbol and contains the definition of the output value:
@ -140,16 +140,26 @@ Now let's go back to the `r` qualifier. As I mentioned above, a qualifier denote
"r" (a), "0" (b) "r" (a), "0" (b)
``` ```
These are input operands - variables `a` and `b`. We already know what the `r` qualifier does. Now we can have a look at the constraint for the variable `b`. The `0` or any other digit from `1` to `9` is called "matching constraint". With this a single operand can be used for multiple roles. The value of the constraint is the source operand index. In our case `0` will match `sum`. If we look at assembly output of our program These are input operands - variables `a` and `b`. We already know what the `r` qualifier does. Now we can have a look at the constraint for the variable `b`. The `0` or any other digit from `1` to `9` is called "matching constraint". With this a single operand can be used for multiple roles. The value of the constraint is the source operand index. In our case `0` will match `sum`. If we look at assembly output of our program:
```C ```C
0000000000400400 <main>: 0000000000400400 <main>:
400401: ba 05 00 00 00 mov $0x5,%edx ...
400406: b8 0a 00 00 00 mov $0xa,%eax ...
40040b: 01 d0 add %edx,%eax ...
4004fe: 48 c7 45 f8 05 00 00 movq $0x5,-0x8(%rbp)
400506: 48 c7 45 f0 0a 00 00 movq $0xa,-0x10(%rbp)
400516: 48 8b 55 f8 mov -0x8(%rbp),%rdx
40051a: 48 8b 45 f0 mov -0x10(%rbp),%rax
40051e: 48 01 d0 add %rdx,%rax
``` ```
we see that only two general purpose registers are used: `%edx` and `%eax`. This way the `%eax` register is used for storing the value of `b` as well as storing the result of the calculation. We have looked at input and output parameters of an inline assembly statement. Before we move on to other constraints supported by `gcc`, there is one remaining part of the inline assembly statement we have not discussed yet - `clobbers`. First of all our values `5` and `10` will be put at the stack and then these values will be moved to the two general purpose registers: `%rdx` and `%rax`.
This way the `%rax` register is used for storing the value of the `b` as well as storing the result of the calculation. **NOTE** that I've used `gcc 6.3.1` version, so the resulted code of your compiler may differ.
We have looked at input and output parameters of an inline assembly statement. Before we move on to other constraints supported by `gcc`, there is one remaining part of the inline assembly statement we have not discussed yet - `clobbers`.
Clobbers Clobbers
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -160,42 +170,53 @@ Consider the example from before, but we will add an additional, simple assemble
```C ```C
__asm__("movq $100, %%rdx\t\n" __asm__("movq $100, %%rdx\t\n"
"addl %1,%2" : "=r" (sum) : "r" (a), "0" (b)); "addq %1,%2" : "=r" (sum) : "r" (a), "0" (b));
``` ```
If we look at the assembly output If we look at the assembly output:
```C ```C
0000000000400400 <main>: 0000000000400400 <main>:
400400: ba 05 00 00 00 mov $0x5,%edx ...
400405: b8 0a 00 00 00 mov $0xa,%eax ...
40040a: 48 c7 c2 64 00 00 00 mov $0x64,%rdx ...
400411: 01 d0 add %edx,%eax 4004fe: 48 c7 45 f8 05 00 00 movq $0x5,-0x8(%rbp)
400506: 48 c7 45 f0 0a 00 00 movq $0xa,-0x10(%rbp)
400516: 48 8b 55 f8 mov -0x8(%rbp),%rdx
40051a: 48 8b 45 f0 mov -0x10(%rbp),%rax
40051e: 48 c7 c2 64 00 00 00 mov $0x64,%rdx
400525: 48 01 d0 add %rdx,%rax
``` ```
we see that the `%edx` register is overwritten with `0x64` or `100` and the result will be `115` instead of `15`. Now if we add the `%rdx` register to the list of "clobbered" register we will see that the `%rdx` register is overwritten with `0x64` or `100` and the result will be `115` instead of `15`. Now if we add the `%rdx` register to the list of `clobbered` registers:
```C ```C
__asm__("movq $100, %%rdx\t\n" __asm__("movq $100, %%rdx\t\n"
"addl %1,%2" : "=r" (sum) : "r" (a), "0" (b) : "%rdx"); "addq %1,%2" : "=r" (sum) : "r" (a), "0" (b) : "%rdx");
``` ```
and look at the assembler output again and look at the assembler output again:
```C ```C
0000000000400400 <main>: 0000000000400400 <main>:
400400: b9 05 00 00 00 mov $0x5,%ecx 4004fe: 48 c7 45 f8 05 00 00 movq $0x5,-0x8(%rbp)
400405: b8 0a 00 00 00 mov $0xa,%eax 400506: 48 c7 45 f0 0a 00 00 movq $0xa,-0x10(%rbp)
40040a: 48 c7 c2 64 00 00 00 mov $0x64,%rdx
400411: 01 c8 add %ecx,%eax 400516: 48 8b 4d f8 mov -0x8(%rbp),%rcx
40051a: 48 8b 45 f0 mov -0x10(%rbp),%rax
40051e: 48 c7 c2 64 00 00 00 mov $0x64,%rdx
400525: 48 01 c8 add %rcx,%rax
``` ```
the `%ecx` register will be used for `sum` calculation, preserving the intended semantics of the program. Besides general purpose registers, we may pass two special specifiers. They are: the `%rcx` register will be used for `sum` calculation, preserving the intended semantics of the program. Besides general purpose registers, we may pass two special specifiers. They are:
* `cc`; * `cc`;
* `memory`. * `memory`.
The first - `cc` indicates that an assembler code modifies [flags](https://en.wikipedia.org/wiki/FLAGS_register) register. This is typically used if the assembly within contains arithmetic or logic instructions. The first - `cc` indicates that an assembler code modifies [flags](https://en.wikipedia.org/wiki/FLAGS_register) register. This is typically used if the assembly within contains arithmetic or logic instructions:
```C ```C
__asm__("incq %0" ::""(variable): "cc"); __asm__("incq %0" ::""(variable): "cc");
@ -208,82 +229,91 @@ The second `memory` specifier tells the compiler that the given inline assembly
int main(void) int main(void)
{ {
int a[3] = {10,20,30}; unsigned long a[3] = {10000000000, 0, 1};
int b = 5; unsigned long b = 5;
__asm__ volatile("incl %0" :: "m" (a[0])); __asm__ volatile("incq %0" :: "m" (a[0]));
printf("a[0] - b = %d\n", a[0] - b);
printf("a[0] - b = %lu\n", a[0] - b);
return 0; return 0;
} }
``` ```
This example may be artificial, but it illustrates the main idea. Here we have an array of integers and one integer variable. The example is pretty simple, we take the first element of `a` and increment its value. After this we subtract the value of `b` from the first element of `a`. In the end we print the result. If we compile and run this simple example the result may surprise you. This example may be artificial, but it illustrates the main idea. Here we have an array of integers and one integer variable. The example is pretty simple, we take the first element of `a` and increment its value. After this we subtract the value of `b` from the first element of `a`. In the end we print the result. If we compile and run this simple example the result may surprise you:
``` ```
~$ gcc -O3 test.c -o test ~$ gcc -O3 test.c -o test
~$ ./test ~$ ./test
a[0] - b = 5 a[0] - b = 9999999995
``` ```
The result is `5` here, but why? We incremented `a[0]` and subtracted b, so the result should be `6` here. If we have a look at the assembler output for this example The result is `a[0] - b = 9999999995` here, but why? We incremented `a[0]` and subtracted `b`, so the result should be `a[0] - b = 9999999996` here.
If we have a look at the assembler output for this example:
```assembly ```assembly
00000000004004f6 <main>: 00000000004004f6 <main>:
4004f6: c7 44 24 f0 0a 00 00 movl $0xa,-0x10(%rsp) 4004b4: 48 b8 00 e4 0b 54 02 movabs $0x2540be400,%rax
4004fd: 00 4004be: 48 89 04 24 mov %rax,(%rsp)
4004fe: c7 44 24 f4 14 00 00 movl $0x14,-0xc(%rsp) ...
400505: 00 ...
400506: c7 44 24 f8 1e 00 00 movl $0x1e,-0x8(%rsp) ...
40050d: 00 40050e: ff 44 24 f0 incq (%rsp)
40050e: ff 44 24 f0 incl -0x10(%rsp)
400512: b8 05 00 00 00 mov $0x5,%eax 4004d8: 48 be fb e3 0b 54 02 movabs $0x2540be3fb,%rsi
``` ```
we see that the first element of the `a` contains the value `0xa` (`10`). The last two lines of code are the actual calculations. We see our increment instruction with `incl` but then just a move of `5` to the `%eax` register. This looks strange. The problem is we have passed the `-O3` flag to `gcc`, so the compiler did some constant folding and propagation to determine the result of `a[0] - 5` at compile time and reduced it to a `mov` with a constant `5` at runtime. we will see that the first element of the `a` contains the value `0x2540be400` (`10000000000`). The last two lines of code are the actual calculations.
Let's now add `memory` to the clobbers list We see our increment instruction with `incq` but then just a move of `0x2540be3fb` (`9999999995`) to the `%rsi` register. This looks strange.
The problem is we have passed the `-O3` flag to `gcc`, so the compiler did some constant folding and propagation to determine the result of `a[0] - 5` at compile time and reduced it to a `movabs` with a constant `0x2540be3fb` or `9999999995` in runtime.
Let's now add `memory` to the clobbers list:
```C ```C
__asm__ volatile("incl %0" :: "m" (a[0]) : "memory"); __asm__ volatile("incq %0" :: "m" (a[0]) : "memory");
``` ```
and the new result of running this is and the new result of running this is:
``` ```
~$ gcc -O3 test.c -o test ~$ gcc -O3 test.c -o test
~$ ./test ~$ ./test
a[0] - b = 6 a[0] - b = 9999999996
``` ```
Now the result is correct. If we look at the assembly output again Now the result is correct. If we look at the assembly output again:
```assembly ```assembly
00000000004004f6 <main>: 00000000004004f6 <main>:
4004f6: c7 44 24 f0 0a 00 00 movl $0xa,-0x10(%rsp) 400404: 48 b8 00 e4 0b 54 02 movabs $0x2540be400,%rax
4004fd: 00 40040b: 00 00 00
4004fe: c7 44 24 f4 14 00 00 movl $0x14,-0xc(%rsp) 40040e: 48 89 04 24 mov %rax,(%rsp)
400505: 00 400412: 48 c7 44 24 08 00 00 movq $0x0,0x8(%rsp)
400506: c7 44 24 f8 1e 00 00 movl $0x1e,-0x8(%rsp) 400419: 00 00
40050d: 00 40041b: 48 c7 44 24 10 01 00 movq $0x1,0x10(%rsp)
40050e: ff 44 24 f0 incl -0x10(%rsp) 400422: 00 00
400512: 8b 44 24 f0 mov -0x10(%rsp),%eax 400424: 48 ff 04 24 incq (%rsp)
400516: 83 e8 05 sub $0x5,%eax 400428: 48 8b 04 24 mov (%rsp),%rax
400519: c3 retq 400431: 48 8d 70 fb lea -0x5(%rax),%rsi
``` ```
we will see one difference here which is in the following piece code: we will see one difference here which is in the last two lines:
```assembly ```assembly
400512: 8b 44 24 f0 mov -0x10(%rsp),%eax 400428: 48 8b 04 24 mov (%rsp),%rax
400516: 83 e8 05 sub $0x5,%eax 400431: 48 8d 70 fb lea -0x5(%rax),%rsi
``` ```
Instead of constant folding, `GCC` now preserves calculations in the assembly and places the value of `a[0]` in the `%eax` register afterwards. In the end it just subtracts the constant value of `b`. Besides the `memory` specifier, we also see a new constraint here - `m`. This constraint tells the compiler to use the address of `a[0]`, instead of its value. So, now we are finished with `clobbers` and we may continue by looking at other constraints supported by `GCC` besides `r` and `m` which we have already seen. Instead of constant folding, `GCC` now preserves calculations in the assembly and places the value of `a[0]` in the `%rax` register afterwards. In the end it just subtracts the constant value of `b` from the `%rax` register and puts result to the `%rsi`.
Besides the `memory` specifier, we also see a new constraint here - `m`. This constraint tells the compiler to use the address of `a[0]`, instead of its value. So, now we are finished with `clobbers` and we may continue by looking at other constraints supported by `GCC` besides `r` and `m` which we have already seen.
Constraints Constraints
--------------------------------------------------------------------------------- ---------------------------------------------------------------------------------
Now that we are finished with all three parts of an inline assembly statement, let's return to constraints. We already saw some constraints in the previous parts, like `r` which represents a `register` operand, `m` which represents a memory operand and `0-9` which represent an reused, indexed operand. Besides these `GCC` provides support for other constraints. For example the `i` constraint represents an `immediate` integer operand with know value. Now that we are finished with all three parts of an inline assembly statement, let's return to constraints. We already saw some constraints in the previous parts, like `r` which represents a `register` operand, `m` which represents a memory operand and `0-9` which represent an reused, indexed operand. Besides these `GCC` provides support for other constraints. For example the `i` constraint represents an `immediate` integer operand with know value:
```C ```C
#include <stdio.h> #include <stdio.h>
@ -306,7 +336,7 @@ The result is:
a = 100 a = 100
``` ```
Or for example `I` which represents an immediate 32-bit integer. The difference between `i` and `I` is that `i` is general, whereas `I` is strictly specified to 32-bit integer data. For example if you try to compile the following Or for example `I` which represents an immediate 32-bit integer. The difference between `i` and `I` is that `i` is general, whereas `I` is strictly specified to 32-bit integer data. For example if you try to compile the following code:
```C ```C
unsigned long test_asm(int nr) unsigned long test_asm(int nr)
@ -318,7 +348,7 @@ unsigned long test_asm(int nr)
} }
``` ```
you will get an error you will get an error:
``` ```
$ gcc -O3 test.c -o test $ gcc -O3 test.c -o test
@ -329,7 +359,7 @@ test.c:7:9: warning: asm operand 1 probably doesnt match constraints
test.c:7:9: error: impossible constraint in asm test.c:7:9: error: impossible constraint in asm
``` ```
when at the same time when at the same time:
```C ```C
unsigned long test_asm(int nr) unsigned long test_asm(int nr)
@ -341,7 +371,7 @@ unsigned long test_asm(int nr)
} }
``` ```
works perfectly. works perfectly:
``` ```
~$ gcc -O3 test.c -o test ~$ gcc -O3 test.c -o test
@ -378,21 +408,21 @@ All of these constraints may be combined (so long as they do not conflict). In t
```C ```C
#include <stdio.h> #include <stdio.h>
int a = 1; unsigned long a = 1;
int main(void) int main(void)
{ {
int b; unsigned long b;
__asm__ ("movl %1,%0" : "=r"(b) : "r"(a)); __asm__ ("movq %1,%0" : "=r"(b) : "r"(a));
return b; return b;
} }
``` ```
will use a memory operand. will use a memory operand:
```assembly ```assembly
0000000000400400 <main>: 0000000000400400 <main>:
400400: 8b 05 26 0c 20 00 mov 0x200c26(%rip),%eax # 60102c <a> 4004aa: 48 8b 05 6f 0b 20 00 mov 0x200b6f(%rip),%rax # 601020 <a>
``` ```
That's about all of the commonly used constraints in inline assembly statements. You can find more in the official [documentation](https://gcc.gnu.org/onlinedocs/gcc/Simple-Constraints.html#Simple-Constraints). That's about all of the commonly used constraints in inline assembly statements. You can find more in the official [documentation](https://gcc.gnu.org/onlinedocs/gcc/Simple-Constraints.html#Simple-Constraints).
@ -410,16 +440,16 @@ int a = 1;
int main(void) int main(void)
{ {
int b; int b;
__asm__ ("movl %1,%0" : "=r"(b) : "d"(a)); __asm__ ("movq %1,%0" : "=r"(b) : "d"(a));
return b; return b;
} }
``` ```
Now we see that value of the `a` variable will be stored in the `%edx` register: Now we see that value of the `a` variable will be stored in the `%rax` register:
```assembly ```assembly
0000000000400400 <main>: 0000000000400400 <main>:
400400: 8b 15 26 0c 20 00 mov 0x200c26(%rip),%edx # 60102c <a> 4004aa: 48 8b 05 6f 0b 20 00 mov 0x200b6f(%rip),%rax # 601020 <a>
``` ```
The `f` and `t` constraints represent any floating point stack register - `%st` and the top of the floating point stack respectively. The `u` constraint represents the second value from the top of the floating point stack. The `f` and `t` constraints represent any floating point stack register - `%st` and the top of the floating point stack respectively. The `u` constraint represents the second value from the top of the floating point stack.