80386ASM程序设计基础(八)

80386实模式下编程

80386在实模式下是一个更快的8086,它不但可以进行32位操作,而且还可以进32位寻址,并且还可以使用80386的扩展指令。不过,由于是在实模下,寻址的最大空间为1M。在一个段内,段的最大长度不超过64K,否则就会发生异常。

在8086下定义一个段的完整格式是:

段名 [定位类型]  [组合类型]  [‘类别’]

80386下定义一个段的完整格式是:

段名 [定位类型]  [组合类型]  [‘类别’] [属性类型]

说明:属性类型有两种:USE32和USE16,USE32表示32位段,USE16表示16位段。如果你在程序中用到伪指令.386,那么默认的属性类型就是USE32(32位段),如果没有用伪指令指定CPU的类型,那么默认的属性类型就是USE16,在实方式下只能使用16位段,即用USE16。

eg:

CSEG PARA PUBLIC USE32;定义一个32位的段

AA DW ?

BB DD ?

CC DB ?

DD DW ?

EE DW 0,0,0.....

CSEG ENDS

由于在80386中用到了66H操作前缀和67H地址前缀,因此尽管在实式模式下,只要设定的CPU类型是80386,仍然可以进行32位操作,可以进行32位寻址,66H,67H这两个前缀无需程序员在程序中书写,汇编程序会自动加上的。只要在程序中对32位操作数进行访问,或进行32位寻址,那么就会加上操作数前缀66H和地址前缀67H。相反,如果在32位段中对16位或8位的访问,汇编程序中也会加上这两个前缀。

下面将给出一个例子程序,演示一下在80386的实模式下编程的方法与技巧(这是从网上down的一个程序,不是我写的,但我会作详细的解剖,并与8086下的程序设计作出比较):

用十进制,十六进制,二进制三种形式显示双字存储单元F000:1234中的内容

|------------------MAIN PROC------------|

| .386                                  |

| code segment para public ‘code‘ use16 |

|   assume cs:code                      |

| begin:                                |

|   mov ax,0f000h                       |

|   mov fs,ax                           |

|   mov eax,fs:[1234H]                  |

|   call todec                          |

|   call newline                        |

|   call tohex                          |

|   mov al,‘H‘                          |

|   call echo                           |

|   call newline                        |

|   call tobin                          |

|   mov al,‘B‘                          |

|   call echo                           |

|   call newline                        |

|   mov ah,4ch                          |

|   int 21h                             |

|---------------------------------------|

;sub-function todec

todec proc near

pushad

mov ebx,10

xor cx,cx

dec1:

xor edx,edx

div ebx

push dx

inc cx

or eax,eax

jnz dec1

dec2:

pop ax

call toasc

call echo

loop dec2

popad

ret

todec endp

;sub-function tobin

tobin proc near

push eax

push ecx

push edx

bsr edx,eax

jnz bin1

xor dx,dx

bin1:

mov cl,31

sub cl,dl

shl eax,cl

mov cx,dx

inc cx

mov edx,eax

bin2:

rol edx,1

mov al,‘0‘

adc al,0

call echo

loop bin2

pop  edx

pop  ecx

pop  eax

ret

tobin endp

;sub-function tohex

tohex proc near

countb=8

enter countb,0

movzx ebp,bp

mov   ecx,countb

mov   edx,eax

hex1:

mov al,dl

and al,0fh

mov [ebp-countb+ecx-1],al

ror edx,4

loop hex1

mov cx,countb

xor ebx,ebx

hex2:

cmp byte ptr [ebp-countb+ebx],0

jnz hex3

inc ebx

loop hex2

dec ebx

mov cx,1

hex3:

mov al,[ebp-countb+ebx]

inc ebx

call toasc

call echo

loop hex3

leave

ret

tohex endp

;sub-function toasc

toasc proc near

and al,0fh

cmp al,‘0‘

