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

硬件架构相关篇为:

汇编如何传复杂的参数?

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

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

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

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

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

入参方式

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

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

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

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

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

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

memcpy汇编调用

        mov     r0, sp          @r0 = sp
sub r3, fp, #392 @r3 = fp - 392
mov r2, #388 @只拷贝388,剩下4个由寄存器传参
mov r1, r3 @保存由r1保存r3,用于memcpy
bl memcpy @拷贝结构体部分内容,将r1的内容拷贝r2的数量到r0
sub r3, fp, #408 @定位到结构体剩余未拷贝处
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)的值.

执行下来的结果就是

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

逐句分析 framePoint

framePoint(reg):
sub sp, sp, #16 @申请栈空间
str fp, [sp, #-4]! @保护main函数栈帧,等同于push {fp}
add fp, sp, #0 @fp变成framePoint栈帧,同时也指向了栈顶
add ip, fp, #4 @定位到入栈口,让4个参数依次入栈
stm ip, {r0, r1, r2, r3}@r0-r3入栈保存
ldr r3, [fp, #4] @取值cpu.pc = 2
ldr r2, [fp, #404] @取值cpu.Rn[0] = 1
mul r3, r2, r3 @cpu.Rn[0] * cpu.pc
mov r0, r3 @返回值由r0保存
add sp, fp, #0 @重置sp,和add fp, sp, #0配套出现
ldr fp, [sp], #4 @恢复main函数栈帧
add sp, sp, #16 @归还栈空间,sp回落到main函数栈顶位置
bx lr @跳回main函数
framePoint申请了4个栈空间目的是用来存放四个寄存器值的,以上汇编代码逐句分析.

第一句: sub     sp, sp, #16     @申请栈空间,用来存放r0-r3四个参数

第二句: str     fp, [sp, #-4]!  @保护main的fp,等同于push {fp},为什么这里要把main函数的fp放到 [sp, #-4]! 位置,注意 !号,表示SP的位置要变动,因为这里必须要保证参数的连续性.

第三句: add     fp, sp, #0      @指定framePoint的栈帧位置,同时指向了栈顶 SP

第四句: add     ip, fp, #4      @很关键,用了ip寄存器,因为此时 fp sp 都已经确定了,但别忘了 r0 - r3 还没有入栈呢.从哪个位置入栈呢, fp+4位置,因为 main函数的栈帧已经入栈了,在已经fp的位置.中间隔了四个空位,就是给 r0-r3留的.

第五句: stm     ip, {r0, r1, r2, r3}@r0-r3入栈,填满了剩下的四个空位.

第六句: ldr     r3, [fp, #4]    @取的就是cpu.pc = 2的值,因为上一句就是从这里依次入栈的,最后一个当然就是cpu.pc了.

第七句: ldr     r2, [fp, #404]  @取值cpu.Rn[0] = 1,其实这一句已经是跳到了main函数的栈帧取值了,所以看明白了没有,并不是在传统意义上理解的在framePoint的栈帧中取值.

第八句: mul     r3, r2, r3      @cpu.Rn[0] * cpu.pc 做乘法运算

第九句: mov     r0, r3          @返回值r0保存运算结构, 目的是return

第十句: add sp, fp, #0          @重置sp,其实这一句可以优化掉,因为此时sp = fp

第十一句: ldr     fp, [sp], #4  @恢复fp,等同于pop {fp},因为函数运行完了,需要回到main函数了,所以要拿到main的栈帧

第十二句: add     sp, sp, #16   @归还栈空间,等于把四个入参抹掉了.

最后一句: 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. Groovy+Spock单元测试

    一.导入依赖 Spock是基于JUnit的单测框架,提供一些更好的语法,结合Groovy语言,可以写出更为简洁的单测. <!-- groovy依赖 --> <dependency&g ...

  2. (三)ES6基础语法。。。freecodecamp笔记

    ECMAScript6 ECMAScript 是 JavaScript 的标准化版本,它旨在统一语言的规范和功能.所有主流的浏览器或者 Javascript 的运行环境都支持这个规范,因此 ECMAS ...

  3. Java中Byte类型数据在运算中的问题

    比如: byte a=1; byte b=2; byte c; c=a+b; //这样是计算不出c,是错误的 c=a+1; //这样也是不能计算c的 c=64+1; //为什么这样就能计算c,在Jav ...

  4. Blazor WebAssembly 应用程序中进行 HTTP 请求

    翻译自 Waqas Anwar 2021年5月13日的文章 <Making HTTP Requests in Blazor WebAssembly Apps> [1] 在我的前篇文章< ...

  5. 并发控制--context篇

    目录 1. 前言 2 Context 实现原理 2.1 接口定义 2.1 cancelCtx 2.1.1 Done()接口实现 2.1.2 Err()接口实现 2.1.3 cancel()接口实现 2 ...

  6. 如何在RHEL7或CentOS 7系统下修改网卡名称(亲测有效~!)

    亲测有效的更改RHEL7或CentOS 7的网卡名称的方法, 按照以下4步来操作就可以实现! Step 1 :网卡配置文件名称重命名为eth0[root@localhost ~]# ifconfige ...

  7. C#多线程开发-任务并行库04

    你好,我是阿辉. 之前学习了线程池,知道了它有很多好处. 使用线程池可以使我们在减少并行度花销时节省操作系统资源.可认为线程池是一个抽象层,其向程序员隐藏了使用线程的细节,使我们可以专心处理程序逻辑, ...

  8. 安全测试工具(1)- Burp Suite Pro的安装教程

    啥是Burp Suite 用于攻击web 应用程序的集成平台 程序员必备技能,不仅可以拿来做渗透测试.漏洞挖掘还能帮助程序员调试程序 Bug 它包含了许多Burp工具,这些不同的burp工具通过协同工 ...

  9. 交换机之vlan详解

    一.为什么需要VLAN 1.1.什么是VLAN? VLAN(Virtual LAN),翻译成中文是"虚拟局域网".LAN可以是由少数几台家用计算机构成的网络,也可以是数以百计的计算 ...

  10. [第九篇]——Docker 镜像使用之Spring Cloud直播商城 b2b2c电子商务技术总结

    Docker 镜像使用 当运行容器时,使用的镜像如果在本地中不存在,docker 就会自动从 docker 镜像仓库中下载,默认是从 Docker Hub 公共镜像源下载. 下面我们来学习: 1.管理 ...