Page 1 of 2

inline assembly

Posted: Wed Jul 09, 2008 3:25 pm
by lukem95
my inline usually works fine, but setting up my syscalls it doesn't seem to want to put a value back into a register :S

Code: Select all

...
case SYS_getpid:
            i = getpid();
            __asm__ __volatile__ ("mov %%eax, %0"::"r"(i):"%eax");
       break;
...
this is my code, in the kernel. very simple as you can see, it looks to me like that should work fine. however when i run my test program with the following:

Code: Select all

...
mov eax, 20
int 0x69

cmp eax, 2
jz success

fail:
mov eax, 2
mov ebx, '%'
int 0x69

exit:
;...and exit
mov eax, 1
int 0x69

ret

success:
mov eax, 2
mov ebx, '!'
int 0x69

jmp exit
it should work fine right? but it doesn't. i don't have a way to print the output yet either, so i'm a bit screwed.

is there something obvious i've done wrong?

Re: inline assembly

Posted: Wed Jul 09, 2008 4:10 pm
by frank
Well are you sure the value of SYS_getpid is 20 decimal? Maybe you need to change the

Code: Select all

mov eax, 20
to

Code: Select all

mov eax, 0x20
Secondly are you sure that getpid() is returning the correct value?

Lastly, have you tried examining the actual assembly code outputted by GCC for the function, maybe it writes a value to EAX before returning from the function. EAX is often where the return code is placed for a function, so if the function the code is from isn't declared as void then there is a good chance that it is overwriting the value in EAX with the return code.

Re: inline assembly

Posted: Wed Jul 09, 2008 4:17 pm
by lukem95
yeah, i've gone through my code a lot to check for silly mistakes, its definately decimal, and the function works fine (it's a kernel function, so i use it quite a lot).

i'll have a look at the assembled code, see what GCC does with it, thanks for the suggestion.

Disassembly:

Code: Select all

mov %eax, %ebx
mov %eax, %ebx
-- this got me thinking, i'm used to coding for NASM, but this is GAS syntax, so i maybe i have it the wrong way around? (im tryingt to put pid into EAX) however upon switching my code to:

Code: Select all

__asm__ __volatile__ ("mov %0,%%eax"::"r"(i):"%eax");
there is no change, my program still reports the eax isnt 2.

Re: inline assembly

Posted: Wed Jul 09, 2008 5:46 pm
by pcmattman
You should try this:

Code: Select all

unsigned int finaleax = 0;
__asm__ __volatile__ ("mov %0,%%eax" : "=a" (finaleax) : "r" (i) );
Shouldn't need to tell GCC what register got smashed because it's an output. Also, AT&T syntax is "mov source,dest".

Re: inline assembly

Posted: Thu Jul 10, 2008 4:11 am
by lukem95
thanks for the suggestion, it appears the correct value is not put into EAX. i use the following:

Code: Select all

i = getpid();
kprintf("i = %d\n",i);
__asm__ __volatile__ ("mov %0,%%eax" :"=a"(eax) :"r"(i));
kprintf("eax = %d\n",eax);
and the output is:

Code: Select all

i = 2
eax = 106823
the thing is, when i try it with EBX it appears to work, but my program (when modified to work with ebx) still reports incorrectly.

Re: inline assembly

Posted: Thu Jul 10, 2008 4:42 am
by pcmattman
the thing is, when i try it with EBX it appears to work, but my program (when modified to work with ebx) still reports incorrectly.
Then your registers are getting clobbered somewhere. My system calls used to modify the "struct regs" pointer so that when the interrupt (int 0x80 on my OS) returned the new values would be popped off.

The way you seem to be doing it looks like a surefire way to get clobbering happening.

Re: inline assembly

Posted: Thu Jul 10, 2008 12:42 pm
by Brynet-Inc
Consider this:

Code: Select all

__asm__ __volatile__ ("mov %1,%%eax" :"=a"(eax) :"r"(i));
That should work.

Re: inline assembly

Posted: Fri Jul 11, 2008 5:02 am
by lukem95
i just thought of something that could solve it, hideously simple, but it may work. the only issue is i'm at work right now so i can't test it.

C functions store the return value in EAX right? so surely i can just do "return return_value;".

Re: inline assembly

