ASM汇编语言基础

ASM汇编语言基础

简介

  • 计算机最底层的语言是机器语言;

  • 机器语言由一系列机器指令组成,现代二进制计算机的机器指令都是一系列01串,对应硬件中的高低电平;

  • 汇编语言是计算机机器语言的一种助记表示;

  • 汇编语言的主体是汇编指令,每条汇编指令对应一条机器指令;

  • 汇编语言可直接访问CPU内部的各个部件,CPU安装汇编语言生成的机器码按序执行;

  • 汇编语言和具体的机器体系架构直接相关;

CPU架构

寄存器

寄存器按照其用途可分为以下4类:

  • 数据寄存器
  • 指针及变址寄存器
  • 段寄存器
  • 控制寄存器
类型寄存器名称8位16位32位64位说明
通用数据寄存器用来存放操作数,运算结果或者其他信息。
累加寄存器AH|ALAXEAXRAX
基址寄存器BH|BLBXEBXRBX
计数寄存器CH|CLCXECXRCX
数据寄存器DH|DLDXEDXRDX在某些IO指令中,DX被用来存放端口地址;
指针及变址寄存器
基址寄存器
(BasePoint)
BPEBPRBP栈桢(Frame)底指针
栈顶指针寄存器
(StackPoint)
SPESPRSP栈栈顶指针
源变址寄存器
(SourceIndex)
SIESIRSIDS:SI一般表示源地址
目的变址寄存器
(DestIndex)
DIEDIRDIES:DI一般表示目的地址
指令及状态寄存器
指令指针寄存器
(InstructPointer)
IPEIPRIPCS:IP表示了当前执行指令在内存中的地址
状态寄存器FLAGSEFLAGSRFLAGS存放条件标志码、控制标志和系统标志等
段寄存器
代码段寄存器
(CodeSegment)
CSECS代码段起始地址
数据段寄存器
(DataSegment)
DSEDS数据段起始地址
栈寄存器
(StackSegment)
SSESS
附加段寄存器
(ExtentSegment)
ESEES
FSEFS
GSEGS
TSS寄存器TR指向当前任务的TSS段
中断描述符表寄存器IDTR指向中断表述符表在内存中的起始位置
全局描述符表寄存器GDTR
局部描述符表寄存器LDTR

寄存器使用约定

x86 调用约定(不同编译器可能不遵守):

  • %rax:用作返回值寄存器

  • %rip:指令指针寄存器,存放下条指令的地址

函数调用时:

  • 如果参数数量 <= 6,就通过 %rdi%rsi%rdx%rcx%r8%r9 的寄存器来传递参数,分别对应函数的第 1 ~ 6 个参数。

  • 如果参数数量 > 6,需要通过栈来传递。传递时对齐 8 字节。

调用者保存寄存器和被调用者保存寄存器

  • 调用者(Caller)保存: %rdi, %rsi, %rdx, %rcx, %r8, %r9, %r10, %r11 

  • 被调用者(Callee)保存: %rbx, %rbp, %rsp, %r12, %r13, %r14

栈桢

%rsp 和 %rbp 都可以被称为栈指针(stack pointer)。

  • %rsp 是栈顶指针,它指示栈顶的位置。

  • %rbp 是栈基址指针,它指向当前栈底的位置,它是历史栈顶的快照,因此也充当各栈帧的分隔符。

通过 %rbp,内存中串起一个栈帧链表。只要不断对其解引用就可以回溯到最前面的栈帧。

x86汇编语言格式

  • x86汇编语言是基于intel x86系列处理器所用的汇编语言标准;

  • 常用的x86汇编语言格式主要分为2种:

    • intel x86汇编语言格式

      • 由intel自己主导设计;
    • AT&T x86汇编语言格式

      • 由发明unix的AT&T实验室设计;

      • unix, linux, gcc相关的工具链都是使用该汇编语言格式;

      • go语言的汇编是AT&T汇编的一个变种;

Intel 和 AT&T 汇编区别

intel和AT&T汇编的主要区别如下:

Intel CodeAT&T Code说明
mov eax,1movl $1,%eax将eax设为1
mov ebx, 0ffhmovl $0xff,%ebx将ebx设置为0xff
int 80hint $0x80调用0x80中断处理程序
mov ebx, eaxmovl %eax, %ebx将eax中的值复制到ebx中
mov eax,[ecx]movl (%ecx),%eax将eax中的值作为偏移,
mov eax,[ebx+3]movl 3(%ebx),%eax
mov eax,[ebx+20h]movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h]addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx]leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]subl -0x20(%ebx,%ecx,0x4),%eax
IntelAT&T说明
寄存器命名eax%eax
操作数顺序 ,
立即数_value$_value
mov ebx,0xd00dmovl $0xd00d,%ebx将0xd00d地址存入ebx寄存器
操作数长度标识mov bx,axmovw %ax,%bx
间接寻址方式immed32(basepointer,
indexpointer,indexscale)
[basepointer +
indexpointer*indexscale + immed32]

