嵌入式内存栈(汇编随记)

嵌入式内存栈 (汇编随记)


version : v1.0 「2022.7.28」 最后补充


author: Y.Z.T.



简介: 随记



⭐️ 目录




1️⃣ 内存空间分布

可执行文件(.o)在被加载到内存中时 , 内存空间分布图:

image-20220924001131747


  • 函数翻译成二进制指令放在代码段
  • 初始化的全局变量静态局部变量放在数据段中(.data)
  • 未初始化的全局变量静态变量放在BSS段中(.bss)
  • 函数的局部变量保存在
  • 使用malloc申请的动态内存保存在堆空间


2️⃣ 栈的使用


栈的基本操作: 入栈(push)和出栈(pop)

空栈与满栈:

  • 根据栈指针SP 指向栈顶元素的不同, 栈可以分成满栈空栈
  • 满栈的栈指针SP 总是指向栈顶元素
  • 空栈的栈指针总是指向栈顶元素上方的可用元素。

递增栈和递减栈:

  • 根据栈的生长方向不同 , 栈可以分成递增栈递减栈
  • 一个元素入栈时, 递增栈的栈指针从低地址向高地址增长
  • 一个元素入栈时, 递减栈的栈指针从高地址向低地址增长

栈的作用 :

  • 是C语言运行的基础
  • C语言函数中的局部变量、传递的实参返回的结果、编译器生成的临时变量都是保存在栈中的
  • 所以在系统一上电会先运行汇编代码 , 先初始化栈空间 , 再跳入第一个C语言函数

ARM处理器使用的是满递减栈


防止栈溢出 :

  • 尽量不要在函数内使用大数组,如果确实需要大块内存,则可以使用malloc申请动态内存。
  • 函数的嵌套层数不宜过深。
  • 递归的层数不宜太深。

栈的分类

image-20221003131823961


满递减栈入栈操作

image-20221003131921334



2.1 函数调用
2.1.1 栈帧

每个函数的栈空间被称为栈帧


说明:

  • 每个栈帧都使用两个寄存器维护 , 其中FP 指向栈帧底部 ; SP 指向栈帧的顶部

image-20221003141511274

  • SP总是指向当前正在运行函数栈帧的栈顶 ; FP总是指向当前运行函数的栈底

  • 栈帧用于保存局部变量和实参, 还用于保存函数的上下文

