snehm
snehm

Reputation: 223

gcc with intel x86-32 bit assembly : accessing C function arguments

I am doing an operating system implementation work.
Here's the code first :

//generate software interrupt
void generate_interrupt(int n) {

    asm("mov al, byte ptr [n]");
    asm("mov byte ptr [genint+1], al");
    asm("jmp genint");
    asm("genint:");
    asm("int 0"); 
}

I am compiling above code with -masm=intel option in gcc. Also, this is not complete code to generate software interrupt.

My problem is I am getting error as n undefined, how do I resolve it, please help?

Also it promts error at link time not at compile time, below is an imageenter image description here

Upvotes: 1

Views: 1197

Answers (2)

Michael Petch
Michael Petch

Reputation: 47573

This isn't an answer to your specific question about passing parameters into inline assembly (see @zwol's answer). This addresses using self modifying code unnecessarily for this particular task.


Macro Method if Interrupt Numbers are Known at Compile-time

An alternative to using self modifying code is to create a C macro that generates the specific interrupt you want. One trick is you need to a macro that converts a number to a string. Stringize macros are quite common and documented in the GCC documentation.

You could create a macro GENERATE_INTERRUPT that looks like this:

#define STRINGIZE_INTERNAL(s) #s
#define STRINGIZE(s) STRINGIZE_INTERNAL(s)

#define GENERATE_INTERRUPT(n) asm ("int " STRINGIZE(n));

STRINGIZE will take a numeric value and convert it into a string. GENERATE_INTERRUPT simply takes the number, converts it to a string and appends it to the end of the of the INT instruction.

You use it like this:

GENERATE_INTERRUPT(0);
GENERATE_INTERRUPT(3);
GENERATE_INTERRUPT(255);

The generated instructions should look like:

int    0x0
int3
int    0xff

Jump Table Method if Interrupt Numbers are Known Only at Run-time

If you need to call interrupts only known at run-time then one can create a table of interrupt calls (using int instruction) followed by a ret. generate_interrupt would then simply retrieve the interrupt number off the stack, compute the position in the table where the specific int can be found and jmp to it.

In the following code I get GNU assembler to generate the table of 256 interrupt call each followed by a ret using the .rept directive. Each code fragment fits in 4 bytes. The result code generation and the generate_interrupt function could look like:

/* We use GNU assembly to create a table of interrupt calls followed by a ret
 * using the .rept directive. 256 entries (0 to 255) are generated.
 * generate_interrupt is a simple function that takes the interrupt number
 * as a parameter, computes the offset in the interrupt table and jumps to it.
 * The specific interrupted needed will be called followed by a RET to return
 * back from the function */

extern void generate_interrupt(unsigned char int_no);
asm (".pushsection .text\n\t"

     /* Generate the table of interrupt calls */
     ".align 4\n"
     "int_jmp_table:\n\t"
     "intno=0\n\t"
     ".rept 256\n\t"
         "\tint intno\n\t"
         "\tret\n\t"
         "\t.align 4\n\t"
         "\tintno=intno+1\n\t"
     ".endr\n\t"

     /* generate_interrupt function */
     ".global generate_interrupt\n"  /* Give this function global visibility */
     "generate_interrupt:\n\t"
#ifdef __x86_64__
     "movzx edi, dil\n\t"              /* Zero extend int_no (in DIL) across RDI */
     "lea rax, int_jmp_table[rip]\n\t" /* Get base of interrupt jmp table */
     "lea rax, [rax+rdi*4]\n\t"        /* Add table base to offset = jmp address */
     "jmp rax\n\t"                     /* Do sepcified interrupt */
#else
     "movzx eax, byte ptr 4[esp]\n\t"    /* Get Zero extend int_no (arg1 on stack) */
     "lea eax, int_jmp_table[eax*4]\n\t" /* Compute jump address */
     "jmp eax\n\t"                       /* Do specified interrupt */
#endif
     ".popsection");

int main()
{
    generate_interrupt (0);
    generate_interrupt (3);
    generate_interrupt (255);
}

If you were to look at the generated code in the object file you'd find the interrupt call table (int_jmp_table) looks similar to this:

00000000 <int_jmp_table>:
   0:   cd 00                   int    0x0
   2:   c3                      ret
   3:   90                      nop
   4:   cd 01                   int    0x1
   6:   c3                      ret
   7:   90                      nop
   8:   cd 02                   int    0x2
   a:   c3                      ret
   b:   90                      nop
   c:   cc                      int3
   d:   c3                      ret
   e:   66 90                   xchg   ax,ax
  10:   cd 04                   int    0x4
  12:   c3                      ret
  13:   90                      nop

  ...
  [snip]

Because I used .align 4 each entry is padded out to 4 bytes. This makes the address calculation for the jmp easier.

Upvotes: 4

zwol
zwol

Reputation: 140689

When you are using GCC, you must use GCC-style extended asm to access variables declared in C, even if you are using Intel assembly syntax. The ability to write C variable names directly into an assembly insert is a feature of MSVC, which GCC does not copy.

For constructs like this, it is also important to use a single assembly insert, not several in a row; GCC can and will rearrange assembly inserts relative to the surrounding code, including relative to other assembly inserts, unless you take specific steps to prevent it.

This particular construct should be written

void generate_interrupt(unsigned char n)
{
    asm ("mov byte ptr [1f+1], %0\n\t"
         "jmp 1f\n"
         "1:\n\t"
         "int 0"
         : /* no outputs */ : "r" (n));
}

Note that I have removed the initial mov and any insistence on involving the A register, instead telling GCC to load n into any convenient register for me with the "r" input constraint. It is best to do as little as possible in an assembly insert, and to leave the choice of registers to the compiler as much as possible.

I have also changed the type of n to unsigned char to match the actual requirements of the INT instruction, and I am using the 1f local label syntax so that this works correctly if generate_interrupt is made an inline function.

Having said all that, I implore you to find an implementation strategy for your operating system that does not involve self-modifying code. Well, unless you plan to get a whole lot more use out of the self-modifications, anyway.

Upvotes: 5

Related Questions