Inline Assembly
Simple tutorial on using asm inline...great for learning or improving your
asm skills...
Now using inline is a great was to test out simple asm algorithms or even
learn the basics of assembly. As you can use your standard C and mix in
some cool optimised assembly... Some cool things we can do, it write some simple
functions mixed in with asm of course which will copy strings, or get the CPU
speed...or even the individual CPU ID number... there hundreds ..thousands of
things you can do.
Lets begin with a simple explanation of inline assembly..then move on to some
simple examples...then onto some really cool and of course useful functions.
First we start by making a simple app...here it is:
// program entry point
void
main()
{
} |
This gives us our entry point...but I don't see any
assembly in there...or anything special? Well we'll add it in now....
// program entry point
void
main()
{
// inline assembly
__asm
{
//our asm code goes here
}
} |
Inline assembly is done using the keywork "__asm"
... now its very important you remember it. And there's two ways we can do
inline assembly...using the { } brackets or put __asm on each line in front of
our assembly code.
Inside our __asm block, we put our assembly code...our
opcodes and mnemonic instructions... an example of a simple set of assembly
instructions, which puts 10 into the eax register, then add's 2 to it, so eax
has 12.
// program entry point
void
main()
{
// inline assembly
__asm
{
mov eax, 10
add eax, 2
}
} |
8, 16 and 32 Bit Registers.
Now inside our CPU, we have registers, the main one's that
you most often use are the general purpose registers, and how they can work with
8, 16 or 32 bit data values is what we'll go over here.
Now today, most machines are 32 bit, and so we use eax, but
if you have a 8 bit value, how do you work with that? Well you use al,
which is a 8 bit register. Hmmm..... now its not really a different
register...its the same register, but a sub section of it.. as we can access
various parts of our 32 bit register by using different names for it...
eax - 32 bits
ax - 16 bits
ah - 8 bits (High 8 Bits)
al - 8 bits (Low 8 Bits)
1111 1111 1111 1111 |
1111 1111 |
1111 1111 |
(32 bits eax ) |
|
1111 1111 |
1111 1111 |
(16 bits ax ) |
|
1111 1111 |
|
(high 8 bits ah) |
|
|
1111 1111 |
(low 8 bits al ) |
And the principles of the eax register apply to the other
registers of course...
eax, ax, ah, al
ebx, bx, bh, bl
ecx, cx, ch, cl
edx, dx, dh, dl
....
Now this is where C or even C++ makes it easy for us, as
its doing a lot of the work for us when we call a function. Its
automatically passing the parameters for us.... pushing them onto the stack and
popping them off the stack for us... which you'd have to do if you where doing
it all in assembly and calling a function.
// simple add function using inline asm
int
add(int i, int j)
{
int r;
__asm
{
mov eax, i
mov ebx, j
add eax, ebx
mov r, eax
}
return r;
}
// program entry point
void
main()
{
int result;
result = add(10, 20);
} |
Wow, that looks tricky... but dont' worry....when you first
start to see assembly inline, and especially if your new to assembly it can be
scary... but its really simple. We first put i, into the CPU eax register,
then put the j value into the CPU register ebx, using the mov instruction.
Then with that amazing add instruction we add eax and ebx and the result is put
back into eax. And thats our answer, so finally we put this result into r,
which is a variable we created and return from our add function.
inline numbers?
Now on occasion, you'll need to put values inline...not
opcodes, but actual values like 2 or 12... now I bet your thinking...hmmm...I
can do this
//
ERROR ** CAUSES ERROR **
__asm
{
0xfe
} |
Why would you want to do this? Well when you really
get into asm, and I'm sure you will...you might want to put the actual optcode
values into your assembly, as you might know the assembly opcode value for
decrement eax, and you can put its value in (e.g. 0xfe, instead of dec ).
To do this, you use the keywork "_emit" and you
would use it like this:
// WORKS
__asm
{
_emit 0xfe
} |
This code doesn't really do much, but it shows you how to
use inline values in your code.
Using the Stack...push'ing and pop'ing
The best example of the stack is function calls... as when
you pass parameters to functions, your in fact pushing them onto the stack and
then calling the function. So the best way for you to see this is with a
simple example. Now how many times have you used the "printf" function
call? You just call it with your string, and it just happens :) Well
let me show you how to do it with assembly:
// function declaration would look like this
// int __cdecl printf(const char *, ...);
#include
<stdio.h>
// program entry point
void
main()
{
// Create a simple string
char string[] = "Test String\n";
// Set a pointer to our string
char* pstr = (char*)&string;
// Call function normally
printf(pstr);
// Call function using assembly
__asm
{
push pstr
call puts
//add esp,4
}
} |
And the output would be:
Test
String
Test
String
Its not much... but it shows how you call a function... but
what about that __cdecl thing? Whats that do I hear you say? Well
that describes the calling convention, and describes who is responsible for
tidying up the stack after we push'ed things onto it...the called function or
us.. And if things are to be push'ed onto the stack from right to left or
left to right... the 3 main types of calling convention are:
__cdecl
__stdcall
__fastcall
Thiscall
With the __cdecl calling convention, where responsible for
restoring the stack, so on returning from our call we have to add 4 to the stack
or pop the stack. In the above example I've commented the 'add esp,4' out,
where esp is the stack pointer register. As we add to the stack, we
decrement the stack pointer. Of course commenting it out won't harm as our
program ends after our call.
Something I discovered though, which is due to how Visual
Studio generates the code, but if I pushed 'string' onto the stack and called
the function, it would cause an error during execution. As I could call
the function with just 'string' and it would work...hmmm...but if I get the
address of 'string' then pushed that it would work. Something to be looked
into I guess...
With a few extra lines, I thought I'd describe the various
calling conventions... as its amazing how many times I get asked... "what does
__stdcall mean" or "what is __cdecl"... etc ... so these few quick descriptions
will help you with the stack and how to use the various calling conventions...
int __cdecl sumExample (int a, int b);
The main characteristics of __cdecl calling convention are:
- Arguments are passed from right to left, and placed on the stack.
- Stack cleanup is performed by the caller.
- Function name is decorated by prefixing it with an underscore
character '_' .
|
int __stdcall sumExample (int a, int b);
The main characteristics of __stdcall calling convention
are:
- Arguments are passed from right to left, and placed on the stack.
- Stack cleanup is performed by the called function.
- Function name is decorated by prepending an underscore character and
appending a '@' character and the number of bytes of stack space required.
|
int __fastcall sumExample (int a, int b);
The main characteristics of __fastcall calling convention
are:
- The first two function arguments that require 32 bits or less are
placed into registers ECX and EDX. The rest of them are pushed on the
stack from right to left.
- Arguments are popped from the stack by the called function.
- Function name is decorated by by prepending a '@' character and
appending a '@' and the number of bytes (decimal) of space required by the
arguments.
|
Thiscall is the default calling convention for
calling member functions of C++ classes (except for those with a variable
number of arguments).The main characteristics of thiscall
calling convention are:
- Arguments are passed from right to left, and placed on the stack.
this is placed in ECX.
- Stack cleanup is performed by the called function.
|
On we go with our stack... another simple example of the
stack in in order I think..... but this time...lets explain how we would push
something on, then pop it off again.
#include
<stdio.h>
// program entry point
void
main()
{
int i=10;
// Display feedback output
printf("start - i: %d \n", i);
// Call function using assembly - push the value
in i, onto the stack
__asm
{
push i
}
i=30;
printf("middle - i: %d \n", i);
// now we restore the value back into i from the
stack
__asm
{
pop i
}
printf("end - i: %d \n", i);
} |
And the output would look like this:
start - i: 10
middle - i: 30
end - i: 10
Its relatively simple to see what's happening, as we put a
value into i, then display it...so you can see its actually there... then
using our assembly instruction, we pop it onto our stack. Change variables
value to 30... and of course display it... you never know... its always good to
double check... and then we pop whats on the stack back into i ... which is our
value from earlier! And yup, its 10... the value we stuck in there at the
start.... cool?
Simple Functions
Example's are always the best way to see how things work I
thing...well well commented examples :)
Adding 2 Numbers With Assembly
#include
<stdio.h>
// add two numbers using assembly
int
addtwo(int x, int
y)
{
int r;
__asm
{
mov eax, x
mov ebx, y
add eax, ebx
mov r, eax
}
return
r;
}
// program entry point
void
main()
{
int i=10, j=20;
int k = addtwo(i, j);
printf("10 + 20 = %d", k);
} |
Adding 3 Numbers With Assembly
#include
<stdio.h>
// add two numbers using assembly
int
addtwo(int x, int
y, int z)
{
int r;
__asm
{
mov eax, x
mov ebx, y
mov ecx, z
add eax, ebx
add eax, ecx
mov r, eax
}
return
r;
}
// program entry point
void
main()
{
int sum = addtwo(10, 20, 30);
printf("sum = %d", sum);
} |
AND'ing 2 Numbers Together With Assembly
int
andtwo(int x, int
y)
{
int r;
__asm
{
mov eax, x
mov ebx, y
and eax, ebx
mov r, eax
}
return r;
} |
Well that's the basics for you.... you'll be not be so
afraid to now and then put in a few lines of assembly code in your code...
either to give that extra speed you need, or to show off to your friends..hehe.
|