cmp al,‘9‘

seta dl

movzx dx,dl

imul dx,7

add al,dl

toasc1:ret

toasc endp

;sub-function newline

newline proc near

push dx

push ax

mov dl,0dh

mov ah,2

int 21

mov dl,0ah

int 21

pop ax

pop dx

ret

newline endp

echo proc near

push ax

push dx

mov dl,al

mov ah,2

int 21h

pop dx

pop ax

echo endp

剖析:

先来看主程序框架,下面就是MAIN PROC:

|------------------MAIN PROC-------------------------------|

|.386;定义处理器的类型为386表示可以使用所有80386指令       |

| code segment para public ‘code‘ use16                    |

|   assume cs:code                                         |

| begin:                                                   |

|   mov ax,0f000h                                          |

|   mov fs,ax;将f000h装入段寄存器fs                        |

|   mov eax,fs:[1234H];将1234H内存单元中的双字送给寄存器EAX|

|   call todec;调用子过程todec                             |

|   call newline;调用子过程newline进行回车换行             |

|   mov eax,fs:[1234h];                                    |

|   call tohex;调用子过程tohex                             |

|   mov al,‘H‘                                             |

|   call echo;显示字符H                                    |

|   call newline;                                          |

|   mov eax,fs:[1234H]                                     |

|   call tobin;调用子过程tobin                             |

|   mov al,‘B‘                                             |

|   call echo

|   call newline                                           |

|   mov ah,4ch                                             |

|   int 21h                                                |

|----------------------------------------------------------|

主程序中的内容一目了然,很简单。和8086下唯一不同的是就是要用伪指令定义CPU的类型,并且段寄存器的定义多了一个属性类型USE16,再就是32位操作,使用80386的指令,其它的和8086下没有什么区别。

重点是要分析几个过程,从网上down下来时,过程newline和toasc没有实现代码,因为这很简单,所以上述toasc,newline,echo的过程体是由我写进去的,这两个过程体代码不多而且非常简单,就不作介绍了。重点介绍todec,tobin,tohex。

a.子过程todec,这个子过程的主要功能是将f000:1234双字单元的内容用十进制显示,下面就来看每一行代码:

|-----------------------------------------------------------|

|todec proc near                                            |

|   pushad                                                  |

|   mov ebx,10                                              |

|   xor cx,cx                                               |

|  dec1:                                                    |

|   xor edx,edx                                             |

|   div ebx                                                 |

|   push dx                                                 |

|   inc cx                                                  |

|   or eax,eax                                              |

|   jnz dec1                                                |

|  dec2:                                                    |

|   pop ax                                                  |

|   call toasc                                              |

|   call echo                                               |

|   loop dec2                                               |

|   popad                                                   |

|   ret                                                     |

|todec endp                                                 |

|-----------------------------------------------------------|

分析:将一个数用十进制数来表示,要它对它进行除以10的运算,得到商和余数。再将商除以10,如此循环直到商为0为止,在这个过程中得到的一系列的模(余数)就是十进制数系列。在主程序中,已经将f000:1234双字单元的内容放到EAX寄存器中,由于后来要用十六进制数,二进制数显示,所以EAX寄存器的内容不允许改变,因此在子过程的一开始,要将EAX的内容先入栈,所以子过程的一开始就用PUSHAD将8个32位通用寄存器的内容全部入栈。在标号dec1不断地进行除以10运算,将所得到的余数全部入栈,同时用cx进行计数。在标号dec2中,逐个弹出在标号dec1中得到的余数,然后分别将它们显示出来,这样就可以将该存储单元中的内容用十进数表示,下面解释每一条指令的功能:

a1.pushad;将8个32位通用寄存器全部入栈

a2.xor cx,cx;cx清0

a3.mov ebx,10;10=>ebx

a4.xor edx,edx;edx清0