汇编语言组成

  • 汇编语言由一系列汇编指令和伪指令组成;

  • 每条汇编指令对应一条机器码,可由机器直接执行生成的机器码;

  • 伪指令用于汇编程序提供附加信息,用于控制汇编程序生成机器码,本身没有机器码对应,无法由机器执行;

指令()

  • 指令由字母开头;

  • 指令格式为:<OP> []

  • 每条指令至少包含一个操作码;

  • 每条指令对应一条机器码;

  • 指令生成机器码后,可由硬件执行;

伪指令(Pseudo Instruction)

  • 伪指令(Pseudo Instruction)是用于对汇编过程进行控制的指令;

  • 伪指令只用于汇编过程中为汇编程序提供汇编信息,不可执行指令,没有机器代码。例如:哪些是指令、哪些是数据及数据的字长、程序的起始地址和结束地址等;

  • 伪指令都由句号('.')开头,其余是字母,通常使用小写;

类别伪指令格式说明示例
.code16.code16生成16位的指令
.globl.globl <label_name>将符号定义为全局可链接的,
使符号可跨文件访问
.globl SYMBOL_NAME(idt)
.extern.extern <label_name>定义外部符号
.text
数据定义
.byte.byte [,,..]定义字节.byte 0x80,0xca
.word.word [,,..].word 0x800,
.file.file ""开启一个逻辑文件.file ”stage1.s”
.fill.fill ,,填充*个val.fill NR_CPUS*4,8,0
符号定义
.equ

伪指令分为:

  • 数据定义伪指令

    • 格式:[<变量名>] <类型> <初值表>

  • 符号定义伪指令

    • EQU(等值伪指令):<符号名> EQU <表达式>

1
2
3
4
5
6
.code16               ; 生成16位的指令
.section .data        ; 数据段
.section .text        ; 代码段


.file stage1.s

内存寻址

intelAT&T
立即数寻址MOV EBX, 2movl $2, %ebx将32bit的立即数2存入寄存器ebx中
直接寻址MOV EAX, 8000hmovl 0x8000, %eax将内存地址0x8000上的32位数据放到eax中
间接寻址MOV EBX, [EAX]movl (%eax), %ebx
基址寻址MOV EBX, [EAX-4]movl -4(%eax), %ebx
变址寻址MOV ECX, [EAX+EBX+4]movl 4(%eax,%ebx), %ecx

AT&T汇编

AT&T伪指令

1
2
.byte 0x80,0xca    //.byte 伪指令
.word 0x800

变量

  • 寄存器前被冠以“%”
  • 立即数前被冠以“$”
  • 十六进制数前被冠以“0x”
1
2
3
4
5
6
7
    .globl  var
    .data
    .align 4
    .type   var, @object
    .size   var, 4
var:
    .long   10

应该放在.data之后,使用“.类型 值”的形式定义,基本类型有byte、short、long 3种,分别占用1/2/4个字节。

然后在之前加上标签。标签不是必须的,标签是当前数据的地址,在其它地方可以使用标签。还有其它定义形式:

1
2
3
    .long 1,2,3
    .zero 10
    .string "hello"

第一行,类似指定一个数组。

第二行,10个字节的数据,全部为0。

第三行,一个字符串。当然,需要.globl声明这个符号,以及指定对齐、类型、大小等。

定义函数

和变量定义类似

1
2
3
4
5
    .text
    .globl  func
    .type   func, @function
func:
    汇编指令

操作数

有3类操作数,立即数、寄存器和存储器引用。下面是3种操作数的AT&T语法:

  • 立即数,使用$加一个数字,如$1, $-20, $0x12等。

  • 寄存器,使用%加上寄存器的名字,如%ebp, %ah等。

  • 存储器引用,格式为Imm(Ea,Ei,s),存储器的值为Imm + Ea + Ei * s,s必须为1,2,4,8。可以省略Imm, Ei和s。如下合法的存储器引用:(%ebp), -1(%ebp), 7(%edx,%edx,4)。甚至可以直接是数字表示的内存地址,如0x100.

标签作操作数

如果标签var是变量,则var表示的变量的内存地址,$var表示的内存地址的立即数。

  • mov var, %eax,把var地址处内存的值拷贝到eax。

  • mov $var, %eax,把var值拷贝到寄存器eax。

  • mov var+1, %eax,把var+1地址处内存的值拷贝到eax。

  • mov $var+1, %eax,把var+1值拷贝到寄存器eax。

