鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数

汇编如何传复杂的参数?

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

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

先看一段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函数栈中保存大部分参数,同时又必须确保数据的连续性,好像也只能用这种办法了,一部分通过寄存器传,一部分通过拷贝的方式倒是挺有意思的.

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/590924.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小程序账号设置以及request请求的封装

一般开发在小程序时&#xff0c;都会有测试版和正式版&#xff0c;这样在开发时会比较方便。 在开发时。产品经理都会给到测试账号和正式账号&#xff0c;后端给的接口也都会有测试环境用到的接口和正式环境用到的接口。 这里讲一讲我这边如何去做的。 1.在更目录随便命名一…

吴恩达2022机器学习专项课程(一)正则化(正则化成本函数正则化线性回归正则化逻辑回归)

目录 一.正则化1.1 正则化的好处1.2 正则化的实现方式 二.正则化改进线性回归的成本函数2.1 正则化后的成本函数的意义2.2 λ参数的作用2.3 不同λ对算法的影响2.4 为什么参数b没有正则化项 三.正则化线性回归的梯度下降3.1 为什么正则化可以在梯度下降迭代中减小w3.2 导数的计…

如何在Mac上恢复格式化硬盘的数据?

“嗨&#xff0c;我格式化了我的一个Mac硬盘&#xff0c;而没有使用Time Machine备份数据。这个硬盘被未知病毒感染了&#xff0c;所以我把它格式化为出厂设置。但是&#xff0c;我忘了备份我的文件。现在&#xff0c;我想恢复格式化的硬盘驱动器并恢复我的文档&#xff0c;您能…

面试算法题精讲:最长公共子串

面试算法题精讲&#xff1a;最长公共子串 最长公共子串问题是指给定两个字符串S1和S2&#xff0c;求它们的公共子串中最长的那一个。其实就是求两个字符串的最长重复子串。 最朴素的算法就是枚举S1和S2的每一对子串&#xff0c;然后判断它们是否相等&#xff0c;时间复杂度是…

手搓堆(C语言)

Heap.h #pragma once#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> #include <string.h> typedef int HPDataType; typedef struct Heap {HPDataType* a;int size;int capacity; }Heap;//初始化 void Heap…

Java Jackson-jr 库是干什么用的

Jackson-jr 是一个轻量级的Java JSON 处理库。这个库被设计用来替代 Jackson 的复杂性。对比 Jackson 的复杂 API&#xff0c;Jackson-jr 的启动速度更快&#xff0c;包大小更小。 虽然Jackson databind&#xff08;如ObjectMapper&#xff09;是通用数据绑定的良好选择&#…

如何从Mac电脑恢复任何删除的视频

Microsoft Office是包括Mac用户在内的人们在世界各地创建文档时使用的最佳软件之一。该软件允许您创建任何类型的文件&#xff0c;如演示文稿、帐户文件和书面文件。您可以使用 MS Office 来完成。所有Microsoft文档都可以在Mac上使用。大多数情况下&#xff0c;您处理文档&…

苹果CEO对未来一代人工智能投资持乐观态度

尽管在动荡的第二季度&#xff0c;苹果的收入和iPhone销量有所下降&#xff0c;但其新兴的人工智能技术可能会带来急需的提振。 在5月2日的电话财报会议上&#xff0c;苹果公布季度收入为908亿美元&#xff0c;比去年下降4%。iPhone的收入也下降了10%&#xff0c;至460亿美元。…

《Python编程从入门到实践》day19

#昨日知识点回顾 使用unittest模块测试单元和类 #今日知识点学习 第12章 武装飞船 12.1 规划项目 游戏《外星人入侵》 12.2 安装pygame 终端管理器执行 pip install pygame 12.3 开始游戏项目 12.3.1 创建Pygame窗口及响应用户输入 import sysimport pygameclass…

SpringCloud微服务项目创建流程