a5.div ebx;edx存放高32位,不过是0,EAX中存放低32位,即ffff:[1234]双字的内容;除法得到的商放在EAX,余数放在EDX

a6.push dx;将edx的低16位dx入栈

a7.inc cx;cx+1=>cx

a8.or eax,eax;对eax进行或操作,主要是用来判断eax是否为0,即判断商是否为0,从而判断是否应该结束标号为dec1的循环。

a9.jnz dec1

a10.pop ax;将放在堆栈中的余数逐个弹出到ax中

a11.call toasc;显示ax的内容

a12.call echo

a13.loop dec2;将所有的余数显示完毕

a14.popad;8个32位通用寄存器全部出栈

a15.ret

b.子过程tohex

PUSH BP

SP=>BP

SP

|------------------------------------------------------------|

|tohex proc near                                             |

|  countb=8                                                  |

|  enter countb,0                                            |

|  movzx ebp,bp                                              |

|  mov   ecx,countb                                          |

|  mov   edx,eax                                             |

|hex1:                                                       |

|  mov al,dl                                                 |

|  and al,0fh                                                |

|  mov [ebp-countb+ecx-1],al                                 |

|  ror edx,4                                                 |

|  loop hex1                                                 |

|  mov cx,countb                                             |

|  xor ebx,ebx                                               |

|hex2:                                                       |

|  cmp byte ptr [ebp-countb+ebx],0                           |

|  jnz hex3                                                  |

|  inc ebx                                                   |

|  loop hex2                                                 |

|  dec ebx                                                   |

|  mov cx,1                                                  |

|hex3:                                                       |

|  mov al,[ebp-countb+ebx]                                   |

|  inc ebx                                                   |

|  call toasc                                                |

|  call echo                                                 |

|  loop hex3                                                 |

|  leave                                                     |

|  ret                                                       |

|tohex endp                                                  |

|------------------------------------------------------------|

分析:该子过程的功能是将f000:1234双字单元的内容以16进制数显示出来,首先来考虑一下将一个数以16进制数表示出来的算法,事实上在汇编语言中操作数一直都是以十六进制表示的。因此,在这个子过程中不可以像上一个子过程一样,通过不断的除法取模得到结果。事实上,我们只需要将32位操作,以每半个字节(四位)的内容显示出来就可以了,有了这一编程思想,就很容易看懂上面的子过程。当然你们会问,为什么要每次只显示半个字节而不显示一个字节呢?呵呵,十六进制的十六个数是从0000-1111,不就是半个字节了。所以要循环8次才可以显示出32位的EAX,所以这里用ror指令来不断循环移位,每次右移4位放到dl的低4位中。这8个半字节分别放在[ebp-1]至[ebp-8]的存储单元中。不过,存储的顺序是由低位到高位,如果就这样显示结果肯定显示反了。标号hex2,hex3的主要功能是用来判断f000:1234双字单元的内容是否为0,如果为0,只需要将最后结果显示一个0即可,否则就显示出8位内容。下面是每条指令的功能:

b1.countb=8;伪指令定义一局部变量countb,其值为8

b2.enter countb,0;建立堆栈框架指令

b3.movzx ebp,bp;对bp进行零扩展

b4.mov   ecx,countb;8=>ecx

b5.mov   edx,eax;将eax=>edx

b6.mov al,dl

b7.and al,0fh;取低4位

b8.mov [ebp-countb+ecx-1],al;将8个半字节的内容逐一送到[ebp-1]至[ebp-8]的内存单元中

b9.ror edx,4;对edx进行循环右移,每次移动4位

b10.loop hex1

b11.mov cx,countb

b12.xor ebx,ebx;ebx清0

b13.cmp byte ptr [ebp-countb+ebx],0;下面的语句主要用来判断源操作数f000:1234的内容是否为0,如果是0,就在屏幕上只显示一个0

b14.jnz hex3

b15.inc ebx

b16.loop hex2

b17.dec ebx

