百篇博客系列篇.本篇为:

硬件架构相关篇为:

汇编如何传复杂的参数?

  • v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

    汇编基础篇中很详细的介绍了一段具有代表性很经典的汇编代码,有循环,有判断,有运算,有多级函数调用。但有一个问题没有涉及,就是很复杂的参数如何处理?

    在实际开发过程中函数参数往往是很复杂的参数,(比如结构体)汇编怎么传递呢?

    先看一段C语言及汇编代码,传递一个稍微复杂的参数来说明汇编传参的过程
  1. #include <stdio.h>
  2. #include <math.h>
  3. struct reg{//参数远超寄存器数量
  4. int Rn[100];
  5. int pc;
  6. };
  7. int framePoint(reg cpu)
  8. {
  9. return cpu.Rn[0] * cpu.pc;
  10. }
  11. int main()
  12. {
  13. reg cpu;
  14. cpu.Rn[0] = 1;
  15. cpu.pc = 2;
  16. return framePoint(cpu);
  17. }
  1. //编译器: armv7-a gcc (9.2.1)
  2. framePoint(reg):
  3. sub sp sp #16 @申请栈空间
  4. str fp [sp #-4]! @保护main函数栈帧,等同于push {fp}
  5. add fp sp #0 @fp变成framePoint栈帧,同时也指向了栈顶
  6. add ip fp #4 @定位到入栈口,让4个参数依次入栈
  7. stm ip {r0 r1 r2 r3}@r0-r3入栈保存
  8. ldr r3 [fp #4] @取值cpu.pc = 2
  9. ldr r2 [fp #404] @取值cpu.Rn[0] = 1
  10. mul r3 r2 r3 @cpu.Rn[0] * cpu.pc
  11. mov r0 r3 @返回值由r0保存
  12. add sp fp #0 @重置sp,和add fp, sp, #0配套出现
  13. ldr fp [sp], #4 @恢复main函数栈帧
  14. add sp sp #16 @归还栈空间,sp回落到main函数栈顶位置
  15. bx lr @跳回main函数
  16. main:
  17. push {fp lr} @入栈保存调用函数现场
  18. add fp sp #4 @fp指向sp+4,即main栈帧的底部
  19. sub sp sp #800 @分配800个线性地址,即main栈帧的顶部
  20. mov r3 #1 @r3 = 1
  21. str r3 [fp #-408] @将1放置 fp-408处,即:cpu.Rn[0]处
  22. mov r3 #2 @r3 = 2
  23. str r3 [fp #-8] @将2放置 fp-8处,即:cpu.pc
  24. mov r0 sp @r0 = sp
  25. sub r3 fp #392 @r3 = fp - 392
  26. mov r2 #388 @只拷贝388,剩下4个由寄存器传参
  27. mov r1 r3 @保存由r1保存r3,用于memcpy
  28. bl memcpy @拷贝结构体部分内容,将r1的内容拷贝r2的数量到r0
  29. sub r3 fp #408 @定位到结构体剩余未拷贝处
  30. ldm r3 {r0 r1 r2 r3} @将剩余结构体内容通过寄存器传参
  31. bl framePoint(reg) @执行framePoint
  32. mov r3 r0 @返回值给r3
  33. nop @用于程序指令的对齐
  34. mov r0 r3 @再将返回值给r0
  35. sub sp fp #4 @恢复SP值
  36. pop {fp lr} @出栈恢复调用函数现场
  37. bx lr @跳回调用函数

两个函数对应两段汇编,干净利落,去除中间各项干扰,只有一个结构体reg,以下详细讲解如何传递它,以及它在栈中的数据变化是怎样的?

入参方式

结构体中共101个栈空间(一个栈空间单位四个字节),对应就是404个线性地址.

main上来就申请了 sub sp, sp, #800 @申请800个线性地址给main,即 200个栈空间

  1. int main()
  2. {
  3. reg cpu;
  4. cpu.Rn[0] = 1;
  5. cpu.pc = 2;
  6. return framePoint(cpu);
  7. }

但main函数只有一个变量,只需101个栈空间,其他都算上也用不了200个.为什么要这么做呢?

而且注意下里面的数字 388, 408, 392 这些都是什么意思?

看完main汇编能得到一个结论是 200个栈空间中除了存放了main函数本身的变量外 ,还存放了要传递给framePoint函数的部分参数值,存放了多少个?答案是 388/4 = 97个. 注意变量没有共用,而是拷贝了一部份出来.如何拷贝的?继续看

memcpy汇编调用

  1. mov r0 sp @r0 = sp
  2. sub r3 fp #392 @r3 = fp - 392
  3. mov r2 #388 @只拷贝388,剩下4个由寄存器传参
  4. mov r1 r3 @保存由r1保存r3,用于memcpy
  5. bl memcpy @拷贝结构体部分内容,将r1的内容拷贝r2的数量到r0
  6. sub r3 fp #408 @定位到结构体剩余未拷贝处
  7. ldm r3 {r0 r1 r2 r3} @将剩余结构体内容通过寄存器传参

看这段汇编拷贝,意思是从r1开始位置拷贝r2数量的数据到r0的位置,注意只拷贝了 388个,也就是 388/4 = 97个栈空间.剩余的4个通过寄存器传的参数.ldm代表从fp-408的位置将内存地址的值连续的给r0 - r3寄存器,即位置(fp-396,fp-400,fp-404,fp-408)的值.

执行下来的结果就是

  1. r3 = fp-408 r2 = fp-404 r1 = fp-400 r0 = fp-396 得到虚拟地址的值,这些值整好是memcpy没有拷贝到变量剩余的值

逐句分析 framePoint

  1. framePoint(reg):
  2. sub sp sp #16 @申请栈空间
  3. str fp [sp #-4]! @保护main函数栈帧,等同于push {fp}
  4. add fp sp #0 @fp变成framePoint栈帧,同时也指向了栈顶
  5. add ip fp #4 @定位到入栈口,让4个参数依次入栈
  6. stm ip {r0 r1 r2 r3}@r0-r3入栈保存
  7. ldr r3 [fp #4] @取值cpu.pc = 2
  8. ldr r2 [fp #404] @取值cpu.Rn[0] = 1
  9. mul r3 r2 r3 @cpu.Rn[0] * cpu.pc
  10. mov r0 r3 @返回值由r0保存
  11. add sp fp #0 @重置sp,和add fp, sp, #0配套出现
  12. ldr fp [sp], #4 @恢复main函数栈帧
  13. add sp sp #16 @归还栈空间,sp回落到main函数栈顶位置
  14. bx lr @跳回main函数
  1. framePoint申请了4个栈空间目的是用来存放四个寄存器值的,以上汇编代码逐句分析.
  2. 第一句: sub sp sp #16 @申请栈空间,用来存放r0-r3四个参数
  3. 第二句: str fp [sp #-4]! @保护main的fp,等同于push {fp},为什么这里要把main函数的fp放到 [sp, #-4]! 位置,注意 !号,表示SP的位置要变动,因为这里必须要保证参数的连续性.
  4. 第三句: add fp sp #0 @指定framePoint的栈帧位置,同时指向了栈顶 SP
  5. 第四句: add ip fp #4 @很关键,用了ip寄存器,因为此时 fp sp 都已经确定了,但别忘了 r0 - r3 还没有入栈呢.从哪个位置入栈呢, fp+4位置,因为 main函数的栈帧已经入栈了,在已经fp的位置.中间隔了四个空位,就是给 r0-r3留的.
  6. 第五句: stm ip {r0 r1 r2 r3}@r0-r3入栈,填满了剩下的四个空位.
  7. 第六句: ldr r3 [fp #4] @取的就是cpu.pc = 2的值,因为上一句就是从这里依次入栈的,最后一个当然就是cpu.pc了.
  8. 第七句: ldr r2 [fp #404] @取值cpu.Rn[0] = 1,其实这一句已经是跳到了main函数的栈帧取值了,所以看明白了没有,并不是在传统意义上理解的在framePoint的栈帧中取值.
  9. 第八句: mul r3 r2 r3 @cpu.Rn[0] * cpu.pc 做乘法运算
  10. 第九句: mov r0 r3 @返回值r0保存运算结构, 目的是return
  11. 第十句: add sp fp #0 @重置sp,其实这一句可以优化掉,因为此时sp = fp
  12. 第十一句: ldr fp [sp], #4 @恢复fp,等同于pop {fp},因为函数运行完了,需要回到main函数了,所以要拿到main的栈帧
  13. 第十二句: add sp sp #16 @归还栈空间,等于把四个入参抹掉了.
  14. 最后一句: bx lr @跳回main函数,如此 fp lr 寄存器中保存的都是 main函数的信息,就可以安全着陆了.

总结

因为寄存器数量有限,所以只能通过这种方式来传递大的参数,想想也只能在main函数栈中保存大部分参数,同时又必须确保数据的连续性,好像也只能用这种办法了,一部分通过寄存器传,一部分通过拷贝的方式倒是挺有意思的.

鸿蒙内核源码分析.总目录

v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o

百万汉字注解.百篇博客分析

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >

百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >

关注不迷路.代码即人生

QQ群:790015635 | 入群密码: 666

原创不易,欢迎转载,但请注明出处.

鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 百篇博客分析OpenHarmony源码 | v23.02的更多相关文章

  1. 鸿蒙内核源码分析(汇编汇总篇) | 所有的汇编代码都在这里 | 百篇博客分析OpenHarmony源码 | v40.03

    百篇博客系列篇.本篇为: v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

  2. 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班? | 百篇博客分析OpenHarmony源码 | v22.01

    百篇博客系列篇.本篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在 ...

  3. v87.01 鸿蒙内核源码分析 (内核启动篇) | 从汇编到 main () | 百篇博客分析 OpenHarmony 源码

    本篇关键词:内核重定位.MMU.SVC栈.热启动.内核映射表 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 鸿蒙内核源码分析(汇编基础) | ...

  4. 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01

    百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...

  5. 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02

    百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

  6. 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 百篇博客分析OpenHarmony源码 | v43.02

    百篇博客系列篇.本篇为: v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里 ...

  7. 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02

    百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

  8. 鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 百篇博客分析OpenHarmony源码 | v39.03

    百篇博客系列篇.本篇为: v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU ...

  9. 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 百篇博客分析OpenHarmony源码 | v38.02

    百篇博客系列篇.本篇为: v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...

随机推荐

  1. 理解js运行时的一些概念

    帧:一个帧是一个连续的工作单元.当一个js函数被调用时,运行时环境就会在栈中创建一个帧.帧里保存了特殊的函数参数和局部变量.当函数返回时,帧就被从栈中推出.例如: function foo(b) { ...

  2. TaskAwaiter<TResult> 结构

    参考网址:https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.compilerservices.taskawaiter-1?view= ...

  3. 物联网协议Coap协议介绍

    COAP协议简介 Coap(Constrained Application Protocol)是一种在物联网世界的类web协议,它的详细规范定义在 RFC 7252.COAP名字翻译来就是" ...

  4. CPU 进程 线程 关系与区别

  5. group by分组查询

    有如下数据: 一个简单的分组查询的案例 按照部门编号deptno分组,统计每个部门的平均工资. select deptno,avg(sal) avgs from emp group by deptno ...

  6. Android系统编程入门系列之应用内键值对数据的简单保存

    在应用程序间及与用户的通信交互过程中,会产生并传递一系列数据.针对这些数据,有部分是只在应用程序中使用的缓存数据,还有一部分是在不同位置多次或长时间使用的持久化数据. 对于缓存数据来说,通常以代码中定 ...

  7. vue 手机号码隐藏中间

    template {{ phone | mobileFilter }}   data export default { filters:{         mobileFilter(val){     ...

  8. K8S——Pod

    一.Pod概念 二.Pod存在的意义 三.Pod的实现机制 四.Pod镜像拉取策略 五.Pod资源限制 六.Pod重启机制 七.Pod的健康检查 八.Pod调度策略(创建Pod流程)

  9. WEB漏洞——文件上传

    有关文件上传的知识 为什么文件上传存在漏洞 上传文件时,如果服务端代码未对客户端上传的文件进行严格的验证和过滤就容易造成可以上传任意文件的情況,包括上传脚本文件(asp.aspx.php.jsp等格式 ...

  10. C# List集合类常用操作 (一)

    所有操作基于以下类 class Employees { public int Id { get; set; } public string Name { get; set; } public stri ...