Page 1 of 1

GCC: extreme strange behavior of inline assemble: loads TSS

Posted: Wed Nov 02, 2011 2:56 am
by lemonyii
hi everyone!
it's long time ago i succeed loading tss. recently i'm working on smp so i have to copy or rewrite a lot of my previous work. This piece of code:

Code: Select all

void cpu_init(){
	......
	// load gdt and tss
	ulong gdtptr[2] = { (sizeof(me->gdt)-1)<<48, (ulong)&(me->gdt) };
	__asm( "lgdt (%%rax)" : : "a"( (ulong)gdtptr + 6 ) );
	__asm( "ltr %%ax" : : "a"( X64_SELECTOR_TSS ) );
        [b]// if i add some, maybe any function call here, it will work properly.[/b]
}
will result in a reset, in all qemu, vmware, bochs. and bochs says
00034055736e[CPU0 ] exception(): 3rd (14) exception with no resolution, shutdown status is 00h, resetting
so the #PF, have no reason to happen, i'm very sure about it. and the following situations proved it.

if i add some function right after the ltr instruction, it will work. ( i found this because i succeed at the begining, with a print function there to say "success" :mrgreen:. ) but some more inline asm will not solve.

and if i replace the inline asm with a asm function call ( fun is in a separate file), it will also work:

Code: Select all

void cpu_init(){
	......
	// load gdt and tss
	ulong gdtptr[2] = { (sizeof(me->gdt)-1)<<48, (ulong)&(me->gdt) };
	__asm( "lgdt (%%rax)" : : "a"( (ulong)gdtptr + 6 ) );
	//__asm( "ltr %%ax" : : "a"( X64_SELECTOR_TSS ) );
	loadtss(X64_SELECTOR_TSS);  [b]// a assembly function also works.[/b]
}
i'm not confident about my inline asm grammar, but i don't think i wrote wrong.
so i'm thinking if it is the problem of gcc, when i put an inline asm at the end of a function, it generated wrong code or sth?

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 3:02 am
by gerryg400
GCC may re-order or delete instructions. This applies to asm instructions. You may need to add 'volatile' to stop deletion or a constraint to stop re-ordering. Have a close look at the asm code that is generated. Then try it like this.

Code: Select all

void cpu_init(){
   ......
   // load gdt and tss
   ulong gdtptr[2] = { (sizeof(me->gdt)-1)<<48, (ulong)&(me->gdt) };
   __asm volatile ( "lgdt (%%rax)" : : "a"( (ulong)gdtptr + 6 ): "memory" );
   __asm volatile ( "ltr %%ax" : : "a"( X64_SELECTOR_TSS ): "memory" );
        [b]// if i add some, maybe any function call here, it will work properly.[/b]
}
The volatiles tell GCC that there are side effects and the code must not be deleted. The "memory" constraints tell GCC that the instructions modify memory and the order must not be changed. Of course the latter is a lie but it does the job.

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 6:27 am
by lemonyii
gerryg400, why are u always hit the right point? it's working now, and i'm going to check what it is like with out volatile and the : "memory" hint.
in fact i don't know much about inline assemble. i tried volatile before, but result in compile error because i put it before __asm :mrgreen: and the "memory" hint, i've never known it before.
thank you so much!

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 6:35 am
by xenos
gerryg400 wrote:The "memory" constraints tell GCC that the instructions modify memory and the order must not be changed. Of course the latter is a lie but it does the job.
Wouldn't this code do the job as well, without the memory constraints?

Code: Select all

void cpu_init(){
   ......
   // load gdt and tss
   ulong gdtptr[2] = { (sizeof(me->gdt)-1)<<48, (ulong)&(me->gdt) };
   __asm volatile (
      "lgdt (%0)\n"
      "ltr %1"
       : : "r"( (ulong)gdtptr + 6 ),
      "r"( X64_SELECTOR_TSS )
   );
}

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 6:43 am
by gerryg400
Yes, because GCC can see that the initialisation of gdtptr (in the definition) must come before it's used and it will not swap the 2 lines within the asm statement. However it might be good practice to add "memory" just in case some other code is later.

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 7:46 am
by lemonyii
XenOS wrote: Wouldn't this code do the job as well, without the memory constraints?
i tried, no. memory constraint is necessary.

Code: Select all

__asm volatile (
      "lgdt (%%rax)\n"
      "ltr %%bx"
       : : "a"( (ulong)gdtptr + 6 ),
      "b"( X64_SELECTOR_TSS )
   );
}
neither.
gerryg400 wrote:The "memory" constraints tell GCC that the instructions modify memory and the order must not be changed.
i think he says right, and the optimization(deleted and re-order) happens at assemble level, as this piece of code proved.

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 7:57 am
by lemonyii
AND, i tested one more thing: turning off optimization with -O0.
everything works fine with -O0. EVERYTHING, all codes posted here included.
but -O1 just like -O2 (my previous setting). and i think -O0 is not acceptable, and -O3 might be too dangerous for an OS :mrgreen: long time ago my -O3 code result in float instructions, so i never use it again, though i used -mno-mmx -mno-sse and so on since then.

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Wed Nov 02, 2011 11:53 am
by xenos
lemonyii wrote:i tried, no. memory constraint is necessary.
Indeed, you're right. I just tried it myself - with optimization disabled everything works fine even without the memory constraint, but with -O2 the assignment to gdtptr is simply omitted.

I also found the reason for this behavior. The register constraint "r"( (ulong)gdtptr + 6 ) only tells GCC that the value of gdtptr is needed - but GCC does not know that the content of the memory location at gdtptr is important as well. Therefore, it simply omits writing to this location, because it is never accessed explicitly (except by the lgdt instruction, of course). In fact, this is documented here:
GCC manual wrote:If your assembler instructions access memory in an unpredictable fashion, add `memory' to the list of clobbered registers. This will cause GCC to not keep memory values cached in registers across the assembler instruction and not optimize stores or loads to that memory. You will also want to add the volatile keyword if the memory affected is not listed in the inputs or outputs of the asm, as the `memory' clobber does not count as a side-effect of the asm. If you know how large the accessed memory is, you can add it as input or output but if this is not known, you should add `memory'. As an example, if you access ten bytes of a string, you can use a memory input like:

Code: Select all

     {"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}
Note that in the following example the memory input is necessary, otherwise GCC might optimize the store to x away:

Code: Select all

     int foo ()
     {
       int x = 42;
       int *y = &x;
       int result;
       asm ("magic stuff accessing an 'int' pointed to by '%1'"
             "=&d" (r) : "a" (y), "m" (*y));
       return result;
     }

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Thu Nov 03, 2011 7:51 am
by Owen
Use

Code: Select all

struct GDTR __attribute__((packed)) {
    uint16_t length;
    void* base; 
};

    GDTR gdtptr;
    // Fill here

__asm volatile (
          "lgdt %[gdtptr]\n"
          "ltr %[selector]"
    : 
    : [gdtptr] "m" (gdtptr)
    , [selector] "r" ( X64_SELECTOR_TSS )
   );
}
1. Using "m" tells the compiler precisely what portion of memory we are dealing with. In this case you need to pair it up with a structure. It can additionally now optimize better, e.g. elide use of a register for the address in favor of substituting in e.g. "28(%rsp)"
2. Using "r" gives the compiler more optimization flexibility.

Now, optimization is probably not a big issue here, but a good habit to get into

Re: GCC: extreme strange behavior of inline assemble: loads

Posted: Thu Nov 03, 2011 10:18 am
by lemonyii
nice, i learnt a lot once more. thank you all!