gcc优化导致的错误

来源:互联网 发布:域名期限查询 编辑:程序博客网 时间:2024/05/06 21:01

在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的值覆盖了,调用结束后寄存中将得不到正确的值。

至此,原因找到了。

看来,关键时刻还得汇编啊。