|

楼主 |
发表于 2003-2-26 03:48:26
|
显示全部楼层
在linux中,当通过一条INT指令进入一个中断服务器程序时,在指令中给出一个中断向量。CPU先根据该中断向量找到一扇门。然后,就要将这个门的DPL与CPU的CPL相比,CPL必须小于或等于DPL,也就是优先级别不低于DPL,才能穿过这扇门。不过,如果中断是由外部产生或因CPU异常而产生的,那就免去了这一层检查。穿过门之后,还有进一步将目标代码段描述项中的DPL和CPL比较,目标段的DPL必须小于或等于CPL。也就是说,通过中断门是只允许保持或提升CPU运行级别,而不允许降低其运行级别。这两个环节中的任何一个失败都会产生一次一般保护异常(general protection exception)。
进入中断服务程序时,cpu要将当前EFLAGS寄存器的内容以及返回地址压入堆栈,返回地址是由段寄存器CS的内容和合取指令指针EIP的内容共同组成的。如果中断是由异常引起的,则还有将一个表示异常原因的出错代码也压入堆栈。进一步,如果中断服务程序的运行级别,也就是目标代码段的DPL,与发生中断时的DPL不同,那就要引起更换堆栈。
3 Linux 汇编代码
在linux内核的源代码中,以汇编语言编写的程序或程序段,有两种不同的形式。
第一种事完全的汇编代码,这样的代码采用.s作为文件的后缀。事实上,尽管是完全的汇编代码,现代的汇编工具也吸收了C语言的长处,也在汇编之前加上了一趟预处理,而预处理之前的文件则以.s为后缀。此类(.s)文件也和C程序一样,可以使用#include、#ifdef等等成分,而数据结构也一样可以在.h的文件中加以定义。
第二种是嵌在C程序中的汇编语言片断。虽然在ANSI的C语言标准中并没有关于汇编片段的规定,事实上各种实际使用的C编译中都作了这方面的扩充,而GNU的C编译gcc也在这方面作了很强的扩充。
在DOS/windows领域中,386汇编语言都采用Intel定义的语句格式。可是,在Unix领域中,采用的却是由AT&T定义的格式。
AT&T的汇编与Intel的汇编主要有以下的区别:
1. 在Intel格式中大多使用大写字母,而在AT&T格式中都使用小写字母。
2. 在AT&T格式中,寄存器名要加上“%”作为前缀,而在Intel格式中不带前缀。
3. 在AT&T的386汇编语言中,指令的源操作数的顺序与在Intel的386汇编语言中正好相反。
4. 在AT&T格式中,访问指令的操作数的宽度有操作码名称的最后一个字母(操作码的后缀决定)。用作操作码后缀的字母有b(8位)。 w(16位)和1(32位)。而在Intel格式中,则是在表示内存单元的操作数前面加上“BYTE PTR”“WORD PTR”,“DWORD PTR”来表示。
5. 在AT$T格式中,直接操作数要加上“$”作为前缀,而在Intel格式中则不带前缀。
6. 在AT$T格式中,绝对转移和调用指令jump/call的操作数要加上“*”作为前缀,而在intel格式则不带。
7. 远程的转移指令和子程序调用指令的操作码名称,在AT$T格式中为“ljump”和“lcall”,而在intel格式中,则为“JMP FAR”和“CALL FAR”当转移和调用的目标为直接操作数时,两种不同的表示如下:
* CALL FAR SECTION:OFFSET(Intel 格式)
* JMP FAR SECTION:OFFSET(Intel 格式)
* lcall $section,$offset (AT$T格式)
* lcall $secton,$offset (AT$T格式). 与之相应的远程返回指令,则为:
* RET FAR STACK_ADJUST (Intel 格式)
* Lret $stack_adjust (AT$T 格式)
8. 间接寻址的一般格式,两者的区别如下:
* SECTION :[BASE+INDEX*SCALE+DISP](Intel 格式)
* Section: disp(base,index,scale)(AT$T 格式)
* 当需要在C语言的程序中嵌入一段汇编语言程序时,可以使用gcc提供的“asm”语句功能。
一般而言,往C代码中插入汇编语言的代码片要比“纯粹”的汇编语言代码复杂的多,因为这里有个怎样分配使用寄存器,怎样与C代码中的变量结合的问题。为了这个目的,必须对所用的汇编语言作更多的扩充,增加对汇编工具的指导作用。其结果是其语法实际上变成了既不同于汇编语言,也不同于C语言的某种中间语言。
插入C代码的一个汇编语言代码片段可以分为四个部分,以“:”号加以分隔,其一般形式为:
指令部: 输出部:输入部:损坏部
第一部分就是汇编代码本身,其格式和在汇编语言中使用基本相同。这一部分称为“指令部”,是必须有的,而其他部分可视具体情况而省略。
当将汇编语言代码片段嵌入到C代码中时,操作数与C代码中的变量如何结合显然是个问题。Gcc采用的策略是:程序员提供具体的指令,而对寄存器的使用则一般只提供一个“样板”和一些约束条件,而把到底如何与变量结合的问题留给了gcc和gas处理。
在指令部中,数字加上前缀%,如%0、%1等等,表示需要使用的寄存器的样板操作数。可以使用的此类操作数的总数取决于具体CPU中通用寄存器的数量。这样,指令部中用到了几个不同的这种操作数,就说明有几个变量需要与寄存器结合,由gcc和gas在编译和汇编时根据后面的约束条件自行变通处理。由于这些样板操作数也使用“%”前缀,在涉及到具体的寄存器时就要在寄存器名前面加上两个“%”符,以免混淆。
紧接在指令部后面的是“输出部”,用以规定对输出变量如何结合的约束条件。每个这样的条件称为一个“约束”。必要是输出部可以有多个约束,互相以逗号分隔。每个输出约束以“=”号开始,然后是一个字母表示对操作数类型的说明,然后是关于变量结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行嵌入的汇编代码后均不保留执行之前的内容,这就给gcc提供了调度这些寄存器的依据。
输出部后面是“输入部”。输入约束的格式和输出约束相似,但不带“=”号。如果一个输入约束要求使用寄存器,则在预处理时gcc会为之分配一个寄存器,并自动插入必要的指将操作数即变量的值装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行嵌入汇编代码后也不保留执行之前的内容。
在有些操作中,除用于输入数据操作和输出数的寄存器以外,还要将若干个寄存器用于计算或操作的中间结果。这样,这些寄存器原有的内容就损坏了,所以要在损坏部队操作的副作用加以说明,让gcc采取相应的措施。
操作数的编号从输出部的第一个约束(序号为0)开始,顺序数下来,每个约束记数一次。在指令部中引用这些操作或分配用于这些操作数的寄存器时,就在序号前面加上一个“%”号。在指令部中引用一个操作数时总是把它当作一个32位的“长字”,但是对其实施的操作,则根据需要也可以是字节操作或是字操作。对操作数进行的字节操作时也允许明确指出是对哪一个字节的操作,此时在%与序号之间插入一个“b”表示最低字节,插入一个“h”表示次低字节。
表示约束调节的字母有很多:
“m”“v”“o” 表示内存单元
“r” 表示任何寄存器
“q” 表示寄存器eax,ebx,ecx,edx之一
“i”和“h” 表示直接操作数
“E”和“F” 表示浮点数
“g”表示任意
“a”,“b”,“c”,“d” 分别表示寄存器eax,ebx,ecx,edx
“S”和“D” 分别表示寄存器esi,edi
“I” 表示常数(0至31)
此外,如果一个操作数要求使用与前某个约束中所要求的是同一个寄存器,那就把与那个约束相对应的操作数标号放在约束条件中。在损坏部常常会以“memory”为约束条件,表示操作完成后内存中的内容已有改变,如果原来某个寄存器(也许在本次操作汇总并未使用到)的内容来自内存,则现在科能已经不一致。
还要注意,当输出部为空,即没有输出约束时,如果有输入约束存在,则必须保留分隔标记“:”号。
4.实方式向保护方式的转换
80386在开机后,首先是在实方式下,而要完全发挥80386的功能,就必须使80386进入保护方式。为了将80386从实方式转换到保护方式,必须遵循一些步骤。在硬件复位或将CR中的PE位变成逻辑0后,微处理器进入实方式。通过给CRO寄存器中的PE位置为1,微处理器将进入保护方式,但在进入保护方式之前,必须对其他方面做好初始化。下面的步骤将完成从实方式到保护方式的切换:
1. 初始化全局描述符表(GDT),使其描述符0为一个空描述符,并且使其至少包含一个有效的代码段描述符。
2. 将CRO的PE位置位,切换到保护方式。
3. 执行一条跳转指令清楚内部指令队列并把TSS描述符基址装到TR中。
4. 将初始选择子的值装到段寄存器中。
5. 现在80386已经运行在保护方式下。
下面的一个汇编程序是在DOS的环境下,从实方式进入保护方式。全局描述符表中共有四个选择子,依次为空描述符、代码段选择子、数据段选择子和屏幕段选择子。其中空描述符时Intel微处理器编程要求,而屏幕段选择子是为了直接写屏法在屏幕上显示信息而设置的。
dseg segment
gdt db 20h,00h, 00h,00h,00h,00h
; ----+--- ------+---------
; | GDT表的基地址
; |
; +------------GDT表的长度,四个段8*4=32=20h
gdtt db 000h,000h,000h,000h,000h,000h,000h,000h;空
db 0ffh,0ffh,000h,000h,000h,09eh,000h,000h;代码段
;limit= 0ffff
;base =00000000
;flag =9e
;G,D,O,AVL=0
db 0ffh,0ffh,000h,000h,000h,092h,000h,000h;数据段
;limit= 0ffff
;base =00000000
;flag =92
;G,D,O,AVL=0
db 0ffh,0ffh,000h,080h,00bh,093h,000h,000h;屏幕段
limit= 0ffff
;base =000b8000 显示存储区基地址
;flag =93
;G,D,O,AVL=0
pm db 'Enter Protect Mode!'
rm db 0dh,0ah,'Return Real Mode!',0dh,0ah,'$'
dseg ends
cseg segment
assume cs:cseg,ds:dseg
.386p
start:
mov ax,dseg
mov ds,ax
mov ax,0600h
mov bx,0700h
mov cx,000h
mov dx,184fh
int 10h
; 清屏,ah=06h,int 10h 向上滚屏
mov ah,02h
mov bh,00h
mov dx,0100h
int 10h
;置光标(1,0),dh=01h,dl-00h
mov ax,dseg
mov ds,ax
mov ax,cs
mov es,ax
xor eax,eax
xor ebx,ebx
mov ax,ds
mov cl,04h
shl eax,cl
mov bx,offset gdtt
add eax,ebx
mov bx,offset gdt+2
mov ds:[bx],eax
;把gdtt的基地址填入gdt中对应的位置
nop
mov ax,dseg
mov ds,ax
xor eax,eax,
xor ebx,ebx
mov ax,cs
mov cl,04h
shl eax,cl
add eax,ebx
mov bx,offset gdtt
mov ds:[bx+0ah],eax
;把代码段的基地址填入gdtt中对应的位置
mov ax,dseg
mov ds,ax
xor eax,eax
xor ebx,ebx
mov ax,ds
mov cl,04h
shl eax,cl
add eax,ebx
mov bx,offset gdtt
;把数据段的基地址填入gdtt中对应的位置
mov ax,dseg
mov ds,ax
mov ax,cs
mov es,ax
mov bx,offset gdt
xor ecx,ecx
cli
cli
lgdt fword ptr ds:[bx]
;载入gdt
mov ax,dseg
mov ds,ax
mov eax,cr0
or eax,01h
mov cr0,eax
jmp protection ;进入保护方式
protection:
db 66h
mov ds,ax
mov si,offset pm
mov bx,0018h
;0000 0000 0001 1000 全局描述符表,第三描述符表项,权限00
mov es,bx
mov di,0000h
mov cx,0014h;14h=20 pm的长度为20,设置循环次数为20
mov ah,0ah;设置显示属性
mov edx,cr0
and edx,1
cmp edx,1
jnz go1
show:
lodsb;源数据串ds:si 把ds:si的内容放入al,ah中放有显示属性
stosw:目的数据串es:di 把ax的内容放入显示存储区
;将DS M搬到0018:00000000 即屏幕段
;(0010的区段=B8000 请参考GDT表)
loop show
go1:
mov eax,cr0
and al,not 1
mov cr0,eax
jmp real;返回实方式
real:
sti
mov edx,cr0
and edx,1
cmp edx,0
jnz go2
mov ax,dseg
mov ds,ax
mov ah,09h
mov dx,offset rm
int 21h
go2:
mov ah,4ch
int 21h
cseg ends
end start |
|