b18.mov cx,1

b19.mov al,[ebp-countb+ebx];逐一显示[ebp-8]到[ebp-1]的内容。

b20.inc ebx

b21.call toasc

b22.call echo

b23.loop hex3

b24.leave;释放堆栈框架

b25.ret

c.子过程tobin

|---------------------------------------|

|tobin proc near                        |

|   push eax                            |

|   push ecx                            |

|   push edx                            |

|   bsr edx,eax                         |

|   jnz bin1                            |

|   xor dx,dx                           |

|bin1:                                  |

|   mov cl,31                           |

|   sub cl,dl                           |

|   shl eax,cl                          |

|   mov cx,dx                           |

|   inc cx

|   mov edx,eax                         |

|bin2:

|   rol edx,1

|   mov al,‘0‘

|   adc al,0

|   call echo

|   loop bin2

|   pop  edx                            |

|   pop  ecx

|   pop  eax

|   ret

|tobin endp

|---------------------------------------|

分析:将一个数用二进制数显示出来,只需要用ROL指令就可以了。这里作者写的程序就是这个思路,在标号bin1中主要判断f000:1234单元的内容是否为0,如果为0,那么只需要在屏幕上显示一个0就可以了。否则的话,就用ROL指令对源操作数移位32位,从最高位31位到最低位逐一显示出来,程序设计思路很简单,没有什么复杂的算法,下面看每一条指令的含义:

c1.push eax;eax入栈

c2.push ecx;ecx入栈

c3.push edx;edx入栈

c4.bsr edx,eax;对eax进行扫描,并把第一个为1的位号送给edx

c5.jnz bin1;如果eax不为0,就跳到c7去执行

c6.xor dx,dx;如果eax为0,就将dx清0

c7.mov cl,31;从c7到c12主要用来设置计数器cx,如果eax=0,那么就设置cx=1,如果eax不等于0,那么就设置ecx=32

c8.sub cl,dl

c9.shl eax,cl

c10.mov cx,dx

c11.inc cx

c12.mov edx,eax

c13.rol edx,1;从c13到c15主要用来显示二进制数据,顺序是从最高位31位到最低位0位

c14.mov al,‘0‘

c15.adc al,0

c16.call echo

c17.loop bin2

c18.pop  edx;edx出栈

c19.pop  ecx;ecx出栈

c20.pop  eax;eax出栈

c21.ret

在后续的篇幅里将主要介绍保护式下的段页管理机制及及如何在保护模下编程。

80386实模式下编程

80386在实模式下是一个更快的8086,它不但可以进行32位操作,而且还可以进32位寻址,并且还可以使用80386的扩展指令。不过,由于是在实模下,寻址的最大空间为1M。在一个段内,段的最大长度不超过64K,否则就会发生异常。

在8086下定义一个段的完整格式是:

段名 [定位类型]  [组合类型]  [‘类别’]

80386下定义一个段的完整格式是:

段名 [定位类型]  [组合类型]  [‘类别’] [属性类型]

说明:属性类型有两种:USE32和USE16,USE32表示32位段,USE16表示16位段。如果你在程序中用到伪指令.386,那么默认的属性类型就是USE32(32位段),如果没有用伪指令指定CPU的类型,那么默认的属性类型就是USE16,在实方式下只能使用16位段,即用USE16。

eg:

CSEG PARA PUBLIC USE32;定义一个32位的段

AA DW ?

BB DD ?

CC DB ?

DD DW ?

EE DW 0,0,0.....

CSEG ENDS

由于在80386中用到了66H操作前缀和67H地址前缀,因此尽管在实式模式下,只要设定的CPU类型是80386,仍然可以进行32位操作,可以进行32位寻址,66H,67H这两个前缀无需程序员在程序中书写,汇编程序会自动加上的。只要在程序中对32位操作数进行访问,或进行32位寻址,那么就会加上操作数前缀66H和地址前缀67H。相反,如果在32位段中对16位或8位的访问,汇编程序中也会加上这两个前缀。

