汇编入门

文章目录

  • 1. 笔记
    • 1.1 常用寄存器
    • 1.2 常用指令
    • 1.3 其他
  • 2. 工具使用
    • 2.1 debug跟踪调试工具
      • 2.1.1 指令
      • 2.1.2 使用
    • 2.2 masm编译工具
    • 2.3 link链接工具
  • 3. 汇编程序结构
    • 3.1 汇编程序分析
  • 4. 包含多个段的程序
  • 5. 查看C程序汇编
    • 5.1 在linux上使用gcc生成汇编文件
    • 5.2 在windows上使用cl生成汇编文件
  • 6. 其他
    • 6.1 通过缓冲区溢出实现函数调用
  • 7. 如何看懂汇编程序


1. 笔记

1.1 常用寄存器

8086有14个寄存器,物理地址 = 段地址*16+偏移地址,

  • 注意:一般不支持将输入直接送入段寄存器,例如 mov ds:1000H 是非法的
  • 入栈时,栈顶从高地址向低地址方向增长,出栈则相反;使用时应注意栈顶超界
  • 栈空时SP = 0 ,栈满时也是 SP = 0 ,超界时,栈顶将环绕
寄存器 名称 说明 示例
AX 通用寄存器 16位寄存器,分高8位AH,低8位AL
BX 通用寄存器 mov ax,[bx] : 从 ds:bx 位置开始读两个字节写到ax中;mov al,[bx] : 从 ds:bx 位置开始读一个字节写到al中
CX 通用寄存器
DX 通用寄存器
CS 段寄存器,CS:IP指向当前要读取指令的地址,读取一条指令后IP值自动增加 段地址*16+偏移地址 构成20位物理地址
DS 段寄存器,存放要访问的数据段地址 mov al,[0] : 将 ds:0 中的低8位读入al,[0]是ds的偏移地址,写入数据寄存器:mov [0],al 将ax寄存器的低8位写入ds:0;[bx] 也是偏移地址,具体的偏移值存放在bx中
SS 段寄存器,栈段,SS:SP指向栈顶
ES 段寄存器
IP 代码偏移地址
SP 栈偏移地址
SI 与BX功能相近的寄存器,不能分为两个8位寄存器 不能分成两个8位寄存器 mov ax,[si] : 将起始位 ds:si 的后两个字节的值写到ax中
DI 与SI相似 不能分成两个8位寄存器 mov ax,[di] : 将起始位 ds:di 的后两个字节的值写到ax中
BP
PSW

1.2 常用指令

  • 在汇编中包含两种指令:汇编指令、伪指令
指令 名称 说明 相关指令 指令性质 指令执行者 对应机器码
mov 寄存器写值,mov ax,10H : ax = 10H
add 寄存器累加,add ax, 10H : ax += 10H
sub 寄存器减法,sub ax, 10H : ax -= 10H
pop 出栈 pop ax : 将栈顶数据读入ax,修改SP = SP + 2
push 入栈 push ax : SP = SP - 2, 将ax从栈顶写入
int
inc 自增1 inc bx : bx中的值自增1
loop loop s : 执行过程:cx = cx - 1 , 判断cx值,大于 0 跳转至 s 位置 (修改cs:ip),否则 往下执行;通常用于循环,cx存放循环次数
and 与运算,and ax,0 : ax&=0
or 或运算,or ax,1 : ax|=1

1.3 其他

  • 字节byte 8bit,字word 16bit = 2byte

2. 工具使用

我使用的是AsmTools,这个工具下的debug、masm、link都是在16位windows dos下运行的,在当前的32位或64位系统中不能运行,需安装DOSBox,在DOSBox中运行汇编的工具。

2.1 debug跟踪调试工具

2.1.1 指令

指令 说明 示例
r 查看当前寄存器
a 写入一条汇编指令
u 查看所有待运行指令,将机器指令翻译成汇编指令
t 执行一条指令
d 查看内存单元 -d 076A:0000 : 查看076A:0000开始的内存数据
e 写入数据 -e ds:0 11 22 33 44 55 66 : 写入数据到从ds:0开始的内存
p
q 退出debug

2.1.2 使用

> debug 1.exe

2.2 masm编译工具

masm将.asm源文件编译生成.obj目标文件
例如:编译1.asm

> masm.exe 1;

2.3 link链接工具

link将.obj文件链接生成.exe目标文件
例如:链接1.obj

