Page 1 of 1

Double fault without error code after sti

Posted: Sat May 25, 2013 1:30 am
by olindung
I'm writing this post to share my experience on this strange bug.

In the begining, I didn't enable gcc's optimization feature(-O). Just yesterday I added -O2 to my CFLAGS and the kernel came to be crazy. One of the bugs was some time after sti, a double fault was raised by the CPU, what's strange is that no error code was pushed. This happends both in bochs and qemu. I checked the idt and gdt, everything is just fine.

When I was STFW, I found it has something to do with the PIC, so I checked the disassembly code and found the code fragment of initialize 8259 was optimized by gcc which I wrote in inline asm without add volatile modifier. After I add "volatile", #DF is gone.

What I learned from this is:
1. Enable optimizaion feature of compiler at the begining.
2. volatile come with asm togehter when writing inline asm.

Re: Double fault without error code after sti

Posted: Sat May 25, 2013 1:48 am
by bluemoon
olindung wrote:found the code fragment of initialize 8259 was optimized by gcc which I wrote in inline asm without add volatile modifier. After I add "volatile", #DF is gone.
You should tell gcc the input, output and clobber list, this this the proper way to avoid code elimination.
volatile meant something different, it just happen to prevent code from eliminated in this case.

I suggest you read the gcc inline assembly manual.

Re: Double fault without error code after sti

Posted: Sat May 25, 2013 3:29 am
by olindung
bluemoon wrote:volatile meant something different, it just happen to prevent code from eliminated in this case.
Thanks for the reply.

I read the manual.

here is two piece I quoted from the manual
You can prevent an asm instruction from being deleted by writing the keyword volatile after the asm.
...
An asm instruction without any output operands is treated identically to a volatile asm instruction.
I reviewed my code

Code: Select all

static inline void
outb(unsigned short port, unsigned char value)
{
     asm("outb %1,%0;" : "=d"(port) : "r"(value));
}
void pic_init(void)
{
     /* set mask */
     outb(0x21, 0xff);
     outb(0xa1, 0xff);
......
}
I inproperly put port into the output list, and I think gcc found the output port does not referenced by anyone, so it just be eliminated.

so, according to the manual

Code: Select all

asm("outb %0, %1;" ::"a"(value), "d"(port));
asm volatile("outb %0, %1;" ::"a"(value), "d"(port));
take the same effects, and this should be the correct implemetation of function outb

Re: Double fault without error code after sti

Posted: Sat May 25, 2013 4:17 am
by sortie
The compiler is smart. It doesn't understand assembly statements at all (how could it?), so you have to tell it what these statements calculate and what registers it needs for it and whether it accesses memory. The compiler is then able to cleverly optimize the statement like the rest of your code. However, if you lie, the compiler may deduce that the assembly statement is not needed. For instance, if you have an assembly statement with only inputs and no outputs, sees a statement that does nothing (such as if you wrote if ( false ) { foo(); }) and the compiler will just remove it. You should tell that the assembly statement has side effects by using the clobber list to modify memory if it does that. In rare cases, your assembly statement has side effects that cannot be expressed otherwise (such as reloading the interrupt vector pointer), in those cases the volatile keyword comes to your rescue as it tells you that the assembly statement has side effects. However, in general, it's best to use the clobber list and output list to let the compiler know what side effects your assembly statement has, so it better can optimize it (and even delete it, if it doesn't do anything).

Additionally, you should carefully specify the clobber list. I think it assumes all output registers are clobbered, but not the input registers, or something like that. Any additional registers you use must be specified as clobbered. Otherwise, the compiler won't know it has to reload the registers and you experience strange corruption bugs. Be sure to read the inline assembly documentation.

I agree, building with optimizations on is important from the start. I recommend building with them on mostly, and every once in a while also testing that it works without optimizations. If you suspect anything is wrong in this regard, check the disassembly and whether it makes sense.