进制
Decimal Binary
25 ÷ 2= 12 r 1 11001 ÷ 10= 1100 r 1
12 ÷ 2 = 6 r 0 1100 ÷ 10 = 110 r 0
6 ÷ 2 = 3 r 0 110 ÷ 10 = 11 r 0
3 ÷ 2 = 1 r 1 11 ÷ 10 = 1 r 1
1 ÷ 2 = 0 r 1 1 ÷ 10 = 0 r 1
2510(十进制) = 11001(二进制)
This method finds the rightmost digit first, this digit is called the least significant bit (lsb).
The leftmost digit is called the most significant bit (msb).
CPU寄存器
8086 16-bit CPU
16个16-bit通用寄存器
- AX, BX, CX, DX 能被分成两个8bit部分使用, 例如
AX=AH + AL
, 用来数据移动和计算指令 - SI, DI 作为指针(Pointers), 无法分割
- BP, SP 栈指针
- CS, DS, SS, ES 分段(segment)寄存器, 分别是:Code Segment, Data Segment, Stack Segment 和 Extra Segment. 其中ES作为temporary segment register
- IP 指令寄存器, CS是指向代码。当指令执行,IP指向内存中下一个要执行的指令
- FLAGS 按照bit位存储的,一些特殊的开关
80386 32-bit CPU
- 新增了32-bit版本EAX,为了保持兼容AX仍然是16-bit版本,AX指向EAX的低16位
- segemnt寄存器仍然16-bit, 新增FS和GS, 和ES一样,是额外的两个temporary segment register
内存模式
Real Mode
真实模式访问限制1MB, 地址范围=[0x00000, 0xFFFFF], 若要表示这些地址需要20-bit寄存器。为了解决这个问题,intel使用2个16-bit值表示内存地址,selector + offset, 公式如下:
16 ∗ selector + offset
其中selector必须在segment寄存器中。 计算的时候,selector左移一位 + offset就可以了
问题1:1个selector只能限制64K大小内存, 当程序大于64K时,需要切分成小于64K的segments,当执行一个segment结尾时,CS需要改变。其他的segment寄存器,也会遇到类似的问题
问题2: 同一个物理地址04808
可以写成047C:0048, 047D:0038, 047E:0028, 047B:0058
即使这样,由于BIOS启动时没有操作系统,所以只能只用这种模式
16-bit Protected Mode
在80286的保护模式中,使用virtual memeory技术, 该技术只保存使用中的数据和代码在内存中,其他未使用的放在磁盘中。操作系统作为搬运工
selector在概念上指向了虚拟内存的描述表(descriptor table),不再是真实的内存段落(paragraph)
此时,每个段寄存器指向描述表的index,操作系统知道该index的所有信息,包括是否当前在内存中,如果在物理地址在哪里,访问权限(只读等)
此时,问题1仍然存在,依然是64K大小限制
32-bit Protected Mode
在80386中,offset使用的是32位,这样segment就可以访问4G的大小。同时,segment能够被切分成更小的pages,每一个page=4K大小,
在80286中,整个segment要么在内存中,要么完全不在内存中。由于segment在32位中太大。virtual memeory不再使用segment转而使用Page
汇编
每种CPU都有自己的机器语言,指令是内存中按照bytes储存的数,指令对应的数叫operation code
简称opcode
03 C3
这个指令,将EAX和EBX加起来,并且将结果存储在EAX中。
汇编语言就是相对机器语言的高级语言,汇编使用的是text, 每一个汇编指令对应一条机器指令。
add eax, ebx
assembler就是将汇编语言翻译成机器码的解析程序
相比高级语言,汇编语言的可移植性很差,因为每个CPU对应的opcode
不同,也就有不同的汇编语言
语法
mnemonic operand(s)
其中operands
一般1~3个主要以下4种:
- register: 寄存器
- memory: 常量或变量,内存终是一个segment的偏移量(offset)
- immediate: 指令中的常量
- implied: 隐含变量,比如对register或memory做increment操作
基本指令
MOV
mov dest src
: 将src中的值拷贝到dest中, 储存src, dest的容器必须同样大小
mov eax, 3 ; eax = 3
mov bx, ax ; bx = ax
ADD
add eax, 4 ; eax = eax + 4
add al, ah ; al = al + ah
SUB
sub bx, 10 ; bx = bx - 10
subb ebx, edi; ebx = ebx - edi
INC
inc ecx ; ecx++
DEC
dec dl ; dl--
Directive
directive是编译器的发明,并不直接翻译到机器码中
- 定义常量
- 定义内存存储数据
- 将内存组成segments
- 条件包含代码
- 引用其他文件
equ
symbol equ value
symbol不能再次定义
define
%define SIZE 100
move eax, SIZE
data
Unit | Letter |
---|---|
byte | B |
word | W |
double word | D |
quad word | Q |
ten bytes | T |
L1 db 0 ; byte L1 = 0
L2 dw 1000 ; Word L2 = 1000
L3 db 110101b ; byte L3 = 0b110101,十进制=53
L4 db 12h ; byte L4 = 0x12, 十进制=18
L5 db 17o ; byte L5 = 0o17, 十进制=15
L6 dd 1A92h ; double word L6 = hex(1A92)
L7 resb 1 ; byte L7
L8 db "A" ; byte L8 = ASCII(A)
L9 db 0,1,2,3 ; byte L9 = [0, 1, 2, 3]
L10 db "w", "o", "r", "d", 0 ; string L10 = "word"
L11 db 'word', 0 : string L11 = "word"
L12 times 100 db 0 ; byte L12[100] = {0}
L13 resw 100 ; word L13[100]
Label和Pointer类似,直接使用就是地址,[Label]就是地址里面的值
mov al, [L1] ; copy byte at L1 into AL
mov eax, L1 ; EAX = address of byte at L1
mov [L1], ah ; copy AH into byte at L1
mov eax, [L6] ; copy double word at L6 into EAX
add eax, [L6] ; EAX = EAX + doubleword at L6
add [L6], eax ; double word at L6 += EAX
mov al, [L6] ; copy first byte of double word at L6 into AL
类型
Unit | Letter |
---|---|
byte | BYTE |
word | WORD |
double word | DWORD |
quad word | QWORD |
ten bytes | TWORD |
mov [L6], 1 ; store a 1 at L6, ❌operation size not specified error
mov dword [L6], 1
打印与调试
%include "asm_io.asm"
Function | Register |
---|---|
print_int | EAX |
print_char | AL |
print_string | EAX |
print_nl | - |
read_int | EAX |
reac_char | EAX |
%include "asm_io.inc"
Macro | params |
---|---|
dump_regs | label(integer) |
dump_mem | label(interger), address, number(16-byte paragraphs after address) |
dump_stack | label(interger), number(double words below EBP), number(double words above EBP) |
dump_math | label(interger) |
第一个程序
程序调用
使用C有几个好处:
- C语言会在保护模式中,所有segment寄存器也会被设置好.
- C语言中的库函数,也能在汇编语言中使用
#if defined(__GNUC__)
# define PRE_CDECL
# define POST_CDECL __attribute__((cdecl))
#else
# define PRE_CDECL __cdecl
# define POST_CDECL
#endif
int PRE_CDECL asm_main( void ) POST_CDECL;
int main()
{
int ret_status;
ret_status = asm_main();
return ret_status;
}
汇编函数
功能: 提示输入两个数,然后输出量数之和并打印出来
所有初始化的变量都在.data segment中, 由于使用printf, 字符串结尾要加个0 为初始化的变量在.bss segment中,是一个stack segment
segment .data
;
; These labels refer to strings used for output
;
prompt1 db "Enter a number: ", 0 ; don't forget nul terminator
prompt2 db "Enter another number: ", 0
outmsg1 db "You entered ", 0
outmsg2 db " and ", 0
outmsg3 db ", the sum of these is ", 0
segment .bss
;
; These labels refer to double words used to store the inputs
;
input1 resd 1
input2 resd 1
代码放在.text segment中,由于C调用标准差异,macOS Windows asm_main 前有下划线. Linux则没有
默认标识符在module
中,使用global
可以被其他module
调用
;
; code is put in the .text segment
;
segment .text
global asm_main
asm_main:
enter 0,0 ; setup routine
pusha
mov eax, prompt1 ; print out prompt
call print_string
call read_int ; read integer
mov [input1], eax ; store into input1
mov eax, prompt2 ; print out prompt
call print_string
call read_int ; read integer
mov [input2], eax ; store into input2
mov eax, [input1] ; eax = dword at input1
add eax, [input2] ; eax += dword at input2
mov ebx, eax ; ebx = eax
dump_regs 1 ; dump out register values
dump_mem 2, outmsg1, 1 ; dump out memory
;
; next print out result message as series of steps
;
mov eax, outmsg1
call print_string ; print out first message
mov eax, [input1]
call print_int ; print out input1
mov eax, outmsg2
call print_string ; print out second message
mov eax, [input2]
call print_int ; print out input2
mov eax, outmsg3
call print_string ; print out third message
mov eax, ebx
call print_int ; print out sum (ebx)
call print_nl ; print new-line
popa
mov eax, 0 ; return back to C
leave
ret
执行结果
root@vultr:~/pcasm/examples/linux# ./first
Enter a number: 1
Enter another number: 23
Register Dump # 1
EAX = 00000018 EBX = 00000018 ECX = 2CDAC02A EDX = FF9F77F0
ESI = FF9F7884 EDI = F7FF7B80 EBP = FF9F77A8 ESP = FF9F7788
EIP = 5658D237 FLAGS = 0206 PF
Memory Dump # 2 Address = 56590030
56590030 59 6F 75 20 65 6E 74 65 72 65 64 20 00 20 61 6E "You entered ? an"
56590040 64 20 00 2C 20 74 68 65 20 73 75 6D 20 6F 66 20 "d ?, the sum of "
You entered 1 and 23, the sum of these is 24
汇编模版
;
; file: skel.asm
; This file is a skeleton that can be used to start assembly programs.
%include "asm_io.inc"
segment .data
;
; initialized data is put in the data segment here
;
segment .bss
;
; uninitialized data is put in the bss segment
;
segment .text
global asm_main
asm_main:
enter 0,0 ; setup routine
pusha
;
; code is put in the text segment. Do not modify the code before
; or after this comment.
;
popa
mov eax, 0 ; return back to C
leave
ret
–END–