打印
[应用方案]

Cortex-M内核知识点总结

[复制链接]
楼主: 米多0036
手机看帖
扫描二维码
随时随地手机跟帖
21
米多0036|  楼主 | 2023-9-15 12:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
函数调用过程中栈帧结构

使用特权

评论回复
22
米多0036|  楼主 | 2023-9-15 12:38 | 只看该作者
如上图所示,函数调用过程 为 main -> func2->func1 , 最终执行到 func1中后 栈的结构如图 ,下图表示函数func2的栈帧

使用特权

评论回复
23
米多0036|  楼主 | 2023-9-15 12:38 | 只看该作者
FP 栈帧底部,用于返回查找,FP将所有的栈帧构建成一个链表
LR 连接寄存器,保存了函数退出后的下一个PC地址,用于函数返回
局部变量:函数中定义的局部变量
函数参数:当函数参数大于4个 ,大于的部分需要入栈,其余通过R0-R3传递

使用特权

评论回复
24
米多0036|  楼主 | 2023-9-15 12:38 | 只看该作者
示例一:无参数,无返回值的函数调用

使用特权

评论回复
25
米多0036|  楼主 | 2023-9-15 12:38 | 只看该作者
可以看到左边为C 程序 ,右边为其对应的汇编程序 , 看到汇编可能会慌张,但其实观察可以看到 , 只是在重复几条指令而已。