Posted: Fri Jul 11, 2008 8:08 am
by frank
In my kernel I treat all of my interrupt functions the same. They all store the entire context of the CPU on the stack when they are called, that way any interrupt can trigger a task switch. The only problem with that approach is that whatever gets placed in the registers in the function called by the interrupt gets overwritten when the function returns. I don't know if maybe that's what your kernel is doing, so I would take a look at your interrupt functions to make sure they aren't overwriting the value in EAX for example.

If however your interrupt function is not restoring the state then its more than likely that return( value ) will place value in EAX. But its not a guarantee.

One more suggestion, I don't know if your function does anything else after the break; but if it doesn't you could try just returning after the mov statement instead of using a break and returning from the end of the function.

Re: inline assembly

Posted: Fri Jul 11, 2008 9:47 am
by lukem95
yeah i will try that too, when i get home. it actually just returned (but it was nested in two other functions, so it may have been getting mangled), but i'll try just returning immediatly.

EDIT: just a quick q... ISR's don't need a EOI do they? IIRC it's just IRQ's.

Re: inline assembly

Posted: Fri Jul 11, 2008 12:34 pm
by Zenith
Yeah, the EOI's just for the IRQs (it tells the PIC that the IRQ has been serviced).

Re: inline assembly

Posted: Sun Jul 13, 2008 2:53 pm
by lukem95
i just can't work it out... i know the function stores the return value in EAX fine, but it seems that the register has it's value changed before returning to the program, but i can't work out where. all my ISR handler does is return, then i'm using the common isr stub from JamesM/Brans (i think they're the same) tutorial - it just restores the original registers, but doesn't touch eax.

Re: inline assembly

Posted: Sun Jul 13, 2008 9:53 pm
by frank
Could you post a link to the code or the code from your ISR stub?

Re: inline assembly

Posted: Mon Jul 14, 2008 2:03 am
by lukem95

Code: Select all

isr_common_stub:
   pusha                    ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

   mov ax, ds               ; Lower 16-bits of eax = ds.
   push eax                 ; save the data segment descriptor

   mov ax, 0x10  ; load the kernel data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   call isr_handler

   pop eax        ; reload the original data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   popa                     ; Pops edi,esi,ebp...
   add esp, 8     ; Cleans up the pushed error code and pushed ISR number
   sti
   iret   
there you go.

i really have no idea why my code isn't working, i've actually got to the point where i'm considering alternatives to syscalls (when they have to return a value), such as shared memory for each task, and the return is stored there.

Re: inline assembly

Posted: Tue Jul 15, 2008 8:26 pm
by frank
Notice the pusha at the very beginning of the function? That means that basically all registers are going to have their contents restored when the function exits. Now the way to solve this problem is to as the very last thing that you do before calling the isr_handler function you should try pushing a pointer to the stack to the stack. That way you can access all of the registers from the isr_handler function. If you don't want the code that explains what I said then stop reading here.



Then you just rewrite the isr_handler function so that it looks like this

Code: Select all

void isr_handler( struct REGS_S *registers )
REGS_S looks like this

Code: Select all

/// The value of the registers when the interrupt is fired
struct packed REGS_S
{
    DWORD ds;       ///< DS when the interrupt fired

    DWORD edi;      ///< EDI when the interrupt fired
    DWORD esi;      ///< ESI when the interrupt fired
    DWORD ebp;      ///< EBP when the interrupt fired
    DWORD esp;      ///< ESP when the interrupt fired
    DWORD ebx;      ///< EBX when the interrupt fired
    DWORD edx;      ///< EDX when the interrupt fired
    DWORD ecx;      ///< ECX when the interrupt fired
    DWORD eax;      ///< EAX when the interrupt fired

}; // end regs_t
Then you can edit the values that are in the registers after the interrupt returns like this

Code: Select all

registers->eax = value;
and last but not the least change isr_stub to look like this

Code: Select all

isr_common_stub:
   pusha                    ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

   mov ax, ds               ; Lower 16-bits of eax = ds.
   push eax                 ; save the data segment descriptor

   mov ax, 0x10  ; load the kernel data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   mov eax, esp  ; save the current esp as a pointer to the register stack
   push esp

   call isr_handler

   pop eax       ; just get rid of the pointer from the stack

   pop eax        ; reload the original data segment descriptor
   mov ds, ax
   mov es, ax
   mov fs, ax
   mov gs, ax

   popa                     ; Pops edi,esi,ebp...
   add esp, 8     ; Cleans up the pushed error code and pushed ISR number
   sti
   iret 
Try all of that and see if that helps any.