在上一篇文章中,我们分析了第一个汇编程序。

# exit.s    .section    __TEXT,__text.globl  _main
_main:movq    $0, %raxretq

这个汇编程序是我们所有汇编程序的框架,因为它实现了程序进入和程序退出的功能。我们接下来所有的程序都是在这个程序的基础上进行修改。

在这篇文章中,我主要介绍的是汇编语言中变量的使用。在x86-64架构下,寄存器的数量很少。而且,寄存器的作用往往是用于运算而不是用于存储。那么,我们在程序中该如何使用变量呢?

.equ定义字面量

最简单的定义变量的方式,是利用汇编器指令.equ. 这类似于C语言中的#define. 比如说,我在程序开头写上

.equ    maxCount, 0x114514

那么,我在之后的程序里就可以写

movq    $maxCount, %rax

来表示将0x114514赋值给rax寄存器。

同时这里应当指出,这个指令是汇编器指令,在汇编的时候,会自动将所有的maxCount直接用0x114514替代。比如说,我有以下程序:

.text.globl  _main.equ    maxCount, 0x114514
_main:movq    $maxCount, %raxretq

我们通过汇编、链接以后,得到一个test可执行文件。我们可以用之前提到的MachOView软件,或者在终端中键入

otool -v -t ./test

来查看生成的可执行文件中__TEXT__text节的内容:

由此可知,最终生成的文件中,是直接替换得到的。

此外,.equ还有一个比较方便的地方在于,它可以支持简单的算术运算,如加减乘除等。比如说,我可以写.equ maxCount, 1919-810, 那么接下来所有出现maxCount的地方,都会用1109来替代。

但是,正如C语言中的#define定义的宏一样,.equ定义的变量只是一个简单的替换,并不支持对这个变量重新赋值之类的操作。这个变量也没有其地址,只是一个字面量。

局部变量

我们知道,在C语言中,局部变量在栈上分配。在汇编语言中也是这样。因此,我们来回忆一下「栈」的概念。

在操作系统基础中,我们谈到,在一个程序运行的时候,系统会自动给这个程序分配一个栈区。这个栈区和数据结构中所说的栈类似,也支持压栈和弹栈的操作。栈区在逻辑地址空间里是一块连续的空间,栈底是固定的,每次压栈,都会使栈顶向逻辑地址减小的方向移动。

在几个寄存器中,有一个寄存器和栈的关系非常大,那就是rsp寄存器。从它的名字就可以看出来,stack pointer, 它存储的值永远是栈顶的地址,所以它又被叫做栈顶指针。我们可以用(%rsp)来获取栈顶存储的值,通过a(%rsp), 其中a是任何一个整数,来获取地址是rsp存储的值加a处的内存单元的值。比如说,2(%rsp)就是栈顶上方(逻辑地址增大方向)2个字节处的值,-2(%rsp)就是栈顶下方(逻辑地址减小方向)2个字节处的值。关于这个记号,我也会在之后的寻址方式中提到。

在汇编语言中,压栈和弹栈的助记符分别是pushpop. 这两个操作均有一个操作数。push的操作是将栈顶指针向下移动(也就是将rsp内的值减小),并将移动后rsp对应位置内存区域的值赋为其操作数,而pop则相反。这里“向下移动”的距离是根据push后面跟着的字母决定的,如pushq就是把rsp内的值减8.

此外,如果是想获得栈顶的值,而不弹栈,可以直接用mov来实现。如popq %rax是将栈顶的8个字节内存储的值赋给rax, 并且栈顶指针向上移动8个字节。而movq (%rsp), %rax则是只将栈顶的8个字节内存储的值赋给rax, 不涉及栈顶指针的移动。而如果只想弹栈却不想赋值,那么直接对rsp进行add即可。如想把栈顶的8个字节的数据弹栈,就直接addq $8, %rsp.

同时,对于push而言,如果我们一下子准备把许多值压入栈内,那么可以先用sub指令减小rsp, 再用mov移动。比如说:

# method 1
pushq   $0x114514
pushq   $0x1919
pushq   $0x810# method 2
subq    $24, %rsp
movq    $0x114514, 16(%rsp)
movq    $0x1919, 8(%rsp)
movq    $0x810, (%rsp)

方法一和方法二的最终效果是一样的。但是,我们建议使用方法二,也就是“先sub, 再mov”,因为这样更高效。

使用局部变量

讲完了栈的概念,接下来就是如何使用局部变量了。使用局部变量非常简单,就是将局部变量放到栈上,然后使用的时候直接去访问栈上对应的地址空间就行。然后在返回之前,把栈恢复即可。

但是,这里有一个常用的技巧。像上面的例子中写的,我们是通过对rsp中存储的地址加偏移量去访问局部变量,但是,如果我们之后又有了压栈、弹栈的操作,那么,偏移量就会改变。这种不稳定性十分不利于我们编程。因此,我们又用了另一个寄存器rbp来解决这个问题。rbp, 顾名思义,base pointer, 基地址指针,一般是用来使用偏移量寻址的。我们使用的技巧是,先将rbppush进栈(之所以保留我会在后面的调用约定里说到),然后利用之前的手法对rspsub. 然后,利用rbp的偏移量来引用局部变量。最后在返回前,将rbp赋值给rsp, 此时栈顶指针指向的是最初对rbppush之后的位置,然后将栈顶pop出来给rbp,最后返回。

比如说,我有以下C程序:

int main()
{int a = 0x114514;int b = 0x1919;int c = 0x810;return 0;
}

那么,它对应的汇编程序如下:

_main:pushq   %rbpmovq    %rsp, %rbpsubq    $24, %rspmovq    $0x114514, -8(%rbp)movq    $0x1919, -16(%rbp)movq    $0x810, -24(%rbp)movq    $0, %raxmovq    %rbp, %rsppopq    %rbpretq

它对应的栈的变化如图所示:

由此可见,在执行完popq %rbp之后,栈又恢复为最初进入时的模样。

我们在使用rbp+偏移量来访问局部变量的时候,有时候会觉得要把变量对应的偏移量记住,这会比较麻烦。我们可以结合上面讲到的.equ定义字面量来解决这一问题:

_main:.equ    a, -8.equ    b, -16.equ    c, -24pushq   %rbpmovq    %rsp, %rbpsubq    $24, %rspmovq    $0x114514, a(%rbp)movq    $0x1919, b(%rbp)movq    $0x810, c(%rbp)movq    $0, %raxmovq    %rbp, %rsppopq    %rbpretq

这样,我们只需要之后用a(%rbp)就可以指代a了。

可以在哪看到这系列文章

我在我的GitHub上,知乎专栏上和CSDN上同步更新。

上一篇文章:macOS上的汇编入门(六)——汇编语言初识

下一篇文章:macOS上的汇编入门(八)——寻址方式与全局变量

