You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
8.1 KiB
240 lines
8.1 KiB
4 months ago
|
Getting Started with VIXL for AArch32
|
||
|
=====================================
|
||
|
|
||
|
|
||
|
This guide will show you how to use the VIXL framework for AArch32. We will see
|
||
|
how to set up the VIXL assembler and generate some code. We will also go into
|
||
|
details on a few useful features provided by VIXL and see how to run the
|
||
|
generated code.
|
||
|
|
||
|
The source code of the example developed in this guide can be found in the
|
||
|
`examples/aarch32` directory (`examples/aarch32/getting-started.cc`).
|
||
|
|
||
|
|
||
|
Creating the macro assembler.
|
||
|
-----------------------------
|
||
|
|
||
|
First of all you need to make sure that the header files for the assembler are
|
||
|
included. You should have the following lines at the beginning of your source
|
||
|
file:
|
||
|
|
||
|
// You may use <cstdint> if using C++11 or later.
|
||
|
extern "C" {
|
||
|
#include <stdint.h>
|
||
|
}
|
||
|
|
||
|
#include <cstdio>
|
||
|
#include <string>
|
||
|
#include "aarch32/constants-aarch32.h"
|
||
|
#include "aarch32/instructions-aarch32.h"
|
||
|
#include "aarch32/macro-assembler-aarch32.h"
|
||
|
|
||
|
In our case, those files are included by "examples.h".
|
||
|
|
||
|
All VIXL components are declared in the `vixl::aarch32` namespace, so let's add
|
||
|
this to the beginning of the file for convenience (once again, done in
|
||
|
"examples.h"):
|
||
|
|
||
|
using namespace vixl::aarch32;
|
||
|
|
||
|
Now we are ready to create and initialise the different components.
|
||
|
|
||
|
First of all we need to create a macro assembler object.
|
||
|
|
||
|
MacroAssembler masm;
|
||
|
|
||
|
|
||
|
Generating some code.
|
||
|
---------------------
|
||
|
|
||
|
We are now ready to generate some code. The macro assembler provides methods
|
||
|
for all the instructions that you can use. As it's a macro assembler,
|
||
|
the instructions that you tell it to generate may not directly map to a single
|
||
|
hardware instruction. Instead, it can produce a short sequence of instructions
|
||
|
that has the same effect.
|
||
|
|
||
|
Before looking at how to generate some code, let's introduce a simple but handy
|
||
|
macro:
|
||
|
|
||
|
#define __ masm->
|
||
|
|
||
|
It allows us to write `__ Mov(r0, 42);` instead of `masm->Mov(r0, 42);` to
|
||
|
generate code.
|
||
|
|
||
|
Now we are going to write a C++ function to generate our first assembly
|
||
|
code fragment.
|
||
|
|
||
|
void GenerateDemo(MacroAssembler *masm) {
|
||
|
__ Ldr(r1, 0x12345678);
|
||
|
__ And(r0, r0, r1);
|
||
|
__ Bx(lr);
|
||
|
}
|
||
|
|
||
|
The generated code corresponds to a function with the following C prototype:
|
||
|
|
||
|
uint32_t demo(uint32_t x);
|
||
|
|
||
|
This function doesn't perform any useful operation. It loads the value
|
||
|
0x12345678 into r1 and performs a bitwise `and` operation with
|
||
|
the function's argument (stored in r0). The result of this `and` operation
|
||
|
is returned by the function in r0.
|
||
|
|
||
|
Now in our program main function, we only need to create a label to represent
|
||
|
the entry point of the assembly function and to call `GenerateDemo` to
|
||
|
generate the code.
|
||
|
|
||
|
Label demo;
|
||
|
masm.Bind(&demo);
|
||
|
GenerateDemo(&masm);
|
||
|
masm.Finalize();
|
||
|
|
||
|
Now we are going to learn a bit more on a couple of interesting VIXL features
|
||
|
which are used in this example.
|
||
|
|
||
|
### Label
|
||
|
|
||
|
VIXL's assembler provides a mechanism to represent labels with `Label` objects.
|
||
|
They are easy to use: simply create the C++ object and bind it to a location in
|
||
|
the generated instruction stream.
|
||
|
|
||
|
Creating a label is easy, since you only need to define the variable and bind it
|
||
|
to a location using the macro assembler.
|
||
|
|
||
|
Label my_label; // Create the label object.
|
||
|
__ Bind(&my_label); // Bind it to the current location.
|
||
|
|
||
|
The target of a branch using a label will be the address to which it has been
|
||
|
bound. For example, let's consider the following code fragment:
|
||
|
|
||
|
Label foo;
|
||
|
|
||
|
__ B(&foo); // Branch to foo.
|
||
|
__ Mov(r0, 42);
|
||
|
__ Bind(&foo); // Actual address of foo is here.
|
||
|
__ Mov(r1, 0xc001);
|
||
|
|
||
|
If we run this code fragment the `Mov(r0, 42)` will never be executed since
|
||
|
the first thing this code does is to jump to `foo`, which correspond to the
|
||
|
`Mov(r1, 0xc001)` instruction.
|
||
|
|
||
|
When working with labels you need to know that they are only to be used for
|
||
|
local branches, and should be passed around with care. The major reason is
|
||
|
that they cannot safely be passed or returned by value because this can trigger
|
||
|
multiple constructor and destructor calls. The destructor has assertions
|
||
|
to check that we don't try to branch to a label that hasn't been bound.
|
||
|
|
||
|
|
||
|
### Literal Pool
|
||
|
|
||
|
On AArch32 instructions are 16 or 32 bits long, thus immediate values encoded in
|
||
|
the instructions have limited size. If you want to load a constant bigger than
|
||
|
this limit you have two possibilities:
|
||
|
|
||
|
1. Use multiple instructions to load the constant in multiple steps. This
|
||
|
solution is already handled in VIXL. For instance you can write:
|
||
|
|
||
|
`__ Mov(r0, 0x12345678);`
|
||
|
|
||
|
The previous instruction would not be legal since the immediate value is too
|
||
|
big. However, VIXL's macro assembler will automatically rewrite this line into
|
||
|
multiple instructions efficiently generate the value, ultimately setting 'r0'
|
||
|
with the correct value.
|
||
|
|
||
|
|
||
|
2. Store the constant in memory and load this value from the memory. The value
|
||
|
needs to be written near the code that will load it since we use a PC-relative
|
||
|
offset to indicate the address of this value. This solution has the advantage
|
||
|
of making the value easily modifiable at run-time; since it does not reside
|
||
|
in the instruction stream, it doesn't require cache maintenance when updated.
|
||
|
|
||
|
VIXL also provides a way to do this:
|
||
|
|
||
|
`__ Ldr(r0, 0x12345678);`
|
||
|
|
||
|
The assembler will store the immediate value in a "literal pool", a set of
|
||
|
constants embedded in the code. VIXL will emit the literal pool when needed.
|
||
|
|
||
|
The literal pool is emitted regularly, such that they are within range of the
|
||
|
instructions that refer to it. However, you can force the literal pool to be
|
||
|
emitted using `masm.EmitLiteralPool()`. It generates a branch to skip the
|
||
|
pool.
|
||
|
|
||
|
|
||
|
Running the code.
|
||
|
-----------------
|
||
|
|
||
|
We first need to run a few operations to get executable code. The
|
||
|
`ExecutableMemory` helper takes care of it:
|
||
|
|
||
|
byte* code = masm.GetBuffer().GetBuffer();
|
||
|
uint32_t code_size = masm.GetBuffer().GetSizeInBytes();
|
||
|
ExecutableMemory memory(code, code_size);
|
||
|
|
||
|
Then we compute a pointer to the function we just generated and copy:
|
||
|
|
||
|
uint32_t (*demo_function)(uint32_t) =
|
||
|
memory.GetOffsetAddress<uint32_t (*)(uint32_t)>(0);
|
||
|
|
||
|
Now, we can call this function pointer exactly as if it were a pointer on a C
|
||
|
function:
|
||
|
|
||
|
uint32_t input_value = 0x89abcdef;
|
||
|
uint32_t output_value = (*demo_function)(input_value);
|
||
|
|
||
|
A little trace:
|
||
|
|
||
|
printf("native: abs(%08x) = %08x\n", input_value, output_value);
|
||
|
|
||
|
|
||
|
The example shown in this tutorial is very simple, because the goal was to
|
||
|
demonstrate the basics of the VIXL framework. There are more complex code
|
||
|
examples in the VIXL `examples/aarch32` directory showing more features of both the
|
||
|
macro assembler and the AArch32 architecture.
|
||
|
|
||
|
Disassembling the generated code.
|
||
|
---------------------------------
|
||
|
|
||
|
Once you have generated something with the macro-assembler, you may want to
|
||
|
disassemble it.
|
||
|
|
||
|
First, you must include iostream.
|
||
|
|
||
|
#include <iostream>
|
||
|
|
||
|
And the disassembler header file:
|
||
|
|
||
|
#include "aarch32/disasm-aarch32.h"
|
||
|
|
||
|
Then you have to define the pc used to disassemble (the one which is used to
|
||
|
display the addresses not the location of the instructions):
|
||
|
|
||
|
uint32_t display_pc = 0x1000;
|
||
|
|
||
|
Or, if you running on a 32 bit host, you can use the real address:
|
||
|
|
||
|
uint32_t display_pc = static_cast<uintptr_t>(masm.GetBuffer().GetBuffer());
|
||
|
|
||
|
Then you can disassemble the macro assembler's buffer:
|
||
|
|
||
|
PrintDisassembler disasm(std::cout, display_pc);
|
||
|
disasm.DisassembleA32Buffer(
|
||
|
masm.GetBuffer().GetOffsetAddress<uint32_t*>(0), masm.GetCursorOffset());
|
||
|
|
||
|
If you generated T32 code instead of A32 code, you must use
|
||
|
DisassembleT32Buffer. Warning: if your buffer contains some data or contains
|
||
|
mixed T32 and A32 code, the result won't be accurate (everything will be
|
||
|
disassembled as T32 or A32 code).
|
||
|
|
||
|
Example of disassembly:
|
||
|
|
||
|
0x00001000 e30f0fff mov r0, #65535
|
||
|
0x00001004 e34f0fff movt r0, #65535
|
||
|
0x00001008 e3041567 mov r1, #17767
|
||
|
0x0000100c e3401123 movt r1, #291
|
||
|
0x00001010 e3a02000 mov r2, #0
|
||
|
0x00001014 e7c2001f bfc r0, #0, #3
|
||
|
0x00001018 e7d4081f bfc r0, #16, #5
|
||
|
0x0000101c e7c72011 bfi r2, r1, #0, #8
|
||
|
0x00001020 e7df2811 bfi r2, r1, #16, #16
|
||
|
0x00001024 e1000070 hlt 0
|