ARM汇编语言(3)
这次我们一起探索ARM程序的更多细节。上边我们分析到了一个函数,看到是用寄存器来传递参数。但寄存器总是有限不可能总用寄存器,以前fast调用程序好像前3个参数使用寄存器,在PPC上,这应该是类似的。为此,我改了上次那个函数,因为MessageBoxW函数4个参数仍然用寄存器传递,所以直接从5个参数的程序开始分析。修改后代码如下:
int WINAPI MyFunc(int,int,int,int,int);
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
int a;
int b=4;
// TODO: Place code here.
MessageBox(0,_T("helloWorld"),_T("hello"),MB_OK);
int i=MyFunc(1,2,3,4,5);
a=b+i;
return 0;
}
int WINAPI MyFunc(int a,int b,int c,int d2,int e)
{
int d =a+b+c+d2+e;
return d;
}
先看看调用部分代码:
var_4 = -4
MOV R3, #5
STR R3, [SP,#4+var_4] ;开始操作栈了
MOV R2, #3 ;前四个参数还是用寄存器传递
MOV R3, #4
MOV R1, #2
MOV R0, #1
BL sub_1104C ;Call我们的子程序
MOV R0, #0
我们看到,第5个参数已经开始使用堆栈做传递了
STR R3, [SP,#4+var_4]
这句的作用是将R3的值放入SP+#4+var_4所指的地址中。var_4是个常数是-4,所以这句等价于STR R3,[SP]
然后让我们看子程序部分的反汇编代码
sub_1104C ; CODE XREF: WinMain+34 p
arg_0 = 0
ADD R0, R0, R1
ADD R1, R0, R2
LDR R0, [SP,#arg_0]
ADD R2, R1, R3
ADD R0, R2, R0
RET
; End of function sub_1104C
结果多少让我有些惊讶,因为子程序跟主程序公用了一个堆栈,并且切入子程序时也没有保存栈顶指针,这样如果子程序崩溃,则整个程序的栈操作必然乱套。难道不会出现不稳定的情况?或许是没有操作堆栈的缘故?并且几乎所有的运算的结果都使用的寄存器。代码相当高效(甚至我想不出如何写比这更高效的程序)。这(RISC代码容易优化)就是RISC的优势所在吧,总之代码比x86下边VC++编译器优化的要好。
为了难为一下编译器,我改的代码稍微麻烦了一点:
int WINAPI MyFunc(int,int,int,int,int,int);
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
int a;
int b=4;
// TODO: Place code here.
MessageBox(0,_T("helloWorld"),_T("hello"),MB_OK);
int i=MyFunc(1,2,3,4,5,6);
a=b+i;
return 0;
}
int WINAPI MyFunc(int a,int b,int c,int d2,int e,int f)
{
int g=3;
int h=4;
int i=6;
int j=9;
int k=i*j;
int d =a+b+c+d2+e+f+g+h;
return (d+k);
}
现在参数我改成了6个,并且我加了几个中间变量,以便把栈操作逼出来,调用处的代码在意料之中,最后2个参数使用了栈传递,而前四个参数使用了寄存器传递。看来可确定,PPC的API调用约定是:前四个参数分别用R0,R1,R2,R3,多余的参数从右向左依次入栈。
.text:0001101C MOV R3, #6
.text:00011020 MOV R0, #5
.text:00011024 STR R3, [SP,#8+var_4]
.text:00011028 STR R0, [SP,#8+var_8]
.text:0001102C MOV R3, #4
.text:00011030 MOV R2, #3
.text:00011034 MOV R1, #2
.text:00011038 MOV R0, #1
.text:0001103C BL sub_11054
.text:00011040 MOV R0, #0
而接下来子程序的反汇编代码,多少让我有些吃惊:
.text:00011054 sub_11054
.text:00011054
.text:00011054 arg_0 = 0
.text:00011054 arg_4 = 4
.text:00011054
.text:00011054 ADD R0, R0, R1
.text:00011058 ADD R1, R0, R2
.text:0001105C LDR R0, [SP,#arg_0]
.text:00011060 ADD R2, R1, R3
.text:00011064 LDR R1, [SP,#arg_4]
.text:00011068 ADD R3, R2, R0
.text:0001106C ADD R0, R3, R1
.text:00011070 ADD R0, R0, #0x3D
.text:00011074 RET
.text:00011074 ; End of function sub_11054
太智能了!不但没把栈操作逼出来,而且在编译时自动作了尽可能多的运算大大减少了实际运行时的效率!但这也让我想到了让编译器操作栈的方法,我把子程序代码改为如下:
int WINAPI MyFunc(int a,int b,int c,int d2,int e,int f)
{
int g=3;
int h=4;
int i=6;
int j=9*c;
int k=i*f;
int d =a+b+c+d2+e+f+g+h;
return (d+k);
}
这次编译器撑不住了,终于开始使用栈:
sub_11054 ; CODE XREF: WinMain+3C p
arg_0 = 0
arg_4 = 4
STMFD SP!, {R4-R6,LR} ;首先r4,r5,r6,lr分别入栈(保存现场)
MOV R4, R0 ;用r4,r5,r6暂存r0-r2的值
MOV R5, R1
MOV R6, R2
LDR R0, [SP,#0x10+arg_4]
ADD R1, R0, #1
RSB R2, R1, R1,LSL#3 ;r2=(r1<<3)-r1
ADD R0, R2, R4
ADD R1, R0, R5
LDR R0, [SP,#0x10+arg_0]
ADD R2, R1, R6
ADD R3, R2, R3
ADD R0, R3, R0
LDMFD SP!, {R4-R6,PC} ;恢复r4-r6的值并离开函数。
; End of function sub_11054
算法我就没必要写分析了,我说一下这次的堆栈操作:
这次仍然没有用栈做中间变量,而是多用了3个通用寄存器。而只有在进入与离开时操作下内存。这样能大大的减少内存的访问率,加快运行效率,微软的代码优化做的确实非常的好。
基本上函数的入口与出口就是这种形式了,下篇研究下字串的调用。