added discussion of VA size and added figures for pictures

This commit is contained in:
Perry Kivolowitz 2022-12-26 13:00:58 -06:00
parent 46ec454c70
commit 8601121e6f

View file

@ -2,34 +2,34 @@
## Overview ## Overview
In this chapter we examine the difference between loading the address of (a pointer to) In this chapter we examine the difference between loading the address of
a data label versus loading the data at the label. Both use the `ldr` instruction (a pointer to) a data label versus loading the data at the label. Both
however, the assembler actually does some trickery behind the scenes to accomplish use the `ldr` instruction however, the assembler actually does some
the loads. trickery behind the scenes to accomplish the loads.
## Length of Instructions ## Length of Instructions
All AARCH64 instructions are 4 bytes in width. **All** AARCH64 instructions are 4 bytes in width.
## Length of Pointers ## Length of Pointers
All AARCH64 pointers are 8 bytes in width. **All** AARCH64 pointers are 8 bytes in width.
## How to Specify an Address Too Big to Fit in an Instruction? ## How to Specify an Address Too Big to Fit in an Instruction?
The title of this section sets the table for the need for trickery. All labels The title of this section sets the table for the need for trickery. All
refer to addresses. Addresses are 8 bytes in width but all instructions are 4 labels refer to addresses. Addresses are 8 bytes in width but all
bytes in width. Clearly, we cannot fit the full address of a label in an instructions are 4 bytes in width. Clearly, we cannot fit the full
instruction. address of a label in an instruction.
Some ISA's (not ARM) have variable length instructions. The instruction may be Some ISAs (not ARM) have variable length instructions. The instruction
four bytes wide but it tells the CPU that the next eight bytes are an operand may be four bytes wide but it tells the CPU that the next eight bytes
of the instruction. Thus the true instruction width is 12 bytes. This is not are an operand of the instruction. Thus the true instruction width is 12
true of the ARM ISA. bytes. This is not true of the ARM ISA.
**All instructions are 4 bytes wide. All of them.** **All instructions are 4 bytes wide. All of them.**
## "`ldr` xregister, =label" is a Pseudo Instruction ## "`ldr` x_register, =label" is a Pseudo Instruction
When you assemble an instruction looking like: When you assemble an instruction looking like:
@ -37,21 +37,22 @@ When you assemble an instruction looking like:
ldr x1, =label ldr x1, =label
``` ```
the assembler puts the address of the label into a special region of memory the assembler puts the address of the label into a special region of
fancily called a "literal pool." What matters is this region of memory is placed memory fancily called a "literal pool." What matters is this region of
immediately after (therefore nearby) your code. memory is placed immediately after (therefore nearby) your code.
Then, the assembler computes the difference between the address of the current Then, the assembler computes the difference between the address of the
instruction (the `ldr` itself) and the address of the data in the literal pool current instruction (the `ldr` itself) and the address of the data in
made from the labeled data. the literal pool made from the labeled data.
The assembler generates a different `ldr` instruction which uses the difference The assembler generates a different `ldr` instruction which uses the
(or offset) of the data relative to the program counter (`pc`). The `pc` is difference (or offset) of the data relative to the program counter
non-other the address of the current instruction. (`pc`). The `pc` is non-other the address of the current instruction.
Because the literal pool for your code is located nearby your code, the offset Because the literal pool for your code is located nearby your code, the
from the current instruction to the data in the pool is a relatively **small** offset from the current instruction to the data in the pool is a
number. Small enough, to fit inside a four byte `ldr` instruction. relatively **small** number. Small enough, to fit inside a four byte
`ldr` instruction.
```text ```text
ldr x1, [pc, offset to data in literal pool] ldr x1, [pc, offset to data in literal pool]
@ -112,32 +113,32 @@ above source code will include:
```text ```text
0000000000007a0 <main>: 0000000000007a0 <main>:
7a0: f81f0ffe str x30, [sp, #-16]! 7a0: f81f0ffe str x30, [sp, #-16]!
7a4: 58000160 ldr x0, 7d0 <main+0x30> 7a4: 58000160 ldr x0, 7d0 <main+0x30>
7a8: 58000181 ldr x1, 7d8 <main+0x38> 7a8: 58000181 ldr x1, 7d8 <main+0x38>
7ac: f9400022 ldr x2, [x1] 7ac: f9400022 ldr x2, [x1]
7b0: 97ffffb4 bl 680 <printf@plt> 7b0: 97ffffb4 bl 680 <printf@plt>
7b4: 580000e0 ldr x0, 7d0 <main+0x30> 7b4: 580000e0 ldr x0, 7d0 <main+0x30>
7b8: 580842c1 ldr x1, 11010 <q> 7b8: 580842c1 ldr x1, 11010 <q>
7bc: f9400022 ldr x2, [x1] 7bc: f9400022 ldr x2, [x1]
7c0: 97ffffb0 bl 680 <printf@plt> 7c0: 97ffffb0 bl 680 <printf@plt>
7c4: f84107fe ldr x30, [sp], #16 7c4: f84107fe ldr x30, [sp], #16
7c8: 2a1f03e0 mov w0, wzr 7c8: 2a1f03e0 mov w0, wzr
7cc: d65f03c0 ret 7cc: d65f03c0 ret
``` ```
and and
```text ```text
000000000011010 <q>: 000000000011010 <q>:
11010: 55667788 11010: 55667788
11014: 11223344 11014: 11223344
``` ```
Let's examine the second snippet first. Let's examine the second snippet first.
It says `000000000011010 <q>:`. This means that what comes next is the data It says `000000000011010 <q>:`. This means that what comes next is the
corresponding to what is labeled `q` in our source code. Notice the data corresponding to what is labeled `q` in our source code. Notice the
relocatable address of `11010`. We will explain "relocatable address" relocatable address of `11010`. We will explain "relocatable address"
below. below.
@ -160,33 +161,45 @@ to accomplish their nefarious purposes.
This image shows `gdb` in `layout regs` at the time our program is loaded. This image shows `gdb` in `layout regs` at the time our program is loaded.
![1](./1_prior_to_running.png) <figure>
<img src="././1_prior_to_running.png" style="width:80%;">
<figcaption>Prior to launchr</figcaption>
</figure>
Notice that all of the addresses match the disassemblies given above. Notice that all of the addresses match the disassemblies given above.
For example `main()` starts at `7a0`. For example `main()` starts at `7a0`.
Now watch what happens the the program is actually launched: Now watch what happens the the program is actually launched:
![2](./2_after_b_and_run.png) <figure>
<img src="./2_after_b_and_run.png" style="width:80%;">
NOTE NOTE NOTE NOTE NOTE <figcaption>After breakpoint and launch</figcaption>
</figure>
THERE IS MUCH HERE THAT SHOULD BE REWRITTEN!!!
COME BACK TO THIS!!!
Suddenly all the address change to much larger values. Suddenly all the address change to much larger values.
**In fact, the addresses all seem to be six bytes long! OK, so they aren't **In fact, the addresses all seem to be six bytes long!**
eight bytes long but this can be explained by choices about how much of the
full "virtual" address space that will be used. The salient point is that Why are these addresses only six bytes long when all pointers are
even six bytes is far too large to fit in a four byte instruction. GDB is 8 bytes long?
masking the pseudo instruction and showing what the effective addresses are.**
Sixty four bit ARM Linux kernels allocate 39, 42 or 48 bits for the size
of a process's virtual address space. Notice 42 and 48 bit values
require 6 bytes to hold them. A virtual address space is all of the
addresses a process can generate / use. Further, all addresses used
by processes are virtual addresses.
The salient point is that even six bytes is far too large to fit in a
four byte instruction. GDB is masking the pseudo instruction and showing
what the effective addresses are.**
Now lets step forward to see the results of the first `ldr` of the Now lets step forward to see the results of the first `ldr` of the
`printf()` template / format string into `x0`. `printf()` template / format string into `x0`.
![3](./3_results_of_first_ldr.png) <figure>
<img src="./3_results_of_first_ldr.png" style="width:80%;">
<figcaption>Results of first ldr</figcaption>
</figure>
There is a pointer in `x0` ending in `b018`. Notice this is **NOT** There is a pointer in `x0` ending in `b018`. Notice this is **NOT**
the value encoded in the instruction ending in `a7d0`. the value encoded in the instruction ending in `a7d0`.
@ -195,31 +208,45 @@ has been modified to use some calculated offset from the `pc`.
To finish, here is how we confirm `x0` is indeed correct. To finish, here is how we confirm `x0` is indeed correct.
![4](./4_confirm_x0_is_correct.png) <figure>
<img src="./4_confirm_x0_is_correct.png" style="width:80%;">
<figcaption>Confirming x0 is correct</figcaption>
</figure>
Notice down below the `x/s $x0` prints the value in memory Notice down below the `x/s $x0` prints the value in memory
corresponding to the address contained in `x0`. corresponding to the address contained in `x0`.
Finally: Finally:
![5](./5_confirm_x2_is_correct.png) <figure>
<img src="./4_confirm_x0_is_correct.png" style="width:80%;">
<figcaption>Confirming x2 is correct</figcaption>
</figure>
At the outset of this discussion we said that this program will crash on source code `line 15`. At the outset of this discussion we said that this program will crash on
See if you can work out why. Take a moment before reading further. source code `line 15`. See if you can work out why. Take a moment before
reading further.
Now that you have a hypothesis in mind, take a look at this screenshot showing Now that you have a hypothesis in mind, take a look at this screenshot
the state of `x1` after this instruction: `ldr x1, q` is executed. showing the state of `x1` after this instruction: `ldr x1, q` is
executed.
![6](./after_bad_load.png) <figure>
<img src="./after_bad_load.png" style="width:80%;">
<figcaption>After bad load</figcaption>
</figure>
Notice that what is in `x1` this time looks very different from the previous attempt Notice that what is in `x1` this time looks very different from the
at printing. Notice still more that the value now in `x1` is the value of `q`, not previous attempt at printing. Notice still more that the value now in
its address. `x1` is the value of `q`, not its address.
Naturally, the next instruction which tries to dereference the value of `q` rather Naturally, the next instruction which tries to dereference the value of
than its address, causes a crash. `q` rather than its address, causes a crash.
![7](./after_crash.png) <figure>
<img src="./after_crash.png" style="width:80%;">
<figcaption>After crash</figcaption>
</figure>
## Summary ## Summary
@ -232,13 +259,14 @@ memory at those labels can be retrieved.
| ldr r, =label | Load the address of the label into r | | ldr r, =label | Load the address of the label into r |
| ldr r, label | Load the value found at the label into r | | ldr r, label | Load the value found at the label into r |
In both cases, the assembler will likely do some magical translation of your In both cases, the assembler will likely do some magical translation of
simple `ldr` instruction into something involving offsets so that the resulting your simple `ldr` instruction into something involving offsets so that
offset can fit into an instruction where the full address cannot. the resulting offset can fit into an instruction where the full address
cannot.
To store a value back to memory at the address given by a label, the address To store a value back to memory at the address given by a label, the
corresponding to the label will have first been loaded as address corresponding to the label will have first been loaded as is
is described above. Then, once the address is in a register, an `str` described above. Then, once the address is in a register, an `str`
instruction can be used to properly locate the values to be written. instruction can be used to properly locate the values to be written.
## Questions ## Questions