博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
FP寄存器及frame pointer介绍
阅读量:2457 次
发布时间:2019-05-11

本文共 4779 字,大约阅读时间需要 15 分钟。

 

理论上来说,ARM的15个通用寄存器是通用的,但实际上并非如此,特别是在过程调用的过程中。
PCS(Procedure Call Standard for Arm architecture)就定义了过程调用中,寄存器的特殊用途。
Role in the procedure call standard
r15 PC The Program Counter.
r14 LR The Link Register.
r13 SP The Stack Pointer.
r12 IP The Intra-Procedure-call scratch register. (可简单的认为暂存SP)
实际上,还有一个r11是optional的,被称为FP,即frame pointer。
1,stack frame
stack我们都知道,每一个进程都有自己的栈。考虑进程执行时发生函数调用的场景,母函数和子函数使用的是同一个栈,在通常的情况下,我们并不需要区分母函数和子函数分别使用了栈的哪个部分。但是,当我们需要在执行过程中对函数调用进行backtrace的时候,这一信息就很重要了。
简单的说,stack frame就是一个函数所使用的stack的一部分,所有函数的stack frame串起来就组成了一个完整的栈。stack frame的两个边界分别由FP和SP来限定。
 
2,backtrace
在程序执行过程中(通常是发生了某种意外情况而需要进行调试),通过SP和FP所限定的stack frame,就可以得到母函数的SP和FP,从而得到母函数的stack frame(PC,LR,SP,FP会在函数调用的第一时间压栈),以此追溯,即可得到所有函数的调用顺序。
3,gcc关于stack frame的优化选项
看起来FP只是在backtrace的时候有用,所以如果我们没有backstrace的需求,我们是否可以不使用FP。
其实gcc就有一个关于stack frame的优化选项:
-fomit-frame-pointer
=================================================================================
Don't keep the frame pointer in a register for functions that don't need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines.

On some machines, such as the VAX, this flag has no effect, because the standard calling sequence automatically handles the frame pointer and nothing is saved by pretending it doesn't exist. The machine-description macro "FRAME_POINTER_REQUIRED" controls whether a target machine supports this flag.

==================================================================================

这里引用别人关于这一参数的实验,自己就不做了。

从实验可以看出,优化后的差别是相当明显的。当然,具体能带来多大的性能提升,不好界定。

另外,x86中EBP寄存器相当于ARM中的FP寄存器。

==================================================================================

环境:X86+Redhat 9.0,gcc 3.2.2

源文件如下:

$ cat test.c 

void a(unsigned long a, unsigned int b)
{
        unsigned long i;
        unsigned int j;

        i = a;

        j = b;

        i++;

        j += 2;

}

默认编译选项:

$ gcc -c test.c -o with_SFP.o

反汇编后是这个样子:

$ objdump -D with_SFP.o

with_SFP.o:     file format elf32-i386

Disassembly of section .text:

00000000 <a>:

   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 08                sub    $0x8,%esp
   6:   8b 45 08                mov    0x8(%ebp),%eax
   9:   89 45 fc                mov    %eax,0xfffffffc(%ebp)
   c:   8b 45 0c                mov    0xc(%ebp),%eax
   f:   89 45 f8                mov    %eax,0xfffffff8(%ebp)
  12:   8d 45 fc                lea    0xfffffffc(%ebp),%eax
  15:   ff 00                   incl   (%eax)
  17:   8d 45 f8                lea    0xfffffff8(%ebp),%eax
  1a:   83 00 02                addl   $0x2,(%eax)
  1d:   c9                      leave  
  1e:   c3                      ret    
Disassembly of section .data:

可以看到函数ENTER时首先把上一层函数的EBP入栈,设置本函数的EBP,然后会根据临时变量的数量和对齐要求去设置ESP,也就产生了函数的stack frame。

我们再看看函数的返回:"leave"指令相当于"mov %ebp,%esp;pop %ebp",也就是ENTER是两条指令的恢复过程,所以,后面的"ret"指令和"call"指令对应。
这里backtrace就可以根据现有函数EBP指针得知上一个函数的EBP----栈底再往上保存着上一个函数的EBP和EIP,然后就可以得知函数调用的路径。

SFP是可以在编译时候优化掉的,用"-fomit-frame-pointer"选项

编译:

$ gcc -fomit-frame-pointer -c test.c -o no_SFP.o

$ objdump -D no_SFP.o

no_SFP.o:     file format elf32-i386

Disassembly of section .text:

00000000 <a>:

   0:   83 ec 08                sub    $0x8,%esp
   3:   8b 44 24 0c             mov    0xc(%esp,1),%eax
   7:   89 44 24 04             mov    %eax,0x4(%esp,1)
   b:   8b 44 24 10             mov    0x10(%esp,1),%eax
   f:   89 04 24                mov    %eax,(%esp,1)
  12:   8d 44 24 04             lea    0x4(%esp,1),%eax
  16:   ff 00                   incl   (%eax)
  18:   89 e0                   mov    %esp,%eax
  1a:   83 00 02                addl   $0x2,(%eax)
  1d:   83 c4 08                add    $0x8,%esp
  20:   c3                      ret    