指令后缀

C声明Intel数据类型后缀大小
char字节b1
shortw2
int双字l4
long四字q8
char*四字q8
float单精度s4
double双精度l8

重要的指令

call

  • 负责将被调指令地址写入 rip

  • 还要负责将返回地址压入栈中

push/pop

push把数据压入栈。等价于:先减去栈指针的值,然后将压入数据的值写入栈顶。

1
2
3
4
pushl $0x1234
// 等价于
sub $0x4 %rsp
movl $0x1234 (%rsp)

pop 把数据弹出栈,是 push 的逆变换。等价于:把栈顶数据写入操作数,然后增加栈指针的值。

1
popq %rax

AT&T汇编示例

1
2
3
4
5
// square.c
int32_t square(int32_t num) {
    int k = 10;
    return 5 * num + 6 - k;
}

对应的AT&T汇编代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
; square.s
square:
        // 注意,此时 create_obj 的栈帧还未构建完毕
        // 因此 rsp 虽然是栈顶指针,但起始指向的是上一个汉奸的栈顶
        // 同时 rbp 也是指向上一个函数的栈底
        pushq   %rbp                // 保存前一个栈帧栈底到当前栈顶。
                                    // 这是为了让上一个函数调用后能恢复它栈底的值到 %rbp
        movq    %rsp, %rbp          // 保存当前栈顶的值到 `%rbp`,作为当前函数的栈底指针的值。
        movl    %edi, -20(%rbp)     // 将函数的第一个参数 num 保存到当前函数的栈底 -20 处。
        movl    $10, -4(%rbp)       // 将函数的第一个局部变量 k 保存到当前函数的栈底 -4 处。
        movl    -20(%rbp), %edx     // 将变量 num 保存到 %edx 中。
        movl    %edx, %eax          // 将变量 num 保存到 %eax 中。
        sall    $2, %eax            // 将变量 num 乘以 4(左移 2 相当于乘以 4) 得到 %eax = num * 4
        addl    %edx, %eax          // 将变量 %eax 加上 %edx ,即 %eax = num * 4 + num = num * 5
        addl    $6, %eax            // 将变量 %eax 加上 6,即 %eax = num * 5 + 6;
        subl    -4(%rbp), %eax      // 将变量 %eax 减去 k ,即 %eax = num * 5 + 6 - k;        
        popq    %rbp                // 将栈顶保存到 %rbp 中,并释放栈帧。
        ret

编译

1
2
3
4
5
6
7
8
; demo.s
.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $4, %ebx
int $0x80
1
2
3
4
5
6
// 汇编
[]$ as demo.s -o demo.o
// 链接
[]$ ld demo.o -o demo
//
[]$ ./demo

Intel汇编

通用数据传送指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
MOV     传送字或字节
MOVSX   先符号扩展,再传送.  
MOVZX   先零扩展,再传送.  
PUSH    把字压入堆栈.  
POP     把字弹出堆栈.  
PUSHA   把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.  
POPA    把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.  
PUSHAD  把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.  
POPAD   把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.  
BSWAP   交换32位寄存器里字节的顺序  
XCHG    交换字或字节.(至少有一个操作数为寄存器,段寄存器不可作为操作数)  
CMPXCHG 比较并交换操作数.(第二个操作数必须为累加器AL/AX/EAX)  
XADD    先交换再累加.(结果在第一个操作数里)  
XLAT    字节查表转换.----BX指向一张256字节的表的起点,AL为表的索引值(0-255,即0-FFH);返回AL为查表结果.([BX+AL]->AL)  

输入输出指令

1
2
IN      I/O端口输入. ( 语法: IN   累加器,    {端口号│DX} )  
OUT     I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )输入输出端口由立即方式指定时,    其范围是 0-255; 由寄存器 DX 指定时,其范围是    0-65535.  

目的地址传送指令

1
2
3
4
5
6
7
LEA <DST>, <SRC>  ;将<SRC>的偏移量OA装载到<DST>中.例: LEA DX,string ;  把偏移地址存到DX.  
LDS <DST>, <SRC>  ;将当前数据段中的一个双字数据装入到一个通用寄存器SI(双字数据的低字)和数据段寄存器DS(双字数据的高字)中;
传送目标指针,把指针内容装入DS.例: LDS SI,string   ; 把段地址:偏移地址存到DS:SI.  
LES     传送目标指针,把指针内容装入ES.例: LES DI,string   ;把段地址:偏移地址存到ES:DI.  
LFS     传送目标指针,把指针内容装入FS.例: LFS DI,string   ;把段地址:偏移地址存到FS:DI.  
LGS     传送目标指针,把指针内容装入GS.例: LGS DI,string   ;把段地址:偏移地址存到GS:DI.  
LSS     传送目标指针,把指针内容装入SS.例: LSS DI,string   ;把段地址:偏移地址存到SS:DI.  

