汇编编译器assembler编译目标代码二进制文件(nasm -f elf -g -F stabs *.asm),连接器linker(ld -o bin_file *.o)除了把目标代码组合成一个单个的块,还要确保模块以外的函数调用能够指向正确的内存引用(连接器必须建立一个索引,也就是符号表,里面存放的是它连接的每一个目标模块中的每一个已命名项,其中存放着一些关于哪个名字或叫符号指向模块内部哪个位置的信息)。
立即数,内置在机器指令内部,它不是存放在寄存器中,也不是存放在位于某个指令之外的内存中。
1, 寄存器打中括号代表寄存器的内存地址中的内存数据。例:
mov eax,[ebx+16]。
2, 在汇编中,变量名代表的地址,不是数据。例:
Msg: "Hello World"
mov ecx,Msg #复制Msg地址到ecx寄存器,而不是数据
mov edx,[Msg] #复制数据,而不是地址
MsgLen: equ $-Msg #$代表末尾,长度=末尾位置减开始位置
8080拥有16根地址线(寄存器是16位,2的16次方=64K), 8086拥有20根地址线(2的20次方=1M=16×64K,但它的寄存器仍然是16位),为了兼容8080,Intel仍然让程序只使用1M里的64K字节段, 这叫内存的实模式。8088, 8086, 80286的寄存器都仍然是16位,它是使用两个16位寄存器来寻址20位,一个16位寄存器用来存放段地址,一个16位寄存器用来存放段偏移。段可以是在1M内任意以16为倍数的段地址开始,接着最大可达64K的界限。0001:0019=0001H段开始的第0019H个字节处,我们知道,一个段可能开始于实内存中所有1M字节里的任意一个16字节处,所以0001:0019等于0000:0029, 也等于0002:0009。
这这种实模式下,一个段只有64K大小,一个程序不够用,所以一般通过划分CS, DS, SS, ES等段的方式来扩大大小。
8088, 8086, 80286有4个用于存放段地址的段寄器,80386增加了2个。
CS(Code Segment):代码段,用于存放机器指令。
DS(Data Segment): 数据段,用于存放变量和其他数据。可能会有很多数据段,但CPU一次只能使用一个。
SS(Stack Segment): 堆栈段, 一个单独的程序只能有一个堆栈
ES(Extra Segment): 附加段寄存器(结合DS使用意味着可以同时访问两个数据段),用于指定内存中某一位置的备用段。
FS和GS:是ES的克隆,命令按ES往后的F,G排列,只存在于80386(寄存位为32位)及后来的x86 CPU中。
汇编中的Push-y指令
push ax
push [ax]
pushf #将16位寄存器标志值压入堆栈
pushfd #将EFLAGS寄存器的全部32位值压入堆栈
pusha #将8个通用寄存器压入堆栈
在32位世界中,通用寄存器分为三个一般类:16位通用寄存器、32位扩展寄存器和8位的半寄存器(实际上,16位和8位寄存器只是32位寄存器内部的一块区域而已)。
有8个16位通用寄存器:AX,BX,CX,DX,BP,SI,DI和SP用于存放16位的或更少位的值。在实模式下它可以是结合段寄存器这样指定一个完整的20位地址使用:
SS:SP
SS:BP
ES:DI
DS:SI
CS:BX
后来x86体系结构将其扩展为32位时,在原有的名称前加了前缀E(EAX, EBX, ECX, EDX, EBP, ESI, EDI和ESP, 低16位仍然可以用老式不加前缀E的叫法,但不幸的是,寄存器的高16位根本没有自己的名字)。
64位系统,在原有的名称前加了前缀R(RAX, RBX, RCX, RBP, RSI, RDI, RSP)。另外添加了8个全新的64位寄存器(R8到R15)。x86-64也增加了8个128位的SSE寄存器到IA-32的8个同类寄存器中,一共有16个SSE寄存器。
上面的4个通用寄存器(EAX, EBX, ECX, EDX)的低16位(AX, BX, CX, DX)又被划分为8位的半寄存器,高位后加H,低位后加L,如:在BX中有BH和BL两个半寄存器,依此类推。
16位中叫IP, 32位中叫EIP,存放当前代码段(一个程序可能包含多个代码段)中下一段即将执行的机器指令的偏移地址。CS和IP一起,保存下一条即将执行的指令的完整地址(在实模式下,CS与IP一起工作带来20位的地址,CS由操作系统设置,IP可以跟踪64K内存段;在保护模式下,32位系统的IP可以跟踪4G内存段)。IP寄存器是唯一不能读入与写出的寄存器,它只能使用跳转指令移动。
16位中叫FLAGS,32位中叫EFLAGS,每一位都有特殊的含义,也有单独的名字,如CF, DF, OF等。
实模式因为寻址只有64K不够用,所以有段的概念。但在32位系统中,可以寻址4G的内存空间,就不需要分段了。但传统的段寄存器依然存在,只是你不能读或写它们,完全交由操作系统来做。操作系统有一个虚拟地址空间可以非常大,但32位系统的物理寻址空间最大只能是4G,操作系统从虚拟地址空间中找一块4G的内存空间作为内存寻址,那么段寄存器就被操作系统设置为这个虚拟空间下的基址。所以说,Linux没有实模式“遗留问题”需要处理,自从1992年第一次出现以来,它就一直运行在保护模式下。只有BIOS需要运行在实模式下(Linux提供软中断80H去访问BIOS)。如果多个程序同时访问一块内存,可能造成混乱,像DOS是单任务程序无此问题,多个程序通过驱动访问(驱动能隔离程序到某块内存的访问)也无此问题,保护模式可以让多个程序在同一时刻运行。
x86-64定义了三种一般模式:实模式,保护模式和长模式。实模式是兼容模式兼容16位系统,保护模式也是一种兼容模式兼容32位系统,长模式。由于64位地址过于巨大(10亿GB),今天的x86-64 CPU一般只支持48位虚拟内存和40位物理内存地址(1000GB)。
所有中断实际上是由位图描述的,每个位图类似于寄存器指向一个内存地址偏移向量(即中断服务程序地址,一个向量由4个字节组成,共有256个向量,由Linux向中断向量表中写入正确的这些地址),也叫中断向量表。x86有一条软中断指令80H专门用于操作系统找中断程序(保护模式下是禁止的):
move eax, 4 #要调用的中断号
move ebx,1 #中断的参数传入到了寄存器中,而不是堆栈中
int 80H #进行软中断调用,然后Linux会通过eax里的中断号在中断向量中找到中断服务程序地址
next_order #下一条指令,在执行上面行的中断之前,会先将这下一条指令压入堆栈,这样中断返回之后就知道继续从这里执行了。