例如main函数调用了f()函数

  • main()的基址FP, main()的返回地址LR都保存在f()函数的栈帧

  • 上级函数栈帧的起始地址(FP), 即栈底会保存在当前函数的栈帧中

  • 多个栈帧通过FP构成一个链 , 就是某个进程的函数调用栈

  • 当函数运行结束后, 当前函数的栈帧空间就会释放, SP/FP指向上一级函数栈帧

    示例:

    C语言原型

    int g (void)
    {
        int x = 100;
        int y = 200;
        return 300;
    }
    
    int f (void)
    {
        int 1 = 20;
        int m = 30;
        int n = 40;
        g();
        return 50;
    }
    
    int main (void)
    {
        int i= 2;
        int j = 3;
        int k = 4;
        f();
        return 0;
    }
    

    汇编代码:

    <main>
    push {fp,lr}	;将当前fp所指向的地址,即main的上级函数栈帧基址压入堆栈 ; 同时将mian的上级返回地址压入堆栈
    add fp,sp #4    ;将fp指向当前sp指针的前一个地址(即保存基址FP的地方)
    sub sp,sp,#16	;开辟栈帧空间 , 将SP指向栈顶(即②位置), 
    mov r3,#2		;
    str r3,[fp,#-16]	;将局部变量2 压入FP偏移4个地址的位置
    ...				; 同理压入其他局部变量
    ...
    bl(f的地址)<f>		   ; 调用f()函数(带链接跳转 , 跳到f()所在的地址)同时将pc值保存在LR寄存器
    mov r3 #0		 ;返回值 
    mov r0 r3		 ;保存返回值
    mov sp,fp,#4	 ;将SP指向上级函数栈帧栈顶,即位置①(出栈先移动SP指针,再弹出数据)
    pop {fp,pc}		 ;将FP、LR的值依次弹出到FP、PC寄存器。实现跳转回上级函数
    ----------------------------------------------
    
    (f的地址)<f>		;函数f的内存地址
    push {fp,lr}	;将当前fp所指向的地址,即main的栈帧基址压入堆栈 ; 同时将mian的返回地址LR压入堆栈
    add fp,sp #4    ;将fp指向当前sp指针的前一个地址(即保存基址FP的地方)
    sub sp,sp,#16	;开辟栈帧空间 , 将SP指向栈顶(即②位置), 
    ...				;同理将局部变量压入栈中
    ...
    bl(g的地址)<g>    ;调用g()函数,同时将pc值保存在LR寄存器
    mov r3 #50		 ;返回值 
    mov r0 r3		 ;保存返回值
    mov sp,fp,#4	 ;同理,将SP指向上级函数栈帧栈顶
    pop {fp,pc}		 ;同理,将FP、LR的值依次弹出到FP、PC寄存器。实现跳转回main函数
    
    
    -------------------------------------------------
    
    (g的地址)<g>
    push {fp} 		; 同理将 f()的基址FP压入堆栈 (即str fp ,[sp,#-4]!)
    ; 因为g函数已经不再跳转,所以LR寄存器的值一直是f()的返回地址,所以不需要压入LR
    add fp,sp,#0	;同理
    sub sp,sp,#12   ;同理将sp指向栈顶
    ...				;同理保存局部变量
    ...
    mov r3,#300		;返回值
    mov r0,r3
    sub sp,fp,#0	;sp = fp - 0 
    pop {fp}     	;将FP的值弹到fp寄存器 , 即将fp指向f()函数基址(ldr fp,[sp],#4)
    bxlr			;直接将lR寄存器的值赋给PC指针 , 实现返回上一级f()函数中运行
    
    image-20221003144546960


2.2 参数传递

ARM处理一般会使用寄存器来传递参数

  • 在函数调用过程, 当要传递的参数个数小于4时 , 会直接使用r0~r3寄存器传递
  • 当要传递的参数个数大于4时, 前4个参数使用寄存器传递 , 剩余的参数则压入堆栈保存
  • C语言默认参数传递是从右到左 , 栈的清理方是函数调用者

示例:

C语言程序原型

int f(int ag, int ag2, int ag3, int ag4, int ag5, int ag6)
{
	int s = 0;
    S = agl + ag2 + ag3 + ag4 + ag5 + ag6;
    return s;
}
int main(void)
{
    int sum =0;
    f(1, 2, 3, 4, 5, 6);
    printf(" sum:%d\n", sum);
    return 9;
}

汇编语言

(f的地址)<f>
push {fp}			; 将 main()的基址FP压入堆栈 (即str fp ,[sp,#-4]!)
add fp,sp,#0
sub sp,sp,#28
str r0,[fp,#-16]	;将main函数通过寄存器r0~r3传递的实参1,2,3,4保存到自己的函数栈帧中
str r1,[fp,#-20]	; r1   
...					; r2
...					; r3
ldr r2,[fp,#-16]	;准备进行累计计算, 将栈帧内的数加载到寄存器
ldr r3,[fp,#-20]	;加载到寄存器
add r2,r2,r3		;r2 = r2 + r3
...					;同理,累计 s = 1+2+3+4;
ldr r3,[fp,#4]		;fp寄存器向后偏移,到上一级函数的栈帧中获取要传递的实参(5)
add r2,r2,r3		;r2 = r2 + r3
ldr r3,[fp,#8]		;读取main栈帧内的实参(6)
add r3,r2,r3		;r3 = r2 + r3
str r3,[fp,#-8]		;将运算结果存入堆栈
ldr r3, [fp, #-8]	
mov r0,r3			;传递返回值
sub sp,fp,#0		;sp指针指向上级函数栈顶
pop {fp}			;fp指向上级函数基址
bxlr				;跳转回函数调用地址
   ----------------------------------
   
   
<main>    
push {fp,lr}	;同上,保存main上级基址和上级函数调用地址lr
add fp,sp,#4	;将fp指向sp的前一地址位置
sub sp,sp,#16	;开辟栈帧空间 , 将SP指向栈顶
mov r3,#0		;保存局部变量sum = 0
str r3,[fp,#-8]
mov r3,#6		;将传递的参数列表从右向左保存,先将实参6压入mian函数的栈帧
str r3,[sp,#4]	;
mov r3,#5		;将实参5压入栈
str r3,[sp]		;
mov r3,#4		;其他实参通过寄存器传递
mov r3,#3		;3
...				;2
...				;1
bl(f()的地址)<f> ;调用f(),同时将pc的值保存在LR中
...				;同上,将sp指针指向栈顶,以及将跳转

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值