标志传送指令

1
2
3
4
5
6
LAHF    标志寄存器传送,把标志装入AH.  
SAHF    标志寄存器传送,把AH内容装入标志寄存器.  
PUSHF   标志入栈.  
POPF    标志出栈.  
PUSHD   32位标志入栈.  
POPD    32位标志出栈.  

EFLAGS寄存器

  • FR(控制标志位):
    • CF(进位标识位): 进行加减运算时, 如果最高二进制位产生进位或错位, CF则为1, 否则为0. 程序设计中, 常用条件转移指令JC, JNC指令据此标志位实现转移
    • PF(奇偶标志位): 操作结果中二进制位1的个数为偶数是, PF为1, 某则为0
    • AF(辅助进位标志位):运算时半字节产生进位或借位时, AF为1, 某则为0. 主要用于BCD码的调整
    • ZF(零标志位): 运算结果为0时, ZF为1, 否则为0
    • SF(符号标志位): 当运算结果的最高位为1时, SF为1, 否则为0. 最高位表示符号数的正和负
    • TF(跟踪标志位): 用于调试程序时进入单步方式工作. TF=1时, 每条指令执行完后产生一个内部中断, 让用户检查指令运行后寄存器, 存储器和各标志位的内容. TF=0时, CPU工作正常, 不产生内部中断
    • IF(中断允许标志位): IF=1同时中断屏蔽寄存器的相应位为0, 允许系统响应可屏蔽中断, 反之, 不接收外部发出的中断请求
    • DF(方向位标志位): 用于控制串操作时地址指针位移方向. 当DF=1时, 指针向高地址方向移动
    • OF(溢出标志位): 算术运算时结果超出系统所能表示的数的范围. 溢出时, OF=1

跳转指令

C语言程序的堆栈图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include<stdio.h>
int function(int x ,int y,int z) 
{
    return x+y-z;
}

int main()
{
    function(2,3,4);  //仅仅调用此函数而已
}

堆栈图如下(过程)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

不同的编译器生成的堆栈图可能不同,要视情况而定

intel汇编示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;; 1. 函数调用过程
; 提升堆栈
PUSH EBP             ; 将旧EBP入栈,保存旧栈桢基址
MOV  EBP, ESP        ; 将旧栈顶指针ESP设为新的栈桢基址,即新的栈桢紧接父调用的栈桢
SUB  ESP, 40         ; ESP减少移动40个字节,即开辟40字节大小的函数栈桢空间
; 保留调用现场
PUSH EBX             ; 将EBX入栈
PUSH ESI
PUSH EDI
; 向分配的空间填充数据
LEA  EDI, DWORD PTR SS:[EBP-40]    ;
MOV  ECX, 10
MOV  EAX, CCCCCCCC
REP  STOS DWORD PTR ES:[EDI]
; 函数功能
MOV  EAX, DWORD PTR SS:[EBP+8]
ADD  EAX, DWORD PTR SS:[EBP+C]
; 恢复现场
POP EDI
POP ESI
POP EBX
; 恢复栈桢
MOV ESP, EBP
POP EBP
RETN            ; 返回
1
2
3
4
5
6
7
8
; 函数入口
PUSH 2         ; 将函数参数压入栈中
PUSH 1
CALL 0040100A  ; 调用函数

; 函数出口
MOV EAX, DWORD PTR SS:[EBP+8]
ADD EAX, DWORD PTR SS:[EBP+C]

参考

  1. GCC内联汇编基础 - 简书

  2. http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html

  3. https://zhuanlan.zhihu.com/p/27339191

  4. https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-02-arch.html

  5. https://langzi989.github.io/2017/10/06/%E6%B1%87%E7%BC%96%E5%9F%BA%E7%A1%80%E4%B9%8B%E5%AF%84%E5%AD%98%E5%99%A8%E4%B8%8E%E7%AE%80%E5%8D%95%E6%8C%87%E4%BB%A4%E5%88%86%E6%9E%90/

  6. https://zhuanlan.zhihu.com/p/54821702

  7. 如何看懂 x86 汇编(AT&T 版)

  8. i386汇编指令初学

  9. AT&T汇编 | Today is the present

  10. http://ref.x86asm.net/geek32-abc.html

  11. 汇编语言程序设计第二篇——80X86汇编语言伪指令

  12. AT&T 汇编伪指令说明 - 正弦公仔的知识库

updatedupdated2024-05-102024-05-10