下面将给出一个例子程序,演示一下在80386的实模式下编程的方法与技巧(这是从网上down的一个程序,不是我写的,但我会作详细的解剖,并与8086下的程序设计作出比较):

用十进制,十六进制,二进制三种形式显示双字存储单元F000:1234中的内容

|------------------MAIN PROC------------|

| .386                                  |

| code segment para public ‘code‘ use16 |

|   assume cs:code                      |

| begin:                                |

|   mov ax,0f000h                       |

|   mov fs,ax                           |

|   mov eax,fs:[1234H]                  |

|   call todec                          |

|   call newline                        |

|   call tohex                          |

|   mov al,‘H‘                          |

|   call echo                           |

|   call newline                        |

|   call tobin                          |

|   mov al,‘B‘                          |

|   call echo                           |

|   call newline                        |

|   mov ah,4ch                          |

|   int 21h                             |

|---------------------------------------|

;sub-function todec

todec proc near

pushad

mov ebx,10

xor cx,cx

dec1:

xor edx,edx

div ebx

push dx

inc cx

or eax,eax

jnz dec1

dec2:

pop ax

call toasc

call echo

loop dec2

popad

ret

todec endp

;sub-function tobin

tobin proc near

push eax

push ecx

push edx

bsr edx,eax

jnz bin1

xor dx,dx

bin1:

mov cl,31

sub cl,dl

shl eax,cl

mov cx,dx

inc cx

mov edx,eax

bin2:

rol edx,1

mov al,‘0‘

adc al,0

call echo

loop bin2

pop  edx

pop  ecx

pop  eax

ret

tobin endp

;sub-function tohex

tohex proc near

countb=8

enter countb,0

movzx ebp,bp

mov   ecx,countb

mov   edx,eax

hex1:

mov al,dl

and al,0fh

mov [ebp-countb+ecx-1],al

ror edx,4

loop hex1

mov cx,countb

xor ebx,ebx

hex2:

cmp byte ptr [ebp-countb+ebx],0

jnz hex3

inc ebx

loop hex2

dec ebx

mov cx,1

hex3:

mov al,[ebp-countb+ebx]

inc ebx

call toasc

call echo

loop hex3

leave

ret

tohex endp

;sub-function toasc

toasc proc near

and al,0fh

cmp al,‘0‘

cmp al,‘9‘

seta dl

movzx dx,dl

imul dx,7

add al,dl

toasc1:ret

toasc endp

;sub-function newline

newline proc near

push dx

push ax

mov dl,0dh

mov ah,2

int 21

mov dl,0ah

int 21

pop ax

pop dx

ret

newline endp

echo proc near

push ax

push dx

mov dl,al

mov ah,2

int 21h

pop dx

pop ax

echo endp

剖析:

先来看主程序框架,下面就是MAIN PROC:

|------------------MAIN PROC-------------------------------|

|.386;定义处理器的类型为386表示可以使用所有80386指令       |

| code segment para public ‘code‘ use16                    |

|   assume cs:code                                         |

| begin:                                                   |

|   mov ax,0f000h                                          |

|   mov fs,ax;将f000h装入段寄存器fs                        |

|   mov eax,fs:[1234H];将1234H内存单元中的双字送给寄存器EAX|

|   call todec;调用子过程todec                             |

|   call newline;调用子过程newline进行回车换行             |

|   mov eax,fs:[1234h];                                    |

|   call tohex;调用子过程tohex                             |

|   mov al,‘H‘                                             |

|   call echo;显示字符H                                    |

|   call newline;                                          |

|   mov eax,fs:[1234H]                                     |

|   call tobin;调用子过程tobin                             |

|   mov al,‘B‘                                             |

|   call echo

|   call newline                                           |

|   mov ah,4ch                                             |

