gcc優化導致的錯誤
在MyOS中,有這樣一段系統呼叫程式碼:
void sys_win_draw(regs_t reg)
{
HWND hWnd = (HWND)reg.ebx;
unsigned long* buffer = (unsigned long*)reg.ecx;
DrawWindow(hWnd, buffer);
}
平時都很正常,今天想測試一下效率,就寫了個迴圈呼叫,結果呼叫次數老是不對,令我十分奇怪。
如果把程式碼改成這樣,
void sys_win_draw(regs_t reg)
{
HWND hWnd = (HWND)reg.ebx;
unsigned long* buffer = (unsigned long*)reg.ecx;
DrawWindow(hWnd, buffer);
reg.eax = 0;
}
其實就是在DrawWindow下加一條語句,隨便什麼都行,程式碼就沒問題。
在百思不得其解的情況下,查看了兩種情況下的彙編語句,真相終於大白:
我們先看正確的程式碼,彙編如下:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 32(%ebp), %eax
pushl %eax
movl 24(%ebp), %eax
pushl %eax
call _DrawWindow
leave
ret
程式碼很容易理解,建立堆疊框架,然後從引數中取值並壓入堆疊作為DrawWindow的引數。
下面是錯誤的程式碼:
pushl %ebp
movl %esp, %ebp
movl 32(%ebp), %eax
movl %eax, 12(%ebp)
movl 24(%ebp), %eax
movl %eax, 8(%ebp)
popl %ebp
jmp _DrawWindow
同樣從堆疊中取出引數,但沒有壓入堆疊中,而是直接又賦給了自己的第一和第二個引數,最後
直接就Jmp到DrawWindow中。
本來這樣的優化是沒有錯的,而且效率還很高,但在MyOS的系統呼叫中就不對了,具體如下:
reg_t的結構如下所示
unsigned edi, esi, ebp, esp, ebx, edx, ecx, eax; //by pusha
unsigned gs, fs, es, ds;
unsigned eip, cs, eflags, user_esp, user_ss;
其中的引數都是系統重要的暫存器引數,視呼叫函式不同,eax,ebx,ecx,edx,esi,edi作為引數,
但處了eax最後要作為返回值返回給應用程式外,其它的值都要在系統呼叫結束後從堆疊恢復到各暫存器,
而上面的優化中esi和edi的值被ebx和ecx的值覆蓋了,呼叫結束後寄存中將得不到正確的值。
至此,原因找到了。
看來,關鍵時刻還得彙編啊。