While I’ve covered basic assembly before, I wanted to quick touch on the idea of implementing simple control flow techniques. GCC of course allows for this in the extended syntax mode, but finding good documentation with examples is hard. The point of this post then is to show a few simple examples of writing control flow code, as well as point out a few of the more subtle points.
Assuming a C++ file with the following declarations:
int a = 1;
int b = 2;
int c = 0;
int ct = 2;
Basic Extended Syntax Code
As a refresher for our earlier post on the subject, in this first example we’ll add a to b and place the result in c. The key is that inline assembly glues our C or C++ code to raw assembly by way of constraints, which are mappings of memory, registers, and other locations to the variables being used.
In this first example we define which registers to use for the add step, but leave it up to the assembler to handle the details of our parameters offsets and stack management.
// add a+b=c __asm__ __volatile__ ( "movl %1, %%eax \n\t" "movl %2, %%ebx \n\t" "addl %%eax, %%ebx \n\t" "movl %%ebx, %0 \n\t" : "=m" (c) : "m" (a), "m" (b) : /* clobbers */ );
Adding a Simple Loop
In this block we employ a label, along with a counting register (ecx), to loop though the code as many times as defined by ct.
// add a+b=c __asm__ __volatile__ ( "movl %3, %%ecx \n\t" // loop counter ".LOOP1_START:" "movl %1, %%eax \n\t" "movl %2, %%ebx \n\t" "addl %%eax, %%ebx \n\t" "movl %%ebx, %0 \n\t" "dec %%ecx \n\t" "jnz .LOOP1_START" : "=m" (c) : "m" (a), "m" (b), "m" (ct) : /* clobbers */ );
To effectively create loops and other flow control statements in assembly we must learn the Label and Symbol syntax.
At the top of the hierarchy is the general idea of a Symbols, with Labels and Local Symbols being implementations of specific types of symbols. This distinction is important because when creating assembly code we will use Labels and Local Symbols to create flow control.
Labels can be thought of as a way to create blocks of code that perform a related function. Within these blocks we can further create Local Symbols to create jump point in our code.
Naming conventions are important, and are defined as follows:
Labels should start (but do not have to) with a .L and end with a colon. For example:
.L_ADD_LOOP:
To refer to a label, for example, using the jmp op-code, we would use:
jmp .L_ADD_LOOP
Local Symbols are used to create jump point within a Label Block, and follow the form:
N:
With N being the unique positional number of that symbol within the larger, named Label block.
For example, we could have:
LReverseShort:
movl %ecx,%edx // copy length
shrl $2,%ecx // #words
jz 3f
1:
subl $4,%esi
movl (%esi),%eax
subl $4,%edi
movl %eax,(%edi)
dec %ecx
jnz 1b
3:
andl $3,%edx // bytes?
jz 5f
4:
dec %esi
movb (%esi),%al
dec %edi
movb %al,(%edi)
dec %edx
jnz 4b
5:
movl 8(%ebp),%eax // get return value (dst ptr) for memcpy/memmove
popl %edi
popl %esi
popl %ebp
ret
Which as you can see, allows the developer to use various jump instructions to navigate the code block.
The important part here is to note how the tail end of each call to the Local Symbols uses a f or b. For example, we have:
jnz 4b and jz 5f.
Why? We follow the convention of writing the raw numeric value of the local symbol, followed by either an f or b, for forward or backward with respect to where that label sits in relation to the calling jump instruction.
Thus, we have two main ways to refer to our Labels and Local Symbols:
We refer to Labels with the full name, and Local Symbols (optionally), with with the number of the Symbol followed by the direction the called area lies in relation to the Local Symbol.
To help drive this point home, see if you can follow the flow if this code segment:
int a = 1; int b = 2; int c = 0; int ct = 1; __asm__ __volatile__ ( "movl %3, %%ecx \n\t" // loop counter "movl %1, %%eax \n\t" // holds 1 "movl %2, %%ebx \n\t" // holds 2 "jmp .Multiply \n\t" ".L_ADD_LOOP:" "addl %%eax, %%ebx \n\t" "cmp $0x1, %%ecx \n\t" "je 1f \n\t" "dec %%ecx \n\t" "jnz .L_ADD_LOOP \n\t" "jmp .EXIT \n\t" "1:" "addl $0x10, %%ebx \n\t" "dec %%ecx \n\t" "cmp $0x0, %%ecx \n\t" "je .EXIT \n\t" "jmp .L_ADD_LOOP \n\t" ".Multiply:" "movl $0x2, %%eax \n\t" "mull %%ebx \n\t" "cmp $0x0, %%ecx \n\t" "jne .L_ADD_LOOP \n\t" ".EXIT:" "movl %%ebx, %0 \n\t" : "=m" (c) : "m" (a), "m" (b), "m" (ct) : /* clobbers */ ); cout << c << endl;
An interesting item of note in this example is that our eax register, because of the requirement the mull instruction places on the loading of the source operand into eax, actually corrupts our final calculation because eax is then used in our addl call in .L_ADD_LOOP.
We could avoid this by using a different register for the add, say edx, or more appropriately, by using a different control flow. No matter, the important point is that if we’re not careful such subtle bugs can creep up. Such is the nature of assembly coding.
For good measure, here is the c++ version of the same function:
// c++ version
int a1 = 1;
int b1 = 2;
int c1 = 0;
int ct1 = 1;
c1 = 2 * b1;
for(int i = ct; i != 0; --i){
c1 = c1 + b1;
if(i == 1){
c1 = 16 + c1;
}
}
A good source of these items in action can be found here.
Useful Links
http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
Local Labels and Symbols
http://tigcc.ticalc.org/doc/gnuasm.html#SEC18
http://tigcc.ticalc.org/doc/gnuasm.html#SEC48