|
fasmcpp
0.1
|
This page will guide you through writing a simple "Hello, world!" application using fasmcpp. We'll go through the basics of fasmcpp.
To include fasmcpp in your source files, you only need to include a single header file.
All public symbols defined in fasmcpp.h are enclosed in the namespace fasmcpp. fasmcpp.h includes few other files and some needed headers from the C++ standard library.
Since we're writing a "Hello, world!", we're going to need a function to show our message. We're going to use printf from assembly and std::cout from C++ to display eventual errors.
We're also going to use an optional using namespace to enable a small helper which we're going to see more about soon.
Initializing fasmcpp is simple. You just have to create an instance of the fasmcpp::Assembler class and specify how much memory you want to allocate for the assembler. Note, however, that in some platforms you must create no more than one instance or else an exception will be thrown.
Also, here's our little helper. It converts automatically to bytes and in this case is equivalent to 102400. A KB is 1024 bytes, a MB is 1024 KB and a GB is 1024 MB. Larger multipliers such as TB aren't needed because FASM is written in x86 and has limited access to memory.
The constructor can fail by throwing an exception even if we do everything right, so we have to construct it in a try block. That's needed because in some platforms the assembler must be loaded into a fixed address in memory (0x10000 by default, but it can be changed in CMake) and that address may be unavailable.
Our catch block just shows the error and exits with an error code.
To specify the code we want to compile, we must first create an fasmcpp::Assembly class. We are going to first create an empty one...
...And then we're going to set our code. Note that each target platform needs specific code.
We're going to analyze the code we're going to compile. Here I've extracted only the content of the string we're passing. Keep in mind that we don't know what address the code is going to be executed from, so our code must be position-independent.
You should be familiar with assembly, the FASM syntax and the calling conventions before continuing.
We start by declaring the desired instruction set.
We must reserve enough space on the stack for the functions we're going to call to be able to store the parameters, plus 16-byte alignment.
We pass the first and only argument through the RCX register. Note that we must use lea instead of mov to pass pointers because lea uses a relative address to calculate an absolute one, while mov just copies the relative address into the destination.
Then we call printf, leave the stack pointer as we found it and return.
We define the variable _printf which is initialized with a pointer to the printf function that we're going to supply later.
And then we define our "Hello, world!" string.
We start by declaring the desired instruction set.
Now we need to know EIP so we can do relative addressing, but we can't do mov eax, eip. So we call the next instruction and the CPU will push EIP to the stack.
[ESP] is the location our code was loaded to, plus five. So we set our string on the stack and call printf.
printf is __cdecl which means we have to clean up the stack for the arguments we pass.
We define the variable _printf which is initialized with a pointer to the printf function that we're going to supply later.
And then we define our "Hello, world!" string.
Before we assemble, we need to first set our Assembler so Assembly knows where to store data during the assembling process.
Also, we have to tell the FASM preprocessor to replace the printf symbol with the pointer to the actual function.
Now we're ready to assemble. To do so, just call:
Instead of throwing exceptions when it finds errors in the code, assemble returns false.
If the compilation was successful we can run it. Note however that the run method receives a bool parameter. If true, run saves all non-volatile registers in the stack, depending on the platform and calling convention. If false, run just runs our code. It's recommended for you to be aware of the non-volatile registers, back them up manually and call run with a false argument.
If the compilation wasn't successful we have to see what's wrong. To do so we call the getErrors method which returns a string describing the error. Our code, however, is correct and should succeed.
If you wish to test this bit of code, you can modify the assembly code and make an intentional typo, or even just comment out helloWorld.addPredefinition("printf", printf);.
Here is everything put together, plus some time measurements. You can find this in example.cpp.
I hope this helped you get started with fasmcpp. You can take a look at other guides or at the reference.
1.8.13