鸿蒙内核源码分析(汇编传参篇) | 汇编如何传递复杂的参数? | 中文注解HarmonyOS源码 | v23.01
鸿蒙内核源码注释 >> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新
鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新
汇编如何传复杂的参数?
鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 ? < CSDN | OSCHINA | WeHarmony | 源动力 >中很详细的介绍了一段具有代表性很经典的汇编代码,有循环,有判断,有运算,有多级函数调用。但有一个问题没有涉及,就是很复杂的参数如何处理? 实际在开发过程中函数参数往往是很复杂的结构体,那复杂参数(比如结构体)怎么传递呢? 先看一段C语言和编译的汇编代码
#include <stdio.h>
#include <math.h>
struct reg{
int Rn[100];
int pc;
};
int fp(reg cpu)
{
return cpu.Rn[0] * cpu.pc;
}
int main()
{
reg cpu;
cpu.Rn[0] = 1;
cpu.pc = 2;
return fp(cpu);
}
//编译器: armv7-a gcc (9.2.1)
fp(reg):
sub sp, sp, #16 @申请栈空间
str fp, [sp, #-4]! @保护fp帧指针,等同于push {fp}
add fp, sp, #0 @fp新值,同时也指向了栈顶
add ip, fp, #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
ldr fp, [sp], #4 @重置fp
add sp, sp, #16 @归还栈空间
bx lr @跳回main函数
main:
push {fp, lr} @入栈保存调用函数现场
add fp, sp, #4 @fp更新
sub sp, sp, #800 @分配800个栈空间给main
mov r3, #1 @r3 = 1
str r3, [fp, #-408] @将1放置 fp-408处
mov r3, #2 @r3 = 2
str r3, [fp, #-8] @将2放置 fp-8处
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 fp(reg) @执行fp
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 fp(cpu);
}
但main函数只有一个变量,只要101个栈空间,其他都算上也用不了200个的.为什么要这么做呢? 而且注意下里面的数字 388, 408, 392 这些都是什么意思? 看完main汇编能得到一个结论是 200个栈空间中除了存放了main函数本身的变量reg cpu外 ,还存放了 fp函数的参数cpu的部分值,存放了多少个?答案是 97个. 注意变量CPU没有共用,而是拷贝了一部份.如何拷贝的?继续看
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参数取用
fp(reg):
sub sp, sp, #16 @申请栈空间
str fp, [sp, #-4]!@保护fp帧指针,等同于push {fp}
add fp, sp, #0 @fp新值,同时也指向了栈顶
add ip, fp, #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
ldr fp, [sp], #4 @重置fp
add sp, sp, #16 @归还栈空间
bx lr @跳回main函数
fp申请了4个栈空间就是用来存放四个寄存器值的,注意它和另外的reg cpu 97个栈空间是连续的. 同时 add fp, sp, #0 表示fp指向了栈顶位置 fp+404 和 fp+4 刚好取到了 cpu.Rn[0] 和 cpu.pc 的值. 如此完成了乘法运算.
总结
因为寄存器数量有限,所以只能通过这种方式来传递大的参数,也只能在main函数栈中保存,也必须确保数据的连续性. 一部分通过寄存器传,一部分通过拷贝的方式倒是挺有意思的.
喜欢就请注入源动力吧
各大站点搜 "鸿蒙内核源码分析",快速找到组织.或者更简单的,如图:


