使用了未赋值的局部变量_macOS上的汇编入门(七)——字面量与局部变量
在上一篇文章中,我们分析了第一个汇编程序。
# 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个字节处的值。关于这个记号,我也会在之后的寻址方式中提到。
在汇编语言中,压栈和弹栈的助记符分别是push
和pop
. 这两个操作均有一个操作数。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上的汇编入门(七)——字面量与局部变量相关推荐
- 汇编 编程实现从键盘输入三位以内的十进制负数_macOS上的汇编入门(二)——数学基础...
在正式介绍汇编语言之前,我会先用几篇文章讲一些数学基础和硬件基础.如果读者已经具备了一定的知识基础,可以直接跳过这些文章去汇编语言部分. 二进制,八进制与十六进制 在计算机底层的软件层面,我们通常采用 ...
- macOS上的汇编入门(四)——操作系统基础
当我们学习汇编的时候,除了数学基础以及硬件基础以外,操作系统的基础也是一个至关重要的环节.汇编语言本质上就是机器码的human-readable的版本,而硬件相同,则同一个程序的机器码一定相同.那么我 ...
- macOS上的汇编入门(五)——第一个汇编程序
通过前几篇文章,我们逐步建立了学习汇编语言之前需要的基础知识.接下来,在这篇文章中,我们开始编写我们的第一个汇编程序了. 编辑器,汇编器与链接器 工欲善其事,必先利其器.我们编写汇编语言,至少需要编辑 ...
- C#使用了未赋值的局部变量
错误原因: 我们先看下例子: int A; Console.WriteLine("数字:{0:d}", A);//在控制台输出文本 这时提示错误:错误 1 使用了未赋值的局部变量& ...
- (转)使.Net程序在未安装framework的电脑上运行(公布方法、源代码)
从四年前刚学C#时就一直想找到一种方法可以让.Net程序在未安装framework的电脑上运行,但一直没有找到真正可用的.虽然有些公司发布了可以将.net代码编译成navtive代码以脱离.n ...
- java赋值兼容原则,多态问题抛出(赋值兼容性原则遇上父类与子类同名函数的时候)...
首先通过一个段代码来分析 #include class Parent//定义父类 { public: Parent(int a = 0) { this->a = a; } void print( ...
- 某OA ajax.do 未授权漏洞任意文件上传getshell复现
某OA ajax.do 未授权漏洞任意文件上传getshell复现 0x00 简介 某OA A8 是一款流行的协同管理软件,在各中.大型企业机构中广泛使用. 由于某旧版本某些接口能被未授权访问,并且部 ...
- 使用winhex恢复U盘RAW格式并提示未格式化故障U盘上的数据
使用winhex恢复U盘RAW格式并提示未格式化故障U盘上的数据 在打开U盘和移动硬盘时,系统提示需要格式化,查看U盘和硬盘属性分区格式为RAW,但如果真的在windows下对其进行格式化的话,系统往 ...
- Java未赋值成员变量的初始值(默认值)
Java未赋值成员变量的初始值(默认值) java中的所有变量必须先声明,后赋值才能使用. java中的成员变量,在创建对象的时候,都会执行一次初始化操作,都会给一个默认值. 基本数据类型默认值都是0 ...
最新文章
- 超酷flash光芒光线特效
- macbook下载苹果版Photoshop cc2019 for mac
- java的知识点13——多态、对象的转型(casting)、final关键字、抽象方法和抽象类、接口的作用、如何定义和使用接口?、接口的多继承、面向接口编程
- 字典数组根据某key排序
- MediaPipe: Google Research 开源的跨平台多媒体机器学习模型应用框架
- 计算机题硬盘分区首先,您对计算机硬盘分区了解多少: 如何进行分区合理?
- android studio 单元测试用法,基于Android Studio2.1.1 进行单元测试完整教程
- 手机QQ空间装逼代码收集
- vue 使用vue-print-nb 实现打印功能 和 用针式打印机打印模糊问题
- android sdk的封装,Android封装SDK的使用
- RBF神经网络算法分析与应用(适合快速入门实战)
- 如何查看本机的内网IP
- Python-飞机大战(二)
- 我从非科班转到图像算法工程师(图像识别、机器学习、深度学习)(3个月)的经历
- 甲骨文确认关闭中国研发中心
- VMware三种网络模式
- Linux添加SSH Key到Github账户
- NC65发布webservice接口开发环境启动服务找不到接口
- 【看论文】之《番茄采摘机器人关键技术研究_王丽丽 》
- 慕课网七月python_7七月的新全栈课