为了模拟微服务场景&#xff0c;学习中为了方便&#xff0c;先创建一个父工程&#xff0c;后续的工程都以这个工程为准&#xff0c;实用maven聚合和继承&#xff0c;统一管理子工程的版本和配置。 后续使用中只需要只有配置和版本需要自己规定之外没有其它区别。 微服务中分为…

电脑数据怎么拷贝到u盘?操作指南与数据丢失防范

在数字时代&#xff0c;数据的传输与备份已成为我们日常生活和工作中不可或缺的一部分。U盘作为一种便捷、高效的移动存储设备&#xff0c;广泛应用于各种数据拷贝场景。无论是个人文件的备份&#xff0c;还是工作资料的传输&#xff0c;U盘都发挥着举足轻重的作用。那么&#…

【刷题(1)】链表

一、链表问题基础 移动:head=head.next 移动到最后:head.next=null 停止:if not xx: 相当于if not null: 取值:a.val 赋值:b.next=a 遍历:while head: head=head.next 或者: if not head: head=head.next 递归:单链表: def func(pre,cur): return func(pre.next,cur.…

Windows安装Ubuntu24详细教程

从官网下载ISO镜像&#xff1a; 使用VMWare创建新的虚拟机&#xff1a; 选择刚才下载的ISO镜像&#xff1a; 填写账号和密码&#xff1a; 选择安装位置和虚拟机名称&#xff0c;因为我装这个主要是为了QT开发的&#xff0c;所以名字叫ubuntu24_for_qt&#xff1a; 处理…

基于大爆炸优化算法的PID控制器参数寻优matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于大爆炸优化算法的PID控制器参数寻优matlab仿真。对比优化前后的PID控制输出。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MATLAB2022a .....................…

前端 JS 异常那些事

前言 人无完人&#xff0c;所以代码总会出异常的&#xff0c;异常并不可怕&#xff0c;关键是怎么处理 什么是异常 程序发生了意想不到的情况&#xff0c;影响到了程序的正确运行 从根本上来说&#xff0c;异常就是一个普通的对象&#xff0c;其保存了异常发生的相关信息&a…

通过符号程序搜索提升prompt工程

原文地址&#xff1a;supercharging-prompt-engineering-via-symbolic-program-search 通过自动探索​​大量提示变体来找到更好的提示 2024 年 4 月 22 日 众所周知&#xff0c;LLMs的成功在很大程度上仍然取决于我们用正确的指导和例子来提示他们的能力。随着新一代LLMs变得越…

django搭建一个AI博客进行YouTube视频自动生成文字博客

文章目录 一、生成Django框架二、项目代码&#xff08;前端&#xff09;1、编写前端代码&#xff08;正文界面&#xff09;1.1、生产html框架1.2、添加live preview扩展1.3、更改title元素中文本1.4、添加CDN&#xff08;CSS&#xff09;样式链接1.5、nav标签1.6、在body标签中…

021、Python+fastapi,第一个Python项目走向第21步:ubuntu 24.04 docker 安装mysql8、redis(二)

系列文章目录 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 安装redis 我会以三种方式安装&#xff0c; 第一、直接最简单安装&#xff0c;适用于测试环境玩玩 第二、conf配置安装 第三、集群环境安装 一…

Flutter笔记:Widgets Easier组件库(9)使用弹窗

Flutter笔记 Widgets Easier组件库&#xff08;9&#xff09;&#xff1a;使用弹窗 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress o…

Redis---------实现更改数据业务,包括缓存更新,缓存穿透雪崩击穿的处理

三种更新策略 内存淘汰是Redis内存的自动操作&#xff0c;当内存快满了就会触发内存淘汰。超时剔除则是在存储Redis时加上其有限期(expire)&#xff0c;有限期一过就会自动删除掉。而主动更新则是自己编写代码去保持更新&#xff0c;所以接下来研究主动更新策略。 主动更新策略…