为什么 ARM gcc 在函数开始时将寄存器 r3 和 lr 压入堆栈?
Why ARM gcc push register r3 and lr into stack at the beginning of a function?
我试过像这样写一个简单的测试代码(main.c):
main.c
void test(){
}
void main(){
test();
}
然后我用arm-non-eabi-gcc编译,objdump得到汇编代码:
arm-none-eabi-gcc -g -fno-defer-pop -fomit-frame-pointer -c main.c
arm-none-eabi-objdump -S main.o > output
汇编代码会压入 r3 和 lr 寄存器,即使函数什么也没做。
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test>:
void test(){
}
0: e12fff1e bx lr
00000004 <main>:
void main(){
4: e92d4008 push {r3, lr}
test();
8: ebfffffe bl 0 <test>
}
c: e8bd4008 pop {r3, lr}
10: e12fff1e bx lr
我的问题是为什么 arm gcc 选择将 r3 压入堆栈,甚至 test() 函数也从不使用它? gcc 是否只是随机选择 1 个寄存器来推送?
如果是为了堆栈对齐(ARM 为 8 字节)要求,为什么不直接减去 sp?谢谢。
==================更新==========================
@KemyLand 对于你的回答,我还有另一个例子:
源码为:
void test1(){
}
void test(int i){
test1();
}
void main(){
test(1);
}
我使用上面相同的编译命令,得到如下程序集:
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test1>:
void test1(){
}
0: e12fff1e bx lr
00000004 <test>:
void test(int i){
4: e52de004 push {lr} ; (str lr, [sp, #-4]!)
8: e24dd00c sub sp, sp, #12
c: e58d0004 str r0, [sp, #4]
test1();
10: ebfffffe bl 0 <test1>
}
14: e28dd00c add sp, sp, #12
18: e49de004 pop {lr} ; (ldr lr, [sp], #4)
1c: e12fff1e bx lr
00000020 <main>:
void main(){
20: e92d4008 push {r3, lr}
test(1);
24: e3a00001 mov r0, #1
28: ebfffffe bl 4 <test>
}
2c: e8bd4008 pop {r3, lr}
30: e12fff1e bx lr
如果第一个例子中的 push {r3, lr} 是为了使用更少的指令,为什么在这个函数 test() 中,编译器不只使用一条指令?
push {r0, lr}
它使用 3 条指令而不是 1 条。
push {lr}
sub sp, sp #12
str r0, [sp, #4]
顺便说一下,为什么它用 12 来替换 sp,堆栈是 8 字节对齐的,它可以用 4 来替换它吗?
根据Standard ARM Embedded ABI,r0
到r3
用于传递参数给函数,其值return,同时lr
(a.k.a: r14
) 是link寄存器,其目的是保存函数的return地址。
显然 lr
必须保存,否则 main()
将无法 return 给它的调用者。
现在臭名昭著的是每条 ARM 指令占用 32 位,正如您提到的,ARM 有 8 字节的调用堆栈对齐要求。而且,作为奖励,我们正在使用 Embedded ARM ABI,因此代码大小将得到优化。因此,通过压入一个未使用的寄存器(不需要 r3
,因为 test()
既不接受参数也不接收 lr
和对齐堆栈,让单个 32 位指令更有效它 return 是任何东西),然后弹出一条 32 位指令,而不是添加更多指令(因此,浪费宝贵的内存!)来操作堆栈指针。
毕竟,得出这只是 GCC 的优化的结论是合乎逻辑的。
我试过像这样写一个简单的测试代码(main.c):
main.c
void test(){
}
void main(){
test();
}
然后我用arm-non-eabi-gcc编译,objdump得到汇编代码:
arm-none-eabi-gcc -g -fno-defer-pop -fomit-frame-pointer -c main.c
arm-none-eabi-objdump -S main.o > output
汇编代码会压入 r3 和 lr 寄存器,即使函数什么也没做。
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test>:
void test(){
}
0: e12fff1e bx lr
00000004 <main>:
void main(){
4: e92d4008 push {r3, lr}
test();
8: ebfffffe bl 0 <test>
}
c: e8bd4008 pop {r3, lr}
10: e12fff1e bx lr
我的问题是为什么 arm gcc 选择将 r3 压入堆栈,甚至 test() 函数也从不使用它? gcc 是否只是随机选择 1 个寄存器来推送? 如果是为了堆栈对齐(ARM 为 8 字节)要求,为什么不直接减去 sp?谢谢。
==================更新==========================
@KemyLand 对于你的回答,我还有另一个例子: 源码为:
void test1(){
}
void test(int i){
test1();
}
void main(){
test(1);
}
我使用上面相同的编译命令,得到如下程序集:
main.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <test1>:
void test1(){
}
0: e12fff1e bx lr
00000004 <test>:
void test(int i){
4: e52de004 push {lr} ; (str lr, [sp, #-4]!)
8: e24dd00c sub sp, sp, #12
c: e58d0004 str r0, [sp, #4]
test1();
10: ebfffffe bl 0 <test1>
}
14: e28dd00c add sp, sp, #12
18: e49de004 pop {lr} ; (ldr lr, [sp], #4)
1c: e12fff1e bx lr
00000020 <main>:
void main(){
20: e92d4008 push {r3, lr}
test(1);
24: e3a00001 mov r0, #1
28: ebfffffe bl 4 <test>
}
2c: e8bd4008 pop {r3, lr}
30: e12fff1e bx lr
如果第一个例子中的 push {r3, lr} 是为了使用更少的指令,为什么在这个函数 test() 中,编译器不只使用一条指令?
push {r0, lr}
它使用 3 条指令而不是 1 条。
push {lr}
sub sp, sp #12
str r0, [sp, #4]
顺便说一下,为什么它用 12 来替换 sp,堆栈是 8 字节对齐的,它可以用 4 来替换它吗?
根据Standard ARM Embedded ABI,r0
到r3
用于传递参数给函数,其值return,同时lr
(a.k.a: r14
) 是link寄存器,其目的是保存函数的return地址。
显然 lr
必须保存,否则 main()
将无法 return 给它的调用者。
现在臭名昭著的是每条 ARM 指令占用 32 位,正如您提到的,ARM 有 8 字节的调用堆栈对齐要求。而且,作为奖励,我们正在使用 Embedded ARM ABI,因此代码大小将得到优化。因此,通过压入一个未使用的寄存器(不需要 r3
,因为 test()
既不接受参数也不接收 lr
和对齐堆栈,让单个 32 位指令更有效它 return 是任何东西),然后弹出一条 32 位指令,而不是添加更多指令(因此,浪费宝贵的内存!)来操作堆栈指针。
毕竟,得出这只是 GCC 的优化的结论是合乎逻辑的。