|   int 21h                                                |

|----------------------------------------------------------|

主程序中的内容一目了然,很简单。和8086下唯一不同的是就是要用伪指令定义CPU的类型,并且段寄存器的定义多了一个属性类型USE16,再就是32位操作,使用80386的指令,其它的和8086下没有什么区别。

重点是要分析几个过程,从网上down下来时,过程newline和toasc没有实现代码,因为这很简单,所以上述toasc,newline,echo的过程体是由我写进去的,这两个过程体代码不多而且非常简单,就不作介绍了。重点介绍todec,tobin,tohex。

a.子过程todec,这个子过程的主要功能是将f000:1234双字单元的内容用十进制显示,下面就来看每一行代码:

|-----------------------------------------------------------|

|todec proc near                                            |

|   pushad                                                  |

|   mov ebx,10                                              |

|   xor cx,cx                                               |

|  dec1:                                                    |

|   xor edx,edx                                             |

|   div ebx                                                 |

|   push dx                                                 |

|   inc cx                                                  |

|   or eax,eax                                              |

|   jnz dec1                                                |

|  dec2:                                                    |

|   pop ax                                                  |

|   call toasc                                              |

|   call echo                                               |

|   loop dec2                                               |

|   popad                                                   |

|   ret                                                     |

|todec endp                                                 |

|-----------------------------------------------------------|

分析:将一个数用十进制数来表示,要它对它进行除以10的运算,得到商和余数。再将商除以10,如此循环直到商为0为止,在这个过程中得到的一系列的模(余数)就是十进制数系列。在主程序中,已经将f000:1234双字单元的内容放到EAX寄存器中,由于后来要用十六进制数,二进制数显示,所以EAX寄存器的内容不允许改变,因此在子过程的一开始,要将EAX的内容先入栈,所以子过程的一开始就用PUSHAD将8个32位通用寄存器的内容全部入栈。在标号dec1不断地进行除以10运算,将所得到的余数全部入栈,同时用cx进行计数。在标号dec2中,逐个弹出在标号dec1中得到的余数,然后分别将它们显示出来,这样就可以将该存储单元中的内容用十进数表示,下面解释每一条指令的功能:

a1.pushad;将8个32位通用寄存器全部入栈

a2.xor cx,cx;cx清0

a3.mov ebx,10;10=>ebx

a4.xor edx,edx;edx清0

a5.div ebx;edx存放高32位,不过是0,EAX中存放低32位,即ffff:[1234]双字的内容;除法得到的商放在EAX,余数放在EDX

a6.push dx;将edx的低16位dx入栈

a7.inc cx;cx+1=>cx

a8.or eax,eax;对eax进行或操作,主要是用来判断eax是否为0,即判断商是否为0,从而判断是否应该结束标号为dec1的循环。

a9.jnz dec1

a10.pop ax;将放在堆栈中的余数逐个弹出到ax中

a11.call toasc;显示ax的内容

a12.call echo

a13.loop dec2;将所有的余数显示完毕

a14.popad;8个32位通用寄存器全部出栈

a15.ret

b.子过程tohex

PUSH BP

SP=>BP

SP

|------------------------------------------------------------|

|tohex proc near                                             |

|  countb=8                                                  |

|  enter countb,0                                            |

|  movzx ebp,bp                                              |

|  mov   ecx,countb                                          |

|  mov   edx,eax                                             |

|hex1:                                                       |

|  mov al,dl                                                 |

|  and al,0fh                                                |

|  mov [ebp-countb+ecx-1],al                                 |

|  ror edx,4                                                 |

|  loop hex1                                                 |

|  mov cx,countb                                             |

|  xor ebx,ebx                                               |

|hex2:                                                       |

|  cmp byte ptr [ebp-countb+ebx],0                           |

|  jnz hex3                                                  |

|  inc ebx                                                   |

|  loop hex2                                                 |

|  dec ebx                                                   |

