本篇关键词:、、、
下载 >> 离线文档.鸿蒙内核源码分析(百篇博客分析.挖透鸿蒙内核).pdf
内核汇编相关篇为:
- v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的
- v75.03 鸿蒙内核源码分析(汇编基础) | CPU上班也要打卡
- v76.04 鸿蒙内核源码分析(汇编传参) | 如何传递复杂的参数
- v77.01 鸿蒙内核源码分析(链接脚本) | 正在制作中 ...
- v78.01 鸿蒙内核源码分析(内核启动) | 从汇编到main()
- v79.01 鸿蒙内核源码分析(进程切换) | 正在制作中 ...
- v80.03 鸿蒙内核源码分析(任务切换) | 看汇编如何切换任务
- v81.05 鸿蒙内核源码分析(中断切换) | 系统因中断活力四射
- v82.06 鸿蒙内核源码分析(异常接管) | 社会很单纯 复杂的是人
- v83.01 鸿蒙内核源码分析(缺页中断) | 正在制作中 ...
汇编如何传复杂的参数?
汇编基础篇 中很详细的介绍了一段具有代表性很经典的汇编代码,有循环,有判断,有运算,有多级函数调用。但有一个问题没有涉及,就是很复杂的参数如何处理? 在实际开发过程中函数参数往往是很复杂的参数,(比如结构体)汇编怎么传递呢? 先看一段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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//编译器: 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.Rn[0] = 1
ldr r2, [fp, #404] @取值cpu.pc = 2 , 高地址位
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 @跳回调用函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
两个函数对应两段汇编,干净利落,去除中间各项干扰,只有一个结构体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);
}
2
3
4
5
6
7
但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} @将剩余结构体内容通过寄存器传参
2
3
4
5
6
7
看这段汇编拷贝,意思是从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.Rn[0] = 1
ldr r2, [fp, #404] @取值cpu.pc = 2
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函数
2
3
4
5
6
7
8
9
10
11
12
13
14
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.Rn[0] = 1的值,因为上一句就是从这里依次入栈的,最后一个当然就是cpu.pc了。
第七句: ldr r2, [fp, #404] @取值cpu.pc = 2,其实这一句已经是跳到了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函数的信息,就可以安全着陆了。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
总结
因为寄存器数量有限,所以只能通过这种方式来传递大的参数,想想也只能在main函数栈中保存大部分参数,同时又必须确保数据的连续性,好像也只能用这种办法了,一部分通过寄存器传,一部分通过拷贝的方式倒是挺有意思的。
百文说内核 | 抓住主脉络
- 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
- 与代码需不断
debug
一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx
代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 - 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,百篇博客系列目录如下。
按功能模块:
百万注源码 | 处处扣细节
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
关注不迷路 | 代码即人生
- 互联网从业十五年,计算机硕士,技术副总裁
- 关注我,持续更新四十年,即聊技术也聊人生
- 交有趣靠谱的人;做难而正确的事
- 不做作,不炒作,只唯真
- 不唯上,不唯书,只唯实
据说喜欢 点赞 + 分享 的,后来都成了大神。😃