背景

主要基于Linux,介绍X86-64和ARM64的用户栈结构。断断续续的学了很多和栈相关的知识,今天打算整理用户栈相关的知识,废话少说,下面进入正题。

栈的定义和类别

栈有时也称堆栈,是一种受限的线性表,只能在线性表的一端按序进行插入(进栈)和删除(出栈),因此先进栈的数据会后出栈。为了便于描述,我们习惯将在线性表进行插入和删除的一端称为栈顶,另一端称为栈底。栈顶会随着插入和删除而发生变化,栈底则保持不变。

其实,栈在计算机中就是一块连续的存储区域(至少虚拟地址是连续的),只不过在这块连续的存储区域写入和删除数据按照先进后出的规则进行,在计算机中使用两个指针就可以完全描述一个栈,bp(base pointer)指向栈底,sp(stack pointer)指向栈顶,如下图所示。

上面主要讲了栈的定义,在上面栈的定义中至少有两个地方没有说清楚,一是往栈中增加数据时,栈是往高地址增加还是往低地址增加;二是栈顶指针SP指向的地方是否存放数据。向高地址增长的栈称为递增栈(Ascendant Stack),向低地址增长的栈称为递减栈(Decendant Stack)。SP指向栈顶元素(即SP指向的地方存放数据)的栈为满栈(Full Stack),SP指向下一个栈顶元素位置(即SP指向的地方不存放数据)的栈为空栈(Empty Stack)。很显然一个栈不能同时为递增栈和递减栈,也不能同时为满栈和空栈。因此,存在4种类型的栈,即空增栈(Empty Ascendant Stack,EA)、空减栈(Empty Descendant Stack,ED)、满增栈(Full Ascendant Stack,FA)和满减栈(Full Descendant Stack,FD)。

空增栈(Empty Ascendant Stack,EA)

在对空增栈中压入数据时,先把数据放入SP所指的位置处,然后SP=SP+1。对这种栈的压入操作,相当于C语言的 memory[SP++]=data;或者相当于ARM64的汇编语言str x1,[SP],#8。出栈操作相当于C语言的data=memory[--SP];或者ARM64的汇编语言ldr x1,[SP,#-8]!

空减栈(Empty Descendant Stack,ED)

在对空减栈中压入数据时,先把数据放入SP所指的位置处,然后SP=SP-1。对这种栈的压入操作,相当于C语言的 memory[SP--]=data;或者相当于ARM64的汇编语言str x1,[SP],#-8。出栈操作相当于C语言的data=memory[++SP];或者ARM64的汇编语言ldr x1,[SP,#8]!

满增栈(Full Ascendant Stack,FA)