|  mov cx,1                                                  |

|hex3:                                                       |

|  mov al,[ebp-countb+ebx]                                   |

|  inc ebx                                                   |

|  call toasc                                                |

|  call echo                                                 |

|  loop hex3                                                 |

|  leave                                                     |

|  ret                                                       |

|tohex endp                                                  |

|------------------------------------------------------------|

分析:该子过程的功能是将f000:1234双字单元的内容以16进制数显示出来,首先来考虑一下将一个数以16进制数表示出来的算法,事实上在汇编语言中操作数一直都是以十六进制表示的。因此,在这个子过程中不可以像上一个子过程一样,通过不断的除法取模得到结果。事实上,我们只需要将32位操作,以每半个字节(四位)的内容显示出来就可以了,有了这一编程思想,就很容易看懂上面的子过程。当然你们会问,为什么要每次只显示半个字节而不显示一个字节呢?呵呵,十六进制的十六个数是从0000-1111,不就是半个字节了。所以要循环8次才可以显示出32位的EAX,所以这里用ror指令来不断循环移位,每次右移4位放到dl的低4位中。这8个半字节分别放在[ebp-1]至[ebp-8]的存储单元中。不过,存储的顺序是由低位到高位,如果就这样显示结果肯定显示反了。标号hex2,hex3的主要功能是用来判断f000:1234双字单元的内容是否为0,如果为0,只需要将最后结果显示一个0即可,否则就显示出8位内容。下面是每条指令的功能:

b1.countb=8;伪指令定义一局部变量countb,其值为8

b2.enter countb,0;建立堆栈框架指令

b3.movzx ebp,bp;对bp进行零扩展

b4.mov   ecx,countb;8=>ecx

b5.mov   edx,eax;将eax=>edx

b6.mov al,dl

b7.and al,0fh;取低4位

b8.mov [ebp-countb+ecx-1],al;将8个半字节的内容逐一送到[ebp-1]至[ebp-8]的内存单元中

b9.ror edx,4;对edx进行循环右移,每次移动4位

b10.loop hex1

b11.mov cx,countb

b12.xor ebx,ebx;ebx清0

b13.cmp byte ptr [ebp-countb+ebx],0;下面的语句主要用来判断源操作数f000:1234的内容是否为0,如果是0,就在屏幕上只显示一个0

b14.jnz hex3

b15.inc ebx

b16.loop hex2

b17.dec ebx

b18.mov cx,1

b19.mov al,[ebp-countb+ebx];逐一显示[ebp-8]到[ebp-1]的内容。

b20.inc ebx

b21.call toasc

b22.call echo

b23.loop hex3

b24.leave;释放堆栈框架

b25.ret

c.子过程tobin

|---------------------------------------|

|tobin proc near                        |

|   push eax                            |

|   push ecx                            |

|   push edx                            |

|   bsr edx,eax                         |

|   jnz bin1                            |

|   xor dx,dx                           |

|bin1:                                  |

|   mov cl,31                           |

|   sub cl,dl                           |

|   shl eax,cl                          |

|   mov cx,dx                           |

|   inc cx

|   mov edx,eax                         |

|bin2:

|   rol edx,1

|   mov al,‘0‘

|   adc al,0

|   call echo

|   loop bin2

|   pop  edx                            |

|   pop  ecx

|   pop  eax

|   ret

|tobin endp

|---------------------------------------|

分析:将一个数用二进制数显示出来,只需要用ROL指令就可以了。这里作者写的程序就是这个思路,在标号bin1中主要判断f000:1234单元的内容是否为0,如果为0,那么只需要在屏幕上显示一个0就可以了。否则的话,就用ROL指令对源操作数移位32位,从最高位31位到最低位逐一显示出来,程序设计思路很简单,没有什么复杂的算法,下面看每一条指令的含义:

c1.push eax;eax入栈

c2.push ecx;ecx入栈