> link.exe 1;

3. 汇编程序结构

assume cs:sg1
sg1 segment
		mov ax,2000H
		mov ss,ax
		mov sp,0
		add sp,10
		pop ax
		pop bx
		push ax
		push bx
		pop ax
		pop bx
		
		mov ax,4c00H
		int 21H
sg1 ends
end

解释:

  • assume cs:sg1 :假设sg1段与cs段寄存器关联,是一个代码段
  • sg1 segment … sg1 ends :定义名为sg1的段,ends 段结束
  • end : 汇编程序的结束标记,碰到end就结束对源程序的编译,程序结束
  • 程序返回,以下两行代码在程序最后执行程序返回:
    mov ax,4c00H
    int 21H

3.1 汇编程序分析

以上面的代码为例进行分析,将上面的代码保存为文件1.asm,存放在AsmTools文件夹下。

  • 编译 masm 1;
  • 链接 link 1;
  • debug分析 : debug 1.exe
    汇编入门_第1张图片
  1. 用r命令查看各个寄存器的设置情况
    cx 中存放的是程序的长度
    汇编入门_第2张图片
  • DS = 075A ,这是程序内存的起始位置。程序的内存分为PSP区和程序区,PSP区占前256个字节,之后是程序区。DS是内存的起始位置,也是PSP区的起始位置;程序区位置 = DS + 256 = DS +10H
  • CS = 076A ,这是程序区起始位置,CS = DS + 10H , CS:IP指向程序的第一条指令
  • CX = 0016H 为 22 ,如何知道程序的长度呢?继续接着看
  • SS:SP = 0769:0000 ,初始栈顶位置
  1. 用u查看程序指令
    汇编入门_第3张图片
  • 中间红框中的即是机器代码,到 INT 21程序返回,计算代码长度 : 3 + 2 + 3 + 3 + 1 + 1 + 1 + 1 + 1 + 1 + 3 + 2 = 22 ,刚好是CX的大小。从地址也能看出来 076A:0000 到 076A:0016。
  • 起始指令地址为 076A:0000,即是 CS:IP
  1. 用t单步执行指令,并查看各个寄存器的变化
    汇编入门_第4张图片
    ax = 2000H
    IP = 0003H

执行了一句指令,为ax赋值了,代码偏移地址IP自动加3

  1. 剩下不说了

4. 包含多个段的程序

assume cs:seg_test
a segment
	db 1,2,3,4,5,6,7,8
a ends
b segment
	db 1,2,3,4,5,6,7,8
b ends
c segment
	db 0,0,0,0,0,0,0,0
c ends

seg_test segment
start :	mov bx,a
	mov ds,bx
	mov bx,0H
	mov si,10H
	mov di,20H
	mov cx,8H
	and ax,0H
s : mov al,[bx]
	add al,[bx+si]
	mov [bx+di],al
	inc bx
	loop s
	
	mov ax,4c00H
	int 21h
seg_test ends
end start
  • 程序中定义了4个段,分别是a、b、c、seg_test,前3个为数据段,seg_test为代码段。
  • 默认的数据段寄存器DS的值为程序内存起始位置,但是a、b、c并不在起始代码段,所以首先需要为DS赋值为a的数据段位置。其实还可以在assume中定义:
assume cs:seg_test,ds:a
  • 根据DS为a的数据段值,a 的数据偏移地址为0,b的数据偏移地址为16(10H),c的数据偏移地址为32(20H);
  • 设置cx的值为loop循环次数,每循环一次cx减1;因为有8个数据,需要循环8次
  • add ax,0H 是为了将ax赋值为0,其实可以使用 mov ax,0H
  • 只有bx可以根据al或ax分别使用8位或16位寄存器,si和di都只能一次使用16位寄存器
  • inc bx : 使 bx自增1

查看程序中的内存地址及数据:
汇编入门_第5张图片
已知默认的DS为0x075A,则程序段的位置为0x075A+256 = 0x76A,可以看到程序段的前3行为a、b、c的值。

5. 查看C程序汇编

写一个C程序,代码如下:

// code.c

int add(int i, int j)
{
    return i + j;
}

int main()
{
    int a = 1;
    int b = 7;
    a = add(a, b);
    return a;
}

很简单的一段代码,先直接生成汇编看看:

5.1 在linux上使用gcc生成汇编文件

> gcc -O0 -S code.c

