本篇是上课随笔欢迎大家补充指正
汇编语言的引入
机器指令是二进制表达的机器操作,汇编语言是这些机器操作的符号化表达
各种操作和资源的符号化实现了对机器物理资源的直接引用
汇编实现代码对底层硬件的直接控制
特点:
- 直观,可读性强,与机器指令一一对应
- 寄存器,内存单元
优点:
编写出来的程序执行效率高,CPU严格按照程序员的要求去做,没有多余的额外操作。
缺点:
用机器语言编写程序有很高的要求和许多不便。
如何分别图示代码是可执行文件 or 目标文件:
区别:目标文件地址从0开始,而可执行文件在代码段中,不从0开始
为了改善机器指令的可读性,选用了一些能反映机器指令功能的单词或词组来代表该机器指令:MOV, ADD, SUB等等。
不再关心机器指令的具体二进制编码。与此同时,也把CPU内部的各种资源符号化,使用该符号名也等于引用了该具体的物理资源,如EAX, ESP等等。
CPU内部寄存器
如图所示,eax,ebx,ecx,edx是数据寄存器,剩下的分别为指针寄存器和索引寄存器。这四个 32 位数据寄存器用于算术、逻辑和其他运算。
这些 32 位寄存器可以通过三种方式使用方式:
- 作为完整的32位数据寄存器:EAX、EBX、ECX、EDX
- 32 位寄存器的下半部分可用作四个 16 位数据寄存器:AX、BX、CX 和 DX
- 上述4个16位寄存器的下半部分和上半部分可以用作8个8位数据寄存器:AH、AL、BH、BL、CH、CL、DH和DL
不同部分功能:
- AX 为主累加器:它用于输入/输出和大多数算术指令。 例如,在乘法运算中,根据操作数的大小将一个操作数存储在EAX或AX或AL寄存器中
- BX 被称为基址寄存器:因为它可用于索引寻址
- CX 被称为计数寄存器:与 ECX 一样,CX 寄存器存储迭代操作中的循环计数
- DX被称为数据寄存器: 它还用于输入/输出操作。 它还与 AX 寄存器和 DX 一起使用,用于涉及大值的乘法和除法运算
内存按字节编制,每个地址的存储单元可以存放1字节(8 bits)数据
存储数据:
大端法与小端法
- 小端法:低位字节放在低地址
- 大端法:低位字节放在高地址
以小端法为例:
存储数据 0x87654321,一共四字节,占用四个内存地址,一个int是4bits,一个字节是8bits,所以一个内存单元存储两个int
小端法:低位字节放在低地址
大端法与此类似
读取数据
取数据过程:
- 小端法:低位字节放在首地址上
- 大端法:高位字节放在首地址上
以读取用小端法存储的数据为例:
从低位地址开始读取数据,类似于一个栈,先进后出
注意:内存地址是连续的
AT&T风格汇编代码
格式:
movl $8 , %eax;
操作数 源寄存器,目的寄存器
AT&T 汇编中:
- 寄存器前被冠以“%”
- 立即数前被冠以“$”
- 十六进制数前被冠以“0x”
指令后缀:
- q :8B
- l : 4B
- w :2B
- b :1B
操作数:
- 立即数(immediate)
- 寄存器(register)
- 存储器(memory)
操作数方向:
源操作数在前,目的操作数在后,这一点和intel汇编语法正好相反。
上课实验:
程序源码的生成目标文件和可执行文件:
链接: https://pan.baidu.com/s/1cjTe2ldipxNWryHW32w2tQ?pwd=4y2a 提取码: 4y2a
命令:xxx为文件名
as -g xxx.s -o xxx.o
ld xxx.o -o xxx
如图所示
使用命令:xxx为可执行文件名
objdump -d xxx
来查看文件内容,如图所示
汇编中的mov:
能实现以下操作:
- CPU内部寄存器之间数据的任意传送(除了码段寄存器CS和指令指针IP以外)。
- 立即数传送至CPU内部的通用寄存器组(即AX、BX、CX、DX、BP、SP、SI、DI),给这些寄存器赋初值。
- CPU内部寄存器(除了CS和IP以外)与存储器(所有寻址方式)之间的数据传送,可以实现一个字节或一个字的传送。
- 能实现用立即数给存储单元赋初值。
可参考博客:汇编语言||基本传送指令MOV的用法详解_汇编mov指令详解-CSDN博客
movl: 用于传送32位的长字值
movw: 用于传送16位的字值
movb: 用于传送8位的字值
gdb调试:
进入调试命令:xxx为可执行文件
gdb -q xxx
如图所示
list命令:
list
l(list)命令用于列出源码 最多列出10行,继续l或空格键
list命令依然有很多用法,但是老师上课没有讲,此处不做补充。
break命令:
默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。设置断点后,会停止在断点处。
break 命令(可以用 b 代替)常用的语法格式有以下 2 种。
(gdb) break location
(gdb) break ... if cond
1) 第一种格式中,location 用于指定打断点的具体位置,其表示方式有多种
location 的值 | 含 义 |
---|---|
linenum | linenum 是一个整数,表示要打断点处代码的行号。要知道,程序中各行代码都有对应的行号,可通过执行 l(小写的 L)命令看到。 |
filename:linenum | filename 表示源程序文件名;linenum 为整数,表示具体行数。整体的意思是在指令文件 filename 中的第 linenum 行打断点。 |
+ offset - offset | offset 为整数(假设值为 2),+offset 表示以当前程序暂停位置(例如第 4 行)为准,向后数 offset 行处(第 6 行)打断点;-offset 表示以当前程序暂停位置为准,向前数 offset 行处(第 2 行)打断点。 |
function | function 表示程序中包含的函数的函数名,即 break 命令会在该函数内部的开头位置打断点,程序会执行到该函数第一行代码处暂停。 |
filename:function | filename 表示远程文件名;function 表示程序中函数的函数名。整体的意思是在指定文件 filename 中 function 函数的开头位置打断点。 |
2) 第二种格式中,... 可以是表 1 中所有参数的值,用于指定打断点的具体位置;cond 为某个表达式。整体的含义为:每次程序执行到 ... 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。
基本演示如图所示:
info命令:
info b
info b 可以让我们查看当前有哪些断点
run命令:
启动程序。GDB 调试器提供了多种方式来启动目标程序,其中最常用的就是 run 指令,其次为 start 指令。也就是说,run 和 start 指令都可以用来在 GDB 调试器中启动程序,它们之间的区别是:
- 默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;
- start 指令会执行程序至 main() 主函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。
next命令:
·借助 next 命令可以控制 GDB 单步执行程序。所谓单步调试,就是通过一行一行的执行程序,观察整个程序的执行流程,进而尝试发现一些存在的异常或者 Bug。
next 命令可以缩写为 n 命令,使用方法也很简单,语法格式如下:
next count
参数 count 表示单步执行多少行代码,默认为 1 行。
如图所示:
print命令
以指定类型打印指定地址内容
以十六进制查看立即数
p/x num
如图所示,查看eax中的数据
x 按十六进制格式显示变量。d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。o 按八进制格式显示变量。
t 按二进制格式显示变量。a 按十六进制格式显示变量。
c 按字符格式显示变量。f 按浮点数格式显示变量
examine命令:
x :查看内存地址中的值
x/<n/f/u> <addr>
<n/f/u>
- n:是正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,一个内存单元的大小由第三个参数u定义。
- f:表示addr指向的内存内容的输出格式,s对应输出字符串。
- u:就是指以多少个字节作为一个内存单元-unit,默认为4。当然u还可以用被一些字符表示,如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.
<addr>:表示内存地址。
整合这个命令的诠释:
就是以addr为起始地址,返回n个单元的值,每个单元对应u个字节,输出格式是f。
寻址模式:
- 立即数寻址-1005.s
- 寄存器寻址 - 1006.s
- 绝对寻址 - 1007.s
- 间接寻址 - 1008.s
- 基址偏移量寻址
- 变址寻址-1009.s
- 变址基址寻址
- 比例变址寻址-1010.s
- 比例变址基址寻址
立即数寻址:
示例:movl $1,%eax
将立即数1传送到eax寄存器中
寄存器寻址:
示例:movl %ebx,%eax
将ebx中的数据传送到eax寄存器中
绝对寻址:
示例:movl 0x08048054,%eax
将地址为0x8048054的内存单元中的数据传送到eax寄存器中
间接寻址:
示例: movl (%ebx),%eax
将ebx中的数据作为基址,找到地址为基址的内存单元,将其数据送到eax寄存器中
基址偏移量寻址:
示例:movl 0x8(%ebx),%eax
将ebx中的数据作为基址,再找到地址为基址+8的内存单元,将其数据送到eax寄存器中
变址寻址:
示例:movl (%ebx,%edx),%eax
新地址=ebx中的数据+edx中的数据
变址基址寻址:
示例:movl 0x8(%ebx,%edx),%eax
新地址=8+ebx中的数据+edx中的数据
比例变址寻址:
示例:movl (%ebx,%ecx,0x2),%eax
新地址=ebx中的数据+ecx中的数据*2
比例变址基址寻址:
示例:movl 0x8(%ebx,%ecx,0x2),%eax
新地址=8+ebx中的数据+ecx中的数据*2
总结一下,这节课讲的东西挺多,但是不难,欢迎大家指正