c3.push edx;edx入栈

c4.bsr edx,eax;对eax进行扫描,并把第一个为1的位号送给edx

c5.jnz bin1;如果eax不为0,就跳到c7去执行

c6.xor dx,dx;如果eax为0,就将dx清0

c7.mov cl,31;从c7到c12主要用来设置计数器cx,如果eax=0,那么就设置cx=1,如果eax不等于0,那么就设置ecx=32

c8.sub cl,dl

c9.shl eax,cl

c10.mov cx,dx

c11.inc cx

c12.mov edx,eax

c13.rol edx,1;从c13到c15主要用来显示二进制数据,顺序是从最高位31位到最低位0位

c14.mov al,‘0‘

c15.adc al,0

c16.call echo

c17.loop bin2

c18.pop  edx;edx出栈

c19.pop  ecx;ecx出栈

c20.pop  eax;eax出栈

c21.ret

在后续的篇幅里将主要介绍保护式下的段页管理机制及及如何在保护模下编程。


相关文章

  • 活性污泥法数学模型的发展及应用
  • 中国给水排水 2003Vol. 19 CHINA WA TER &WASTEWA TER No. 2 活性污泥法数学模型的发展及应用 孙德荣, 吴星五 (同济大学城市污染控制国家工程研究中心, 上海200092) 摘 要: 综述了I ...查看


  • 我的ARM学习经历
  • 1.基础阶段 话说06年第一份工作从事的是PLC开发,用protel画原理图和PCB,写AVR单片机程序,焊焊板子,还去过华强北买器件,比较杂,但是接触面比较广,为进一步学ARM打下了基础. 期间不甘心只停留在AVR单片机层次,买了邵贝贝的 ...查看


  • 一级减速器说明书
  • 目录 引言-------------------------------------------------------------1 一. 减速器三维设计------------------------------------------ ...查看


  • 日本F2支援战斗机发展史:战后日本航空坡脚的起步
  • 日本航空自卫队F-2A双机空中编队 战斗支援概念的出现 世界大战结束后,日本必须要为其残暴的侵略行为付出代价,因此面临着严厉的制裁和全亚洲乃至世界的敌视.在这种情况下,日本完全丧,失了在国际上的政治地位,而在军事上,日本必须时时小心地在为国 ...查看


  • 美国ASME规范与中国压力容器标准的比较
  • 第37卷 第4期 石 油 化 工 设 备 Vol 137 No 14 2008年7月 PETRO 2CH EMICAL EQU IPM EN T J uly 2008 标准化 文章编号:100027466(2008) 0420051208 ...查看


  • 海上莲花:新加坡艺术科学博物馆
  • ? Timothy Hursley 新加坡艺术科学博物馆(ArtScience Museum,ASM)是新加坡海岸线上地标式的建筑,出水芙蓉般的造型与毗邻同样出自萨夫迪之手的金沙酒店相映成趣,亦被称为 " 新加坡欢迎之手 &quo ...查看


  • 微型计算机的发展.现状及趋势
  • 微型计算机的发展.现状及趋势 摘要:Microcomputer is an electronic computer, according to their performance index classification for namel ...查看


  • 温度控制器
  • 温度控制器 姓名: 专业班级: 指导教师: 摘 要 本文主要讨论Pt100传感器在温度控制系统中的应用.采用Pt100恒流源三线制,采集信号经过AD7705转换之后一方面传至ATmega16L 单片机处理并通过LED 数码管显示出来,另一方 ...查看


  • 数字逻辑(第六版 白中英)课后习题答案
  • 第六章习题答案 1现有D 触发器组成的三个n 位寄存器,需要连接起来传送数据.当控制信号S a 有效时,执行(Ra )→Rc 的操作:当控制信号S b 有效时,执行(R b )→R C 的操作.试写出连接电路的逻辑表达式,并画出逻辑电路图. ...查看


热门内容