上一篇中讲了64位程序函数的调用情况, 这一篇我们来看32位程序的调用规则, 如果还没有看过上一篇的, 先阅读完上篇再读本文

x86程序分析

默认gcc编译成64位程序, 加上-m32参数可编译为32位程序

-Og优化级别下

首先看add函数

subl  $60, %esp
/* 栈保护代码
movl $20, %edx
movl %gs:(%edx), %eax
movl %eax, 44(%esp)
*/
xorl %eax, %eax
movl 68(%esp), %eax // a
addl 64(%esp), %eax // b
addl 12(%esp), %eax // array[2]
addl 8(%esp), %eax // array[1]
/* 栈保护代码
movl 44(%esp), %ecx
xorl %gs:(%edx), %ecx
jne .L4
*/
addl $60, %esp
ret

可以看出和64位程序差别不大, 只是在获取参数的时候使用了栈上的值而不是寄存器

再看main函数

leal  4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %esi
pushl %ebx
pushl %ecx

subl $36, %esp
xorl %eax, %eax
movl $4, -40(%ebp)
movl $5, -36(%ebp)
movl $6, -32(%ebp)
pushl $8
pushl $7
call add
addl $12, %esp
pushl %eax
leal .LC0@GOTOFF(%ebx), %eax
pushl %eax
pushl $1
call __printf_chk@PLT
addl $16, %esp

main函数前面一段多了很多奇怪的指令, ebp这一套上一篇已经说过了, 这里即使开启了优化也保留了ebp

而后面几条pushl保存了几个寄存器的值, 属于Callee Saved, 函数运行结束时会popl还原

前三条命令有点迷惑, 查了下资料看到了一些解释

leal 4(%esp), %ecxmain函数第一个参数放入寄存器ecx中, 便于后面访问

andl $-16, %espesp地址与16字节对齐(-16 = 0xfffffff0)

pushl -4(%ecx) 存储函数的返回地址, 暂不知道作用

后面调用函数时并没有将参数压入寄存器, 而是使用push命令压栈, 函数调用结束后使用add命令释放, 这点和64位下多参数情况一致

无优化下

add函数

pushl %ebp
movl %esp, %ebp
subl $56, %esp
xorl %eax, %eax
movl 8(%ebp), %edx // a
movl 12(%ebp), %eax // b
addl %eax, %edx
movl -44(%ebp), %eax // array[2]
addl %eax, %edx
movl -48(%ebp), %eax // array[1]
addl %edx, %eax
movl -12(%ebp), %ecx
leave
ret

可以看出仅仅是使用了ebp

再看main函数

leal  4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
subl $32, %esp
xorl %eax, %eax
movl $4, -24(%ebp)
movl $5, -20(%ebp)
movl $6, -16(%ebp)
movl $7, -32(%ebp)
movl $8, -28(%ebp)
subl $8, %esp
pushl -28(%ebp)
pushl -32(%ebp)
call add
addl $16, %esp
subl $8, %esp
pushl %eax
leal .LC0@GOTOFF(%ebx), %eax
pushl %eax
call printf@PLT

可以看到main函数和优化过的没有大的区别, 只是printf并没有被优化

总结

总的分析下来, 64位程序和32位程序差别不大, 仅是参数传递的差异, 而当64位参数过多时, 也会采用32位压栈的方式传参

而不同优化级别下, 最大的差别就是使用rsp作为函数基地址还是rbp作为函数基地址

转载申请

知识共享许可协议

本文知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接