在对满增栈中压入数据时,先对SP操作腾出位置SP=SP+1,然后数据放入SP指向的位置。对这种栈的压入操作,相当于C语言的 memory[++SP]=data;或者相当于ARM64的汇编语言str x1,[SP,#8]!。出栈操作相当于C语言的data=memory[SP--];或者ARM64的汇编语言ldr x1,[SP],#-8

满减栈(Full Descendant Stack,FD)

在对满减栈中压入数据时,先对SP操作腾出位置SP=SP-1,然后数据放入SP指向的位置。对这种栈的压入操作,相当于C语言的 memory[--SP]=data;或者相当于ARM64的汇编语言str x1,[SP,#-8]!。出栈操作相当于C语言的data=memory[SP++];或者ARM64的汇编语言ldr x1,[SP],#8。或者X86-64的汇编指令push r1x86-64的汇编指令push和pop操作栈是按照满减栈的规则进行。默认情况下,ARM64也使用满减栈的规则操作栈。

栈的生命周期

栈的生命周期是和进程的生命周期保持一致的,进程在则栈在,进程亡则栈亡。因此,不妨从进程的生命周期探讨栈的生命周期。一个用户进程从无到开始运行,需要经过几个重要的步骤:

  • Linux首先创建一个task_struct用于管理进程的方方面面。这里只是有了进程的“草图”,进程还没有被创建。
  • 建立进程的虚拟地址空间,也即建立页表,建立虚拟地址到物理地址的映射,到这时一个用户进程所需的基本元素已经具备,一个进程被创建完成,在创建进程的过程中,进程的内核栈也被创建,内核栈不在本文的说明范围内。
  • 接下来就需要可执行文件本身的参与,读取可执行文件头,解析文件头,文件头的前几个字节会指出当前文件是何种类型,如果是#!/bin/sh或 #!/bin/python 则该文件是脚本文件,有负责脚本文件的加载程序,本文只关注可执行文件。建立虚拟地址和可执行文件之间的映射。
  • 初始化进程环境,其中比较重要的一项便是初始化用户栈
  • 跳转到可执行文件的入口,执行可执行文件,运行到用户程序main函数,这其中主要右libc对进行管理。
  • main()函数通过切换栈帧调用其它子函数,子函数也能通过切换栈帧调用其子函数。
  • mian()函数返回,整个进程结束,释放栈占的内存,栈消失

结合上面所述以及下图所示,栈的生命周期可以分为4个部分:

  • Linux Kernel创建用户栈,为栈分配内存空间,处理传递给用户的参数,将参数压入栈中,压入指向参数的argv,计算出argc并将其压栈。
  • libc的_start函数将 Linux Kernel创建的栈和libc库函数接上头,由体系结构相关的汇编语言编写,核心作用是将栈顶地址赋值给SP,还将Linux设置的栈传递、参数传递以及一些库函数的函数指针传递给C语言编写的函数__libc_start_mian_start函数只是起到一个过渡作用,根据CPU的体系结构将Linux Kernel初始化好的栈传递给后续的C语言编写的函数。
  • libc的__libc_start_mian函数是一个C语言写的函数,运行到该函数时用户栈的结构已经是编译器设计的了,同时由于_start函数已经设置好了SP的值,各种压栈、出找操作都在不断调整SP的值。该函数的功能主要有,main调用前的初始化工作;调用main;main函数返回后的清尾工作。
  • 编译器设计main函数及其调用的子函数的栈。

  • 用户栈在系统中的位置
  • 对于Linux内核而言,将整个内存空间划分为两个部分,Kernel Space 和User Space,前者用于支撑Linux Kenrel本身的运行所需空间,后者就是用于支持用户程序所需的运行空间。用户栈就是位于用户空间,一般位于用户空间的最高部分,向低地址处增长。

https://blog.51cto.com/iamokay/2138525

X86-64和ARM64用户栈的结构 (1) ---背景介绍相关推荐

  1. linux的x64与x86_在Linux x86 64机器上链接

    linux的x64与x86 Linking is the process of combining various pieces of code and files in order to const ...

  2. 第19部分- Linux x86 64位汇编GDB单步调试

    第19部分- Linux x86 64位汇编GDB单步调试 本篇我们使用gdb来调试上篇中的汇编代码. gdb调试 使用gdb进行调试. #gdb ./addsum_arg 设置参数: (gdb) s ...

  3. 第41部分-Linux x86 64位汇编MMX使用

    第41部分-Linux x86 64位汇编MMX使用 使用MMX架构需要一下步骤 从整数值创建打包整数值 把打包整数值加载到MMX寄存器中 对打包整数值执行MMX数学操作. 从MMX寄存器获得结果放到 ...

  4. docker拉取不同架构的镜像如:x86拉取arm64的

    docker拉取不同架构的镜像如x86拉取arm64的 一:指定platform方式 首先修改docker的配置文件:vi /etc/docker/daemon.json 添加如下代码:"e ...

  5. 第77部分- Linux x86 64位汇编 优化编译器代码

    第77部分- Linux x86 64位汇编 优化编译器代码-O1/-O2/-O3 仅仅使用汇编语言代码替换C或者C++不会必然使得程序执行的更好,因为编译器已经把所有高级语言代码都转化成了汇编语言. ...

  6. 阿里云发布全新开源操作系统『龙蜥』,支持 X86 64 和 ARM 64 架构及鲲鹏、飞腾等芯片...

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 近日,2021 云栖大会上,阿里云发布了全新操作系统 "龙蜥"(Anolis OS),并宣布开 ...

  7. 概念整理ia32/x86/amd64/ia64/arm64

    x86 早期,1980s年代,x86一般指当时的处理器8088和80286,不过这两个处理器都是16位的.如今,x86通常指32位指令集架构的处理器,比如80386.80386处理器是intel在19 ...

  8. qemu+linux+x86+64,qemu以64位跟32位的方式跑busybox

    qemu以64位和32位的方式跑busybox 两种方式x86_64 和32位的i386方式 -----------x86_64------------------------------------ ...

  9. X86 64位和32位

    不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Architecture),也可以称为指令集(instruction set).Intel将x86系列CP ...

  10. 栈溢出攻击系列:shellcode在linux x86 64位攻击获得root权限(二)shellcode

    shellcode 是一组指令opcode, 是可以被程序运行,因为shellcode是要直接操作寄存器和函数,所以opcode 必须是十六进制的形式. 既然是攻击,那shellcode 主要的目的是 ...

最新文章

  1. 程序员Web面试之前端框架等知识
  2. fork()与pid
  3. Matlab GUI 基础(2):M文件
  4. linux centos7开放端口命令,Centos7开放端口教程
  5. 答应我不要问TCP三次握手四次挥手
  6. python中函数定义_Python中函数的定义与使用
  7. 【图像去噪】基于matlab GUI中值+小波+维纳+滤波器图像去噪【含Matlab源码 616期】
  8. 杰奇2.3内核淡绿唯美小说网站源码 PC+手机版
  9. 数独大师级技巧_数独游戏技巧(图解)
  10. C语言统考试卷一及答案,2017年《计算机应用基础》统考试题及答案
  11. 学会使用这些常见的网络诊断工具,助力你的网络编程之路
  12. 集成学习——BAGGING和随机森林
  13. 陈年咖啡豆是什么?陈年咖啡能喝吗?陈年咖啡有什么特别的?
  14. 360无线网卡linux驱动下载,如何在Linux下写无线网卡的驱动
  15. android的输出流和剪裁python实现以下原理
  16. java.lang.ClassNotFoundException:org.springframework.transaction.TransactionException
  17. 量化投资学习——ESG因子收益分析
  18. 70进货卖100利润是多少_成本20卖100利润是多少 70进货卖100利润是多少
  19. java提取发票里面的信息,如何从OpenERP获取发票中的销售订单字段值
  20. 阿里云服务器无法通过浏览器访问

热门文章

  1. 为SIT Portal切换SIT所连接的数据库
  2. 如何开发一款堪比APP的微信小程序(腾讯内部团队分享)
  3. r0~r15寄存器作用
  4. c++ 覆盖、重载与隐藏
  5. Swift实现糗事百科Demo(实战项目)
  6. 阅读笔记-游戏开发中的人工智能-第3章-移动模式
  7. html5中标签与类的区别,html5中section标签与div标签的区别是什么
  8. java的程序控制关键字_Java编程的关键字有哪些
  9. linux修改max user processes limits
  10. ganglia集群监控搭建步骤