使用了未赋值的局部变量_macOS上的汇编入门(七)——字面量与局部变量相关推荐

  1. 汇编 编程实现从键盘输入三位以内的十进制负数_macOS上的汇编入门(二)——数学基础...

    在正式介绍汇编语言之前,我会先用几篇文章讲一些数学基础和硬件基础.如果读者已经具备了一定的知识基础,可以直接跳过这些文章去汇编语言部分. 二进制,八进制与十六进制 在计算机底层的软件层面,我们通常采用 ...

  2. macOS上的汇编入门(四)——操作系统基础

    当我们学习汇编的时候,除了数学基础以及硬件基础以外,操作系统的基础也是一个至关重要的环节.汇编语言本质上就是机器码的human-readable的版本,而硬件相同,则同一个程序的机器码一定相同.那么我 ...

  3. macOS上的汇编入门(五)——第一个汇编程序

    通过前几篇文章,我们逐步建立了学习汇编语言之前需要的基础知识.接下来,在这篇文章中,我们开始编写我们的第一个汇编程序了. 编辑器,汇编器与链接器 工欲善其事,必先利其器.我们编写汇编语言,至少需要编辑 ...

  4. C#使用了未赋值的局部变量

    错误原因: 我们先看下例子: int A; Console.WriteLine("数字:{0:d}", A);//在控制台输出文本 这时提示错误:错误 1 使用了未赋值的局部变量& ...

  5. (转)使.Net程序在未安装framework的电脑上运行(公布方法、源代码)

        从四年前刚学C#时就一直想找到一种方法可以让.Net程序在未安装framework的电脑上运行,但一直没有找到真正可用的.虽然有些公司发布了可以将.net代码编译成navtive代码以脱离.n ...

  6. java赋值兼容原则,多态问题抛出(赋值兼容性原则遇上父类与子类同名函数的时候)...

    首先通过一个段代码来分析 #include class Parent//定义父类 { public: Parent(int a = 0) { this->a = a; } void print( ...

  7. 某OA ajax.do 未授权漏洞任意文件上传getshell复现

    某OA ajax.do 未授权漏洞任意文件上传getshell复现 0x00 简介 某OA A8 是一款流行的协同管理软件,在各中.大型企业机构中广泛使用. 由于某旧版本某些接口能被未授权访问,并且部 ...

  8. 使用winhex恢复U盘RAW格式并提示未格式化故障U盘上的数据

    使用winhex恢复U盘RAW格式并提示未格式化故障U盘上的数据 在打开U盘和移动硬盘时,系统提示需要格式化,查看U盘和硬盘属性分区格式为RAW,但如果真的在windows下对其进行格式化的话,系统往 ...

  9. Java未赋值成员变量的初始值(默认值)

    Java未赋值成员变量的初始值(默认值) java中的所有变量必须先声明,后赋值才能使用. java中的成员变量,在创建对象的时候,都会执行一次初始化操作,都会给一个默认值. 基本数据类型默认值都是0 ...

最新文章

  1. 超酷flash光芒光线特效
  2. macbook下载苹果版Photoshop cc2019 for mac
  3. java的知识点13——多态、对象的转型(casting)、final关键字、抽象方法和抽象类、接口的作用、如何定义和使用接口?、接口的多继承、面向接口编程
  4. 字典数组根据某key排序
  5. MediaPipe: Google Research 开源的跨平台多媒体机器学习模型应用框架
  6. 计算机题硬盘分区首先,您对计算机硬盘分区了解多少: 如何进行分区合理?
  7. android studio 单元测试用法,基于Android Studio2.1.1 进行单元测试完整教程
  8. 手机QQ空间装逼代码收集
  9. vue 使用vue-print-nb 实现打印功能 和 用针式打印机打印模糊问题
  10. android sdk的封装,Android封装SDK的使用
  11. RBF神经网络算法分析与应用(适合快速入门实战)
  12. 如何查看本机的内网IP
  13. Python-飞机大战(二)
  14. 我从非科班转到图像算法工程师(图像识别、机器学习、深度学习)(3个月)的经历
  15. 甲骨文确认关闭中国研发中心
  16. VMware三种网络模式
  17. Linux添加SSH Key到Github账户
  18. NC65发布webservice接口开发环境启动服务找不到接口
  19. 【看论文】之《番茄采摘机器人关键技术研究_王丽丽 》
  20. 慕课网七月python_7七月的新全栈课

热门文章

  1. 面试题:聊聊Unix与Java的IO模型?
  2. leetcode 441. 排列硬币(Java版)
  3. 【转自CodeSheep】程序猿好书推荐
  4. springcloud实践之断路器:Hystrix原理和解构
  5. MyBatisPlus注入公共Sql问题
  6. Java如何查看死锁?
  7. Spring事务管理--嵌套事务详解
  8. leetcode-- 338. Counting Bits
  9. 【简单解法】1093 字符串A+B (20分)_16行代码AC
  10. 数据库连接池技术详解【吐血整理,疯狂推荐】