-O是编译器优化参数,-O0不优化。执行上面的代码后,会生成一个code.s文件:

	.file	"code.c"
	.text
	.globl	add
	.type	add, @function
add:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	add, .-add
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$1, -8(%rbp)
	movl	$7, -4(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	movl	%edx, %esi
	movl	%eax, %edi
	call	add
	movl	%eax, -8(%rbp)
	movl	-8(%rbp), %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0"
	.section	.note.GNU-stack,"",@progbits

我们继续,生成可执行文件,然后再查看可执行文件的汇编:

> gcc -O0 -c code.c

将会生成一个code.o文件,使用命令查看汇编:

> objdump -d code.o

汇编如下:

code.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <add>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   89 75 f8                mov    %esi,-0x8(%rbp)
   a:   8b 55 fc                mov    -0x4(%rbp),%edx
   d:   8b 45 f8                mov    -0x8(%rbp),%eax
  10:   01 d0                   add    %edx,%eax
  12:   5d                      pop    %rbp
  13:   c3                      retq   

0000000000000014 <main>:
  14:   55                      push   %rbp
  15:   48 89 e5                mov    %rsp,%rbp
  18:   48 83 ec 10             sub    $0x10,%rsp
  1c:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  23:   c7 45 fc 07 00 00 00    movl   $0x7,-0x4(%rbp)
  2a:   8b 55 fc                mov    -0x4(%rbp),%edx
  2d:   8b 45 f8                mov    -0x8(%rbp),%eax
  30:   89 d6                   mov    %edx,%esi
  32:   89 c7                   mov    %eax,%edi
  34:   e8 00 00 00 00          callq  39 <main+0x25>
  39:   89 45 f8                mov    %eax,-0x8(%rbp)
  3c:   8b 45 f8                mov    -0x8(%rbp),%eax
  3f:   c9                      leaveq 
  40:   c3                      retq  

5.2 在windows上使用cl生成汇编文件

> cl /FA code.c

生成的汇编code.asm:

; Listing generated by Microsoft (R) Optimizing Compiler Version 18.00.40629.0 

	TITLE	D:\vscode\cpp\socket_model\linux\code.c
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC	_add
PUBLIC	_main
; Function compile flags: /Odtp
_TEXT	SEGMENT
_b$ = -8						; size = 4
_a$ = -4						; size = 4
_main	PROC
; File d:\vscode\cpp\socket_model\linux\code.c
; Line 8
	push	ebp
	mov	ebp, esp
	sub	esp, 8
; Line 9
	mov	DWORD PTR _a$[ebp], 1
; Line 10
	mov	DWORD PTR _b$[ebp], 7
; Line 11
	mov	eax, DWORD PTR _b$[ebp]
	push	eax
	mov	ecx, DWORD PTR _a$[ebp]
	push	ecx
	call	_add
	add	esp, 8
	mov	DWORD PTR _a$[ebp], eax
; Line 12
	mov	eax, DWORD PTR _a$[ebp]
; Line 13
	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
; Function compile flags: /Odtp
_TEXT	SEGMENT
_i$ = 8							; size = 4
_j$ = 12						; size = 4
_add	PROC
; File d:\vscode\cpp\socket_model\linux\code.c
; Line 3
	push	ebp
	mov	ebp, esp
; Line 4
	mov	eax, DWORD PTR _i$[ebp]
	add	eax, DWORD PTR _j$[ebp]
; Line 5
	pop	ebp
	ret	0
_add	ENDP
_TEXT	ENDS
END

6. 其他

6.1 通过缓冲区溢出实现函数调用

问题:不实用汇编或函数调用,实现函数调用。

函数调用在汇编中是使用jmp指令实现的,我们只需要想办法将jmp指令插入到程序要执行的地方即可。

通过strcpy或memcpy溢出缓冲区,将jmp指令写入正确位置即可实现。

参考:缓冲区溢出之Strcpy和Memcpy

7. 如何看懂汇编程序

同一段c语言代码,由于编译器、平台、指令集等的不同,编译后的汇编是有区别的。如何去看懂一段汇编程序,需要先了解以下内容:

    1. 指令集中的寄存器,通用寄存器、专用寄存器等
    1. 函数传参方式,例如 ARM-V7-A 中,fastcall函数调用前4个参数由R0-R3四个寄存器保存,之后的参数从右往左入栈,由栈传递
    1. 熟悉常用指令

你可能感兴趣的:(汇编)