Disassembly of section .data:

这里把EBP省掉了,ESP兼职了EBP的部分工作(索引临时变量)。
显而易见,代码难懂了;-P, 代码执行长度缩短了,应该能引起效率的提升。 可恶的是,不能用backtrace调试了。

看一下arm下面的情况:

含有SFP的版本:
$ arm-linux-objdump -D SFP_arm.o

SFP_arm.o :     file format elf32-littlearm

Disassembly of section .text:

00000000 <a>:

   0:   e1a0c00d        mov     ip, sp
   4:   e92dd800        stmdb   sp!, {fp, ip, lr, pc}
   8:   e24cb004        sub     fp, ip, #4      ; 0x4
   c:   e24dd010        sub     sp, sp, #16     ; 0x10
  10:   e50b0010        str     r0, [fp, -#16]
  14:   e50b1014        str     r1, [fp, -#20]
  18:   e51b3010        ldr     r3, [fp, -#16]
  1c:   e50b3018        str     r3, [fp, -#24]
  20:   e51b3014        ldr     r3, [fp, -#20]
  24:   e50b301c        str     r3, [fp, -#28]
  28:   e51b3018        ldr     r3, [fp, -#24]
  2c:   e2833001        add     r3, r3, #1      ; 0x1
  30:   e50b3018        str     r3, [fp, -#24]
  34:   e51b301c        ldr     r3, [fp, -#28]
  38:   e2833002        add     r3, r3, #2      ; 0x2
  3c:   e50b301c        str     r3, [fp, -#28]
  40:   e91ba800        ldmdb   fp, {fp, sp, pc}
Disassembly of section .data:

优化后的版本:

$ arm-linux-objdump -D no_SFP_arm.o

no_SFP_arm.o:     file format elf32-littlearm

Disassembly of section .text:

00000000 <a>:

   0:   e24dd010        sub     sp, sp, #16     ; 0x10
   4:   e58d000c        str     r0, [sp, #12]
   8:   e58d1008        str     r1, [sp, #8]
   c:   e59d300c        ldr     r3, [sp, #12]
  10:   e58d3004        str     r3, [sp, #4]
  14:   e59d3008        ldr     r3, [sp, #8]
  18:   e58d3000        str     r3, [sp]
  1c:   e59d3004        ldr     r3, [sp, #4]
  20:   e2833001        add     r3, r3, #1      ; 0x1
  24:   e58d3004        str     r3, [sp, #4]
  28:   e59d3000        ldr     r3, [sp]
  2c:   e2833002        add     r3, r3, #2      ; 0x2
  30:   e58d3000        str     r3, [sp]
  34:   e28dd010        add     sp, sp, #16     ; 0x10
  38:   e1a0f00e        mov     pc, lr
Disassembly of section .data:

这里,"fp"充当了"EBP"的角色,ESP在X86里面被leave隐含的恢复好了,所以没有显示设置的必要。

看起来arm平台上"-fomit-frame-pointer"选项的优化作用更加明显。 

转载地址:http://fashb.baihongyu.com/

你可能感兴趣的文章
不同管理岗层级的团队影响力_高影响力团队的最高要求
查看>>
j pocket_Wallabag:Pocket的开源替代品
查看>>
cms系统和管理员系统区别_如何成为懒惰的系统管理员
查看>>
dropbox内容更改_Dropbox替代品,Git技巧,Linux技巧,DevOps必须阅读的内容等等
查看>>
pandoc epub_使用Pandoc将您的书变成网站和ePub
查看>>
开源 计划管理_公司开源计划的三大好处
查看>>
devops java使用_谁会在使用DevOps时最大程度地退缩?
查看>>
java 补丁差异_差异和补丁简介
查看>>
python django_8个Python软件包将简化Django的生活
查看>>
shell 点文件_Shell点文件可以为您做什么
查看>>
zsh 简单高效使用技巧_使用zsh提高生产力的5个技巧
查看>>
perl子例程_子例程签名在Perl 6中如何工作
查看>>
开源 js图形_年度计算机图形会议鼓励使用开源
查看>>
多步骤建立镜像_通过四个步骤建立更多可信赖的团队
查看>>
linux中top命令_在Linux中使用top命令的提示
查看>>
管理工具 tools不可用_赶紧为管理员准备的10种杀手tools工具
查看>>
sysadmin默认密码_sysadmin的SELinux指南:42个主要问题的答案
查看>>
sysadmin默认密码_sysadmin的Ansible指南:如何简化任务
查看>>
linux开源游戏_适用于Linux的5个开源策略和模拟游戏
查看>>
开源语法解释器_抓住机会解释开源
查看>>