1. 程式人生 > >gcc優化導致的錯誤

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的值覆蓋了,呼叫結束後寄存中將得不到正確的值。

至此,原因找到了。

看來,關鍵時刻還得彙編啊。