mov R0 , #1           ;将立即数1(也就是常数)写入到 R0 , MOV只能赋值8位的数值,32位的需要用伪指令LDR
ldr R3 , [fp,#-8]     ;将 [FP-8] 内存地址的内容加载到 R3      
str R3 , [sp,#-12]    ;将 R3的值写入到 [FP-12] 内存地址
add fp , sp ,#0       ; fp = sp + 0
sub sp , sp ,#28      ; sp = sp - 28
push {fp ,lr}         ;使用 push 指令是 默认从左往右依次入栈(但是R0-R12通用寄存器会先入大的编号) ,
                      ;栈指针 sp 会自动"增加" , 压fp 和 lr 则 sp = sp -8 (思考为何是减而不是加)
                     
pop  {fp}             ;pop 指令会将栈内数据依次弹出 , 赋值给{}内的寄存器 , 同时栈指针会自动"减小"
bl <func>             ; 跳转指令 ,调整PC指针到对应的跳转地址 , 并且自动将跳转地址的下一个地址写入到 lr 寄存器

使用特权

评论回复
26
米多0036|  楼主 | 2023-9-15 12:39 | 只看该作者
sp我们知道是栈指针 , 每次使用 push 指令,sp都将自动生长(减小,栈是向低地址方向生长),每次使用pop指令时,sp都将增大。fp是什么呢,它是frame point的缩写 , 直译过来就是栈帧指针。我们在进入main 函数后,sp和fp都指向栈底,此时栈是空的,如下图所示。

使用特权

评论回复
27
米多0036|  楼主 | 2023-9-15 12:55 | 只看该作者
从main函数的汇编开始分析

使用特权

评论回复
28
米多0036|  楼主 | 2023-9-15 12:55 | 只看该作者

使用特权

评论回复
29
米多0036|  楼主 | 2023-9-15 12:56 | 只看该作者
我们假设栈底在内存中的地址是 0x20005000 ,栈的大小为 1K 。 在执行 push {fp , lr} 后,栈的变化情况如下(当栈是空的时使用push指令sp的偏移会少一个单元,如下图,push两个单元进入,正常sp = sp-8 ,但这里是sp=sp-4 , 只要记住一点,sp指针***指向栈顶,这里就是lr所在地址):

使用特权

评论回复
30
米多0036|  楼主 | 2023-9-15 12:56 | 只看该作者
接下来的 两条指令 add fp , sp , #4 和 sub sp , sp , #16 执行后的结果为,这个16的由来是因为main函数里有4个局部变量需要存到栈中。

使用特权

评论回复
31
米多0036|  楼主 | 2023-9-15 12:56 | 只看该作者
接下来就是一直到 bl 跳转前,都是让局部变量入栈,注意局部变量的入栈顺序,最先定义的局部变量反而最后入栈 , fp指针和sp指针之间我们称为main函数的栈帧。

使用特权

评论回复
32
米多0036|  楼主 | 2023-9-15 12:56 | 只看该作者
接下来跳转到 函数func , 具体看右侧注释过程,可以分为4个部分 , 我们分别将4个过程 栈的变化情况用图形表示出来

使用特权

评论回复
33
米多0036|  楼主 | 2023-9-15 12:57 | 只看该作者
第一部分,划分出函数func 的栈帧空间(移动fp 和sp) , 这里有一点需要注意,在main函数中,定义了4个局部变量,在分配栈帧空间时,刚好分配4个32位空间来存储4个整形的 , 而在func 中,有5个整型局部变量,但这里却分配了6个单位(4*6=24字节),因为栈空间需要8字节对齐。一般我们都是熟悉的4字节对齐,但是由于浮点数的原因 (double)需要8字节 , 所以使用8字节对齐。还有一点与main函数的栈帧不同 , func函数的栈帧未将lr 寄存器入栈,因为func函数已经是调用最深的函数 , 它内部没有其他调用函数,因此lr的值不会变,当函数执行完后 , 直接令 pc = lr 即可返回。

使用特权

评论回复
34
米多0036|  楼主 | 2023-9-15 12:57 | 只看该作者
第二部分,func函数的局部变量入栈

使用特权

评论回复
35
米多0036|  楼主 | 2023-9-15 12:57 | 只看该作者
第三部分,依次求和,最终将就和结果保存到e , 也就是上图数值5的地方。

第四部分,出栈,首先调整栈指针 sp = fp + 0 , 在弹出保存的main_fp = 0x20005000 到 fp 指针 ,sp指针因为pop操作上移一个单元。最终的栈空间如下图右侧部分。虽然func的值依旧保留 , 但是由于sp 的值改变,当再次使用push压栈时,将覆盖旧值。func函数没有返回值 , 所以直接跳转到main 函数。也就是bx lr 。 还记得lr 保存得内容吗。在使用bl 指令时 , 会将bl 的下一条指令的地址自动写入 lr 寄存器。所以 返回时就可以接着执行了。

使用特权

评论回复
36
米多0036|  楼主 | 2023-9-15 12:58 | 只看该作者

使用特权

评论回复
37
米多0036|  楼主 | 2023-9-15 12:59 | 只看该作者
中断发生后栈的变化
中断和函数调用的区别在于中断任何时刻都可能发生,发生中断后,需要保存现场环境,然后执行中断服务函数,执行完后,需要恢复现场环境。从寄存器组章节可知,和当前环境相关的寄存器有 8个 ,这8个寄存器硬件上会按以下顺序自动入栈 ,若中断服务函数中还有局部变量或函数调用,按栈帧形式继续入栈

使用特权

评论回复
38
米多0036|  楼主 | 2023-9-15 14:25 | 只看该作者
HardFault 问题查找步骤 : 发生HardFault时 ,当前PC 和 LR参考意义不大 ,应该根据当前SP指针的地址 , 获取此8个寄存器的值 ,其中PC为发生异常时的PC,

LR为发生异常代码所在函数的下一条执行的代码 , R0-R4 为过程值 ,可能为参数 ,返回值 ,内存加载值等等 ,具体依靠PC和LR能分析其内容。

使用特权

评论回复
39
米多0036|  楼主 | 2023-9-15 14:25 | 只看该作者
以下为实际发生HardFault的Jlink读取情况

使用特权

评论回复
40
米多0036|  楼主 | 2023-9-15 14:25 | 只看该作者
根据PSP(裸机使用MSP) 查找发生 入栈的 8个寄存器 PC : 0x080249AA LR:0x0801E895

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则