在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的值覆盖了,调用结束后寄存中将得不到正确的值。
至此,原因找到了。
看来,关键时刻还得汇编啊。