飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 15611|回复: 42

[C/C++] 《汇编语言》王爽版 学习笔记(精辟的第十一章总结完毕)

  [复制链接]

该用户从未签到

发表于 2009-4-14 21:07:39 | 显示全部楼层 |阅读模式
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

前言:

   汇编语言,这个东西我是自学的,所以理解上也是很皮毛,这里简单写下读书写得,文中如有出错之处,还望多多指正。我把这本书分成两部分:前11章为第一部分。后几章涉及到中断等16位操作系统的知识,这儿就不写了,我们只关心前一部分。

    汇编语言。我是这样理解的:首先他是一套指令集,学习ASM可以站到一个“如何设计处理器、代码如何执行的角度”来看待这套指令集,或者说我们如何通过学习汇编语言来构建一个数据自己涉及的虚拟机平台;期中包括了寄存器的设置、如何将程序模块化==>堆栈思想的引用等等。  
    程序,就是内存中的一段数据 我们可以将其当做代码 也可以当做数据 也可以作堆栈来使用 所以内存的这些数据如何使用 决定权在我们如何去定义 我们如何让内存的数据与处理器产生联系 并去实现程序员的目的

    作为要写一本教程,首先要考虑如何将知识讲授给对方。这就是一个将知识系统化并条理化展开的一个过程。前言上有两句话:一个是循序渐进 后边的知识后边再讲。这句话很厉害 这个思想代表了作者的逻辑,第二句话是 编程的平台不是操作系统而是硬件。我们在16的单任务操作系统中可以更专注的了解汇编这门语言

    《汇编语言》这本书是王老师在河北农业大学上汇编语言这门课时的一套教学笔记,当然也有后期教学环节中对内容的完善补充,ASM我没听过,对这本书的理解只能通过对王老师的了解和对C语言的讲解中来尽量去深挖这本书更深层的东西。OK,旅程开始!

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

正文:

**************************************************************************************

前两章:

    首先介绍了16位的程序如何寻址:段地址+偏移地址。可以找到内存数据后,我们的处理器就有事情来做了。处理器要做的工具就是处理内存的数据,如读取内存的数据、移动内存的数据、对内存的数据做相应的运算(如加减乘除、布尔运算、逻辑移位),我们知道CPU有一些寄存器,那如果不用这些通用寄存器,程序将变成怎样?比如实现一个加法的操作:

int i=10,j=20;
j+=i;

用ASM描述一下:
........................................................


; ************** 描述性代码 *********************

assume cs:code,ds:data

data segment

i dw 10
j dw 20

data ends

code segment

start:
mov ds,data
add j,i

执行返回代码

code ends
end start

; ************** 描述性代码 *********************

........................................................


首先,如果CPU内部没有暂时存储器,那我们所有的操作都将直接操作处理内存数据,如果直接操作内存数据,CPU的执行效率将降低,如从内存中COPY数据到加法器中肯定没有从CPU内部传递效率高,资源上本身就是浪费。同时操作上即不方便也将对执行上造成紊乱,如果我们在CPU内部添加一个寄存器,作为一个中间停放站,情况又将会怎样。

我们设置一个中间变量 ax 情况将变成这样:

........................................................

assume cs:code,ds:data

data segment

i dw 10
j dw 20

data ends

code segment
start:
mov ax,data
mov ds,ax
mov ax,i
add ax,j

mov ax,4c00h
int 21h

code ends
end start


........................................................

0BD1:0000 B8D00B        MOV     AX,0BD0
0BD1:0003 8ED8          MOV     DS,AX
0BD1:0005 A10000        MOV     AX,[0000]
0BD1:0008 03060200      ADD     AX,[0002]
0BD1:000C B8004C        MOV     AX,4C00
0BD1:000F CD21          INT     21

在debug中观看该程序的汇编代码,我们发现 变量名 其实就相对与数据段的偏移地址。

于是我们发现在处理器上添加一个存储空间效果更好一些,当然还有其他原因使创建寄存器更为必要。

我们看一下上方这段程序在内存的情况:

C:\masm5>debug add.exe
-d
0BD1:0000  B8 D0 0B 8E D8 A1 00 00-03 06 02 00 B8 00 4C CD   ..............L.
0BD1:0010  21 5D C3                                          !]..

    程序就是内存中的一段数据,要处理这些数据,我们首先要知道内存中数据的地址和长度。内存中的代码段,由IP这个寄存器来指向将要执行的代码,而这条指令有多长,处理器通过第一个字节就可以判断出该指令的长度,以实现CPU去正确的执行下一条指令。

    IP指向的内存数据,首字节为机器码 机器码之后是否有数据,跟几个字节的数据,这个要根据机器码来判断且该值是一个固定值。即第一个字节是机器码,之后都是操作数(关于机器码的扩充,一个字节只能存放256中机器码,基本上够用了,将机器码扩充至两个字节的思路就是设置字节中的某位为标志位来作判断)。这也算是汇编指令设计的一个优点-->少去了判断所要处理数据长度的麻烦。


**************************************************************************************

第三章 栈机制


MOV 指令 是将数据从一个单元 复制到 另一个单元,这个单元只可能有三种情况: 段寄存器、通用寄存器、内存数据。

注意,以下格式是禁止的:内存-->内存;内存-->段寄存器;段寄存器-->段寄存器;寄存器和段寄存器的累加;内存和段寄存器的累加。

mov es,cs
mov [0],ds
mov ds,[0]
add es,ax
add ax,cs
add es,[0]
add [0],es

内存间数据的传递,CPU的指令集中有更便捷的指令代码,今后我们会有接触的。在数据传递中,我们要有格式匹配的意识,即传递字节要相等。下面我们来看几组简单的数据移动指令的机器码:

004295A2      B8 00000000   MOV EAX,0
004295A7      BB 00000000   MOV EBX,0
004295AC      B9 00000000   MOV ECX,0
004295B1      BA 00000000   MOV EDX,0

可以看到  E8 E9 EA EB 是对寄存器传递立即数的机器码

00429598      8BC0          MOV EAX,EAX
0042959A      8BDB          MOV EBX,EBX
0042959C      8BC9          MOV ECX,ECX
0042959E      8BC3          MOV EAX,EBX
004295A0      8BF7          MOV ESI,EDI

我们这里只想通过这几行代码来简单的了解下机器码的指令集,同时思考下,如果我们设计一套指令集,每条指令对应的机器码会是多少。

ADD 为加法;SUB 为减法;格式和使用上与 MOV 类似。

这里有一个注意点,就是我们在反汇编中看到的代码和源码是有差异的,如:

mov ax,[0] 语句在debug中就是将word ptr ds;[0]放到ax中,而源码中这样写则是 mov ax,0 ([0]是十进制的)

下面是栈机制,这个栈机制实在是太完美了,假如代码执行上我们不使用栈机制,大家可以试着设计一下如何实现程序的模块化:如寄存器的暂时保存、子程序如何传参、子程序如何返回等问题将如何处理。

栈机制这里就不想写了,用过OD的人对这个东西都非常了解。就是有一个栈溢出问题,该问题我们要特别注意一下。

**************************************************************************************

第四章 汇编程序格式

assume cs:code,ds:data,ss:stack

stack segment
stack ends

data segment
data ends

code segment
start:

mov ax,4c00h
int 21h
code ends
end start


编译: masm test;
连接:link test;

debug 跟踪调试 进入debug后输入个 ?  指令就都有了 ~~

常用的指令:

A 看代码
D 看内存
E 写内存
R 看寄存器
T 单步执行 遇Call跟进
G 执行到XX

**************************************************************************************

第五章 像C一样去学习ASM 构造一个循环

    一个语言,大多通过定义变量来实现对内存数据的处理(变量=内存的地址+长度(变量类型)),然后要有判断和循环语句,这样语言的基本框架就出来了。我们先来看一下在c语言和asm定义变量的区别:

---------------------------------------------------

所占内存大小:
char 占一个内存空间  定义为:byte
int  占两个内存空间  定义为:word

格式:
C语言:int a=0x10;
A语言:a byte 10h

定义字符串:
C语言:char *s="Hello,Nisy!";
A语言:szHello db 'Hello,Nisy!',0

==> 先变量名 然后定义类型 最后给定数值
1.数值项若为空 则用 ? 替代
2.定义数组,可用 n dup (?)

---------------------------------------------------

我们发现在处理器中加添暂存器(官方叫寄存器)比较方便后,需要考虑应该设置几个寄存器,寄存器用来做些什么的问题。

我们再设置两个寄存器:bx 和 cx ,bx 用来做偏移地址寻址,cx 用来存储循环指令中的次数。
(注意哦,AX,BX,CX 都是通用寄存器,当他们不做专项功能时,可以随意使用。)

BX 寄存器还有一种功效:可以用BX来做间接寻址。[BX] 默认的段地址为 ds 数据段。本章第一部分 就是来利用 CX,LOOP 构造循环 (记住我们构造的这三个通用寄存器,寄存器不在于多,够用就行)

用C来描述一个循环十次的代码:
for(int sum=0,i=0;i<10;i++)
{
        sum++;
}
for(int sum=0,i=10;i!=0;i--)
{
        sum++;
}

因为汇编中for都用while来替代 所以我们用while来实现一下

int i=0,sum=0;
while(i<10)
{
        sum++;
        i++;
}

while 和 for 的区别 初始值(如int i)while前定义好,()内放循环条件,{}中变量自增运算。

用汇编翻译一下:

   mov cx,10   
   mov ax,0
@@:inc ax
   loop @@

这段代码中涉及到了“标号问题”,同时也告诉了我们一种构建循环的方法。

本章第二部分就是:程序有简单推演到复杂的一个逻辑思维过程,程序由一个段扩展为多个段的必然趋势。

剩下的内容算是一个扩展,[bx] 和 loop 及 当循环体中需利用到 CX 情况的处理:

当循环体中利用到 CX 时,可以可以将其保存到一个word型变量中,用c描述就是:
{
        int x=i;
        {
                把 i 作为一个空闲内存来处理
        }
        i=x;
}

而汇编中的处理是引入栈机制,我认为栈机制是一个非常巧妙精简的思想,设想下,如果CPU没有栈机制,那我们程序模块化处理时将是如何的费劲,CALL时首先要保存返回地址,其次如何来传递参数 …… 长的结果将导致内存混乱,不堪设想。

随便写一段小循环的模型

; ///////////////// Code is Test ~~ By:Nisy  ///////////////////////

assume cs:code,ds:data,ss:stack

stack segment

dw 10 dup (?)

stack ends

data segment

szHello db 'Hello,Nisy!',0

data ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,2*10
mov ax,data
mov ds,ax

xor ax,ax
mov bx,offset szHello
mov cx,11
@@:
push cx
mov cx,5
ror word ptr [bx],cl    ; 这里先对数据进行循环右移 再进行累加
pop cx
add ax,[bx]
inc bx
inc bx
loop @@

mov ax,4c00h
int 21h

code ends

end start

; ///////////////// Code is Over ~~ By:Nisy  ///////////////////////


**************************************************************************************

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-4-15 17:09:30 | 显示全部楼层
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

**************************************************************************************

第六章 逻辑思维的扩展

程序由一个段扩展为多个段的历程,整个演变过程被王老师推理的非常经典。

**************************************************************************************

第七章 利用基地址和偏移地址 实现对数据、数据的处理

我们要设计一个转化大小写的程序,首先要弄明白转化的算法,我们用 AND 0DFH 来实现小变大,用 OR 20H 来实现大变小。
第二个问题要解决如何传参的问题,我们用C来看一下:

/**************************************************************************/

#include <iostream>
using namespace std;
void change(char *s,int n,int t)
{
        int i;  /* 查看该局部变量在堆栈的位置 */
        for(i=0;i<n;i++)
        {
                if(t==0)
                {
                        if( *(s+i) >= 'a' && *(s+i) <= 'z'  )
                        *(s+i)&=0x0df;
                }
                if(t==1)
                {
                        if( *(s+i) >= 'A' && *(s+i) <= 'Z'  )
                        *(s+i)^=0x20;                       
                }
               
        }
}

void main()
{
        char s[]="Hello,Nisy!";
        puts(s);
        change(s,strlen(s),0);
        puts(s);
        change(s,strlen(s),1);
        puts(s);
}

/**************************************************************************/


运行结果:

Hello,Nisy!
HELLO,NISY!
hello,nisy!

先OD一下 看下参数和局部变量在堆栈中的位置:


00401150  /> \55            PUSH EBP
00401151  |.  8BEC          MOV EBP,ESP
00401153  |.  83EC 44       SUB ESP,44         // SUB 的数值小一些也是OK的                  
00401156  |.  53            PUSH EBX           // 将用到的 寄存器 压栈
00401157  |.  56            PUSH ESI
00401158  |.  57            PUSH EDI
00401159  |.  C745 FC 00000>MOV DWORD PTR SS:[EBP-4],0       //  局部变量 i=0
00401160  |.  EB 09         JMP SHORT std.0040116B

堆栈数据:
0012FF10   00000000    .. 这里是 局部变量 i
0012FF14  /0012FF80    .. 这里是 PUSH EBP
0012FF18  |00401257  返回到 std.00401257 来自 std.00401005
0012FF1C  |0012FF74  ASCII "Hello,Nisy!"
0012FF20  |0000000B
0012FF24  |00000000


用IDA加载 看下 change 函数是如何编写的:

/**************************************************************************/

.text:00401150 change          proc near               ; CODE XREF: j_changej
.text:00401150
.text:00401150 var_4_i         = dword ptr -4
.text:00401150 arg_0_s         = dword ptr  8
.text:00401150 arg_4_len       = dword ptr  0Ch
.text:00401150 arg_8_t         = dword ptr  10h
.text:00401150
.text:00401150                 push    ebp             ; var_型 为局部变量 4为相对与 ESP+N 的N
.text:00401151                 mov     ebp, esp        ; arg_型 为参数 0 4 8 分别 + 8 为该参数在堆栈中的位置
.text:00401153                 sub     esp, 44h        ; 此为参数从左到右的顺序 (Std模式 参数从右到左压栈)
.text:00401156                 push    ebx
.text:00401157                 push    esi
.text:00401158                 push    edi
.text:00401159                 mov     [ebp+var_4_i], 0 ; 局部变量 i 赋初值 0
.text:00401160                 jmp     short loc_40116B
.text:00401162 ; ---------------------------------------------------------------------------
.text:00401162
.text:00401162 loc_401162:                             ; CODE XREF: change:loc_4011DDj
.text:00401162                 mov     eax, [ebp+var_4_i]
.text:00401165                 add     eax, 1
.text:00401168                 mov     [ebp+var_4_i], eax
.text:0040116B
.text:0040116B loc_40116B:                             ; CODE XREF: change+10j
.text:0040116B                 mov     ecx, [ebp+var_4_i]
.text:0040116E                 cmp     ecx, [ebp+arg_4_len]
.text:00401171                 jge     short loc_4011DF
.text:00401173                 cmp     [ebp+arg_8_t], 0 ; 判断是哪种转化方式 0 为大写转小写
.text:00401177                 jnz     short loc_4011A8
.text:00401179                 mov     edx, [ebp+arg_0_s] ; edx 做字符串的基地址
.text:0040117C                 add     edx, [ebp+var_4_i] ; 计算第 i 位字符的地址
.text:0040117F                 movsx   eax, byte ptr [edx]
.text:00401182                 cmp     eax, 61h        ; 以下为判断 字符是否在 'A'和'Z'之间
.text:00401185                 jl      short loc_4011A8
.text:00401187                 mov     ecx, [ebp+arg_0_s]
.text:0040118A                 add     ecx, [ebp+var_4_i]
.text:0040118D                 movsx   edx, byte ptr [ecx]
.text:00401190                 cmp     edx, 7Ah
.text:00401193                 jg      short loc_4011A8 ; 以上为判断 字符是否在 'A'和'Z'之间
.text:00401195                 mov     eax, [ebp+arg_0_s]
.text:00401198                 add     eax, [ebp+var_4_i]
.text:0040119B                 mov     cl, [eax]
.text:0040119D                 and     cl, 0DFh
.text:004011A0                 mov     edx, [ebp+arg_0_s]
.text:004011A3                 add     edx, [ebp+var_4_i]
.text:004011A6                 mov     [edx], cl       ; 转化后 实现 寄存器 ==> 内存数据 的传递
.text:004011A8
.text:004011A8 loc_4011A8:                             ; CODE XREF: change+27j
.text:004011A8                                         ; change+35j ...
.text:004011A8                 cmp     [ebp+arg_8_t], 1
.text:004011AC                 jnz     short loc_4011DD
.text:004011AE                 mov     eax, [ebp+arg_0_s]
.text:004011B1                 add     eax, [ebp+var_4_i]
.text:004011B4                 movsx   ecx, byte ptr [eax]
.text:004011B7                 cmp     ecx, 41h
.text:004011BA                 jl      short loc_4011DD
.text:004011BC                 mov     edx, [ebp+arg_0_s]
.text:004011BF                 add     edx, [ebp+var_4_i]
.text:004011C2                 movsx   eax, byte ptr [edx]
.text:004011C5                 cmp     eax, 5Ah
.text:004011C8                 jg      short loc_4011DD
.text:004011CA                 mov     ecx, [ebp+arg_0_s]
.text:004011CD                 add     ecx, [ebp+var_4_i]
.text:004011D0                 mov     dl, [ecx]
.text:004011D2                 xor     dl, 20h
.text:004011D5                 mov     eax, [ebp+arg_0_s]
.text:004011D8                 add     eax, [ebp+var_4_i]
.text:004011DB                 mov     [eax], dl
.text:004011DD
.text:004011DD loc_4011DD:                             ; CODE XREF: change+5Cj
.text:004011DD                                         ; change+6Aj ...
.text:004011DD                 jmp     short loc_401162
.text:004011DF ; ---------------------------------------------------------------------------
.text:004011DF
.text:004011DF loc_4011DF:                             ; CODE XREF: change+21j
.text:004011DF                 pop     edi
.text:004011E0                 pop     esi
.text:004011E1                 pop     ebx
.text:004011E2                 mov     esp, ebp
.text:004011E4                 pop     ebp
.text:004011E5                 retn
.text:004011E5 change          endp


/*******************************************************************/

怎么C的代码编译出来 执行上这么费劲呢 汗一个 ~~ 摸清规律后,我们开始行动 ~~

assume cs:code,ds:data,ss:stack

stack segment
dd 20 dup (?)
stack ends

data segment
szTest db 'hello,Nisy!',0
data ends

code segment

; 构造一个将小写字母转化为大写的函数
ChangeSmallToBig:

push bp
mov bp,sp
sub sp,10

push cx
push bx
push dx
push si

mov bx,[bp+4+0]           ; 0 s
mov cx,[bp+4+2]           ; 4 size

cmp word ptr [bp+4+4] ,0
jnz small
mov si,0
big:
mov dl,byte ptr [bx+si]
cmp dl,'a'
jl big_1
cmp dl,'z'
jg big_1
and dl,0dfh
mov byte ptr [bx+si],dl
big_1:
inc si
loop big
jmp over

small:   
cmp word ptr [bp+4+4] ,1   ; samm 的时候用一下i 呵呵
jnz over
mov word ptr [bp-2],0
mov si,word ptr [bp-2]    ; -4 i
small_1:
mov si,word ptr [bp-2]
mov dl,byte ptr [bx+si]
cmp dl,'A'
jl small_2
cmp dl,'Z'
jg small_2
or dl,020h
mov byte ptr [bx+si],dl
small_2:
inc si
mov word ptr [bp-2],si
loop small_1

over:
pop si
pop dx
pop bx
pop cx
mov sp,bp
pop bp
ret
; 函数结束

start:
mov ax,stack
mov ss,ax
mov sp,4*20
mov ax,data
mov ds,ax

mov bx,offset szTest
mov cx,11
xor ax,ax
push ax
push cx
push bx
call ChangeSmallToBig

mov bx,offset szTest
mov cx,11
mov ax,1
push ax
push cx
push bx
call ChangeSmallToBig

mov ax,4c00h
int 21h
code ends
end start


**************************************************************************************

  查看程序的运行结果方法:
输入:
debug test.exe
u
g 7b
d ds:0
u
g 8a
d ds:0


就可以看到内存中,字符串大小写的转化了。

代码上其实还差一个 ShowText 的函数,回头在写,先来总结下对以上这段代码的思考

1. 首先是太刻意模仿 C 了,为什么要 sub sp,10 其实真的没必要,直接 push 0 不就算定义了一个变量么,回头再用 [ebp-2] 就可以操作了。
2. 对于小的函数,其实无需必要搞 push ebp mov ebp,esp 这样正式的函数入口。计数器完全可以用寄存器替代。
3. 在汇编中,犯下了一个错误,其实汇编语言中是没有 for() 语句的,for 用 while 给替代了,我们通过看 WIN32的汇编,和IDA对程序的反编译就可以了解到。

下面我们定义一个数组  char a[10];

在处理该数组的时候,我们习惯上用一个循环
#include<stdio.h>
main()
{
        char a[10];
        int i;
        for(i=0;i<10;i++)
        {
                *(a+i)=getchar();
        }
        *(a+9)='\0';
        puts(a);
}

我们处理的时候,用一个首地址+偏移值来做处理。

在汇编中我们照样可以构建指针来实现对数组的操作。可以使用寄存器组合来构建,比如[bx+idata],为了更为方便的处理数据,我们又引入了两个寄存器,SI 和 DI (这两个寄存器不可拆分为两个 8 位单元使用),他们的功能和 bx 类似(段地址都默认放在 DS 中)。这样我们就可以构建强大的数组指针。组合格式如:

[BX + IDATA]
[SI + IDATA]
[DI + IDATA]
[BX + SI]
[BX + DI]
[BX + SI + IDATA]
[BX + DI + IDATA]

说明的是,WIN16下只有这三个 通用寄存器 来做指针来处理,而WIN32下是都可以使用的,比如[EAX]。

下边我们看一下本章的问题 7.9

;*****************************************************************

assume cs:code,ss:stack,ds:data

stack segment
dd 20 dup (?)
stack ends

data segment
db '1. hello        '
db '2. china        '
db '3. nisy         '
db '4. good         '
data ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,20*4
mov ax,data
mov ds,ax

mov bx,0
mov cx,4
str_0:
push cx
mov si,0
mov cx,4
str_1:
mov dl,byte ptr [bx+si+3]
and dl,11011111b            ; 偷个懒 对数据不做判断了
mov byte ptr [bx+si+3],dl
inc si                 
loop str_1
add bx,10h
pop cx
loop str_0


mov ax,4c00h
int 21h
code ends
end start

;*****************************************************************


PS: 我们处理BX 时,相当于将数据作为二维数组 a[4][16] 处理,bx+16 相当于 *(a+1) ,当然,我们也可以将其视为顺序表数据,循环部分也可以写成这样 BX 只做基址处理,si 做指针处理。

mov bx,0
mov cx,4
str_0:
push cx
mov cx,4
str_1:
mov dl,byte ptr [bx+si+3]
and dl,11011111b            ; 偷个懒 对数据不做判断了
mov byte ptr [bx+si+3],dl
inc si                 
loop str_1
add si,9
pop cx
loop str_0

这里放一个我写代码和看结果的心得:

记事本保存一段代码 保存到 c:\masm5 目录中(如文件名为 test.asm)
若想检测下自己的代码是否有格式上的错误 直接 cmd --> cd\ --> cd masm5 --> masm 后回车,输入 文件名 让其编辑.
如果OK最好,若有提示问题,直接看该行代码是哪出的错,修改后保存直接运行
masm test;
link test;
当我们修改文件后 按向上的箭头 就可以看到该指令 敲下回车就可以继续编译。

然后是看结果,到现在为止,我们还未解除 0XB8000000 这个地址,所以看结果就需要看内存的数据,我的方法是:
输入 debug test.exe
然后 u 直到看到 mov ax,4c00 这行代码 假如说该代码偏移地址为29 输入 g 29  然后 d ds:0 (假如0是我们字符串在代码段的偏移地址) 就可以看到我们的程序是否正确的处理了字符串。


**************************************************************************************

第八章

第八章多半还是寻址,处理二维数组的数据。其中介绍了一个 div 除法指令。

这里引入另一个寄存器,就是 DX 寄存器,到现在为止 我们 8 个通用寄存器就介绍完了。
在进行乘法和除法运算时,我们就需要考虑到计算结果的如何保存的问题。

比如 100 * 100 = 10000 通过该结果我们可得出:2位 * 2位 结果一定不超过 4位。所以做16位数据运算时,16位 * 16位 其结果必定可以用 32 位来表示。 于是退出,我们做计算时,让 DX 和 AX 联合, DX做高16位,AX做低16位,这样就可以计算16位数据的运算。这个就是我们处理 16 位数据的思路。

本章只介绍了除法操作,
被除数若32位,则用DX+AX表示, 商保存再AX中,余数保存再DX中
被除数若16位,则用  AX 表示, 商保存在AL中,余数保存再AH中
当我们将被除数设置好之后,使用div指令即可
div reg
div [内存数据]

注意的是 div 指令后不能是立即数 必须得是寄存器或内存数据

在debug中随意写一些除法运算来观察一下

C:\>debug
-a
0B52:0100 mov ax,10
0B52:0103 mov cx,3
0B52:0106 div cl
0B52:0108
-r
AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B52  ES=0B52  SS=0B52  CS=0B52  IP=0100   NV UP EI PL NZ NA PO NC
0B52:0100 B81000        MOV     AX,0010
-t

AX=0010  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B52  ES=0B52  SS=0B52  CS=0B52  IP=0103   NV UP EI PL NZ NA PO NC
0B52:0103 B90300        MOV     CX,0003
-t

AX=0010  BX=0000  CX=0003  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B52  ES=0B52  SS=0B52  CS=0B52  IP=0106   NV UP EI PL NZ NA PO NC
0B52:0106 F6F1          DIV     CL
-t

AX=0105  BX=0000  CX=0003  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B52  ES=0B52  SS=0B52  CS=0B52  IP=0108   NV UP EI PL NZ AC PO NC
0B52:0108 B81000        MOV     AX,0010

这一章介绍内容不多,最后一个图表题也是对二维数组的考察,比较简单,回头再写。

**************************************************************************************


&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-4-15 22:24:05 | 显示全部楼层
第三章到第八章讲解了8个通用寄存器的使用

第九第十章 是对 JMP 和 CALL 指令的研究 明天再写

第十一章 讲的是标志寄存器 我对这里颇有研究 也是最喜欢写的内容 明天将这里作为重点来讲解 ~~

十一章之后 有一个HOOK的思想 回头简单写一下 其他的章节就不打算整了

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

对第八章数组的补充内容

我们知道,程序在处理内存数据的时候,首先要知道数据的地址和长度,在代码段,start标识了代码段的起始位置,第一个字节为机器码,IP作为指针指向代码段当前地址,并通过所指向的数据计算当前指令的长度和下一条指令的地址。在数据段,我们得知变量首地址之后,取变量长度用的是word ptr [] 和 byte ptr [] 等指令去获取数据的长度。我们再深入讨论一下这个问题:

0.GIF

全局变量在数据段,局部变量在栈段,这个写一个程序就知道了。

下面我们看一下一维数组的寻址:
int a[10];
int i=0;
bx = a;   // 将数组首地址给bx来做基地址
si = i    // 用si来做变量进行对数组元素的寻址
==>  &a[ i ] == bx+si
==> *(a+i) == word ptr [bx+si]

接着是二维数组:

int a[3][4];
int i=0;
bx = a + n*4 ;
si = i ;

对于a[2][3] (数组的最后一个元素)
bx = bx + 2*4
si = 3
==> &a[2][3] = bx + 2*4 + si (si=3)
==> *( *(a+2) + 3 ) = word ptr [bx+si+2*4]  (si=3)

注意:这四个寄存器间只有这四种组合,使用[bx]的时候默认的段地址是ds,使用[bp]的时候段地址是ss,不过貌似我们用到[bp+si+idata]组合的时候比较少。我们用数组的思想再来看一下大小写转化的例子。


assume cs:code,ds:data,ss:stack

stack segment
dd 20 dup (?)
stack ends

data segment
szTest db 'Hello,Nisy! Good Boy!',0
_size         dw ?
data ends

code segment

ChangeSmallToBig:  ; 将小写转化为大写

push bp
mov bp,sp

push bx
push cx
push si
push di

mov bx,[bp+4+0]
mov di,[bp+4+2]
mov si,0
mov cl,0dfh
st_c:
cmp si,di
je over_c
cmp byte ptr [bx+si],'a'
jl st_c1
cmp byte ptr [bx+si],'z'
jg st_c1
and [bx+si],cl
st_c1:
inc si
jmp st_c

over_c:
pop di
pop si
pop cx
pop bx
mov sp,bp
pop bp
ret

Sizeof:               ; 求字符串长度
push bp
mov bp,sp
push bx
push si

xor si,si
st_s:
mov bx,[bp+4+0]
mov bx,[bx+si]
test bx,bx
je over_si
inc si
jmp st_s

over_si:
mov ax,si
pop si
pop bx
mov sp,bp
pop bp
ret

ShowStr:            ; 显示字符串
push bp
mov bp,sp
push ax
push bx
push cx
push dx
push si
push di

mov ax,0b800h
mov es,ax
xor ax,ax
mov dx,[bp+4+4]
mov al,160
mul dh
mov si,ax
mov ah,0
mov al,2
mul dl
add si,ax

xor di,di
mov bx,[bp+4]
mov cx,[bp+4+2]
st_sh:
cmp cx,di
je over_sh
mov dl,[bx+di]
mov es:[si],dl
inc si
mov dx,[bp+4+6]
mov es:[si],dl
inc si
inc di
jmp st_sh

over_sh:

pop di
pop si
pop dx
pop cx
pop bx
pop ax
mov sp,bp
pop bp
ret


start:
mov ax,stack
mov ss,ax
mov sp,2*20
mov ax,data
mov ds,ax

mov bx,offset szTest
push bx
call sizeof             ; 求长度     参数 字符串地址
mov _size,ax
mov ax,2h
push ax
mov ax,405h
push ax
push _size         
mov bx,offset szTest
push bx
call ShowStr            ; 显示原字符串 参数 字符串地址,字符串长度,行列,文字属性
push _size
mov bx,offset szTest
push bx
call ChangeSmallToBig   ; 大小写转化 参数 字符串地址,长度
mov ax,2h
push ax
mov ax,505h
push ax
push _size         
mov bx,offset szTest
push bx
call ShowStr            ; 显示转化后的字符串 参数 字符串地址,字符串长度,行列,文字属性

mov ax,4c00h
int 21h
code ends
end start


简单模块化了一下 感觉有点像用ASM32来写ASM16的程序 反倒是把小函数给复杂化了

显示字符串函数中 其实没必要加上 那个长度的参数 取每一位 然后test一下就OK

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-4-18 13:15:00 | 显示全部楼层
得周一才能更新 这两天出去玩了

PS:使用某些指令时,寄存器的任务是提供给相匹配指令数据,其他时寄存器其实是给我们可用的空间和暂存器,不要让众多的寄存器成为我们的负担。

------------------------------------------------------------------------------------

第九章 和 第十章 是转移指令

修改IP 或者修改CS和IP的指令称为转移指令

首先来看JMP指令的格式 JMP 之后跟的地址只有三种:REG、标号、内存数据

jmp REG
jmp short ptr 标号  (8位)  // 默认的是 short ptr
jmp near  prt 标号  (16位)
jmp far   prt 标号  (32位)
jmp word  ptr XX    // 默认的是 word ptr
jmp dword ptr XX

而 XX 是什么呢? 该内存数据就是我们常说的指针

lpsh        dw ?
sh:
mov lpsh,offset sh
……
jmp word ptr lpsh // 其实也可以写成 jmp lpsh 默认的类型就是  word ptr

条件转移指令都是短转移

================================================

条件转移指令都是短转移,-128到+127 之间

无符号运算转移指令(标志C和Z).
JA/JNBE    不小于或不等于时转移.(意思就是大于则就跳)
JAE/JNB    大于或等于转移.
JB/JNAE    小于转移.
JBE/JNA    小于或等于转移.

有符号运算转移指令 (标志S,O和Z)
JG/JNLE    大于转移.
JGE/JNL    大于或等于转移.
JL/JNGE    小于转移.
JLE/JNG    小于或等于转移.

================================================   
     
JE/JZ      等于转移.
JNE/JNZ    不等于时转移.
JC         有进位时转移.
JNC        无进位时转移.
JO         溢出转移.
JNO        不溢出时转移.
JNP/JPO    奇偶性为奇数时转移.
JP/JPE     奇偶性为偶数时转移.
JNS        符号位为 "0" 时转移.
JS         符号位为 "1" 时转移.

================================================

循环控制指令(短转移)

LOOP            CX不为零时循环.(CX--)
LOOPE/LOOPZ     CX不为零且标志Z=1时循环.
LOOPNE/LOOPNZ   CX不为零且标志Z=0时循环.
JCXZ            CX为零时转移.
JECXZ           ECX为零时转移.

================================================

我们看一下这个代码:
jmp s
s:
xor ax,ax

0BD4:0006 EB01          JMP     0009
0BD4:0008 90            NOP
0BD4:0009 33C0          XOR     AX,AX

EB 之后跟的是01 JMP 指令中的数值 = 目的地址 - next IP

标号位置这一行 如果没有指令则自动编译为 NOP

PS: 这里有一个SMC程序和显示子函数 先不搞了 ~~

$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

我们看 CALL 指令

CALL REG
CALL short ptr 标号  (8位)  // 默认的是 short ptr
CALL near  prt 标号  (16位)
CALL far   prt 标号  (32位)
CALL word  ptr XX    // 默认的是 word ptr
CALL dword ptr XX


CALL 之后的地址 其实和JMP一样 也就这三种,

CALL 的意思:
PUSH NEXT IP
JMP  XXXX

00A01328    E8 E3E9E9FF     CALL phpDesig.0089FD10
00A0132D    84C0            TEST AL,AL

CALL 机器码 E8 之后跟的数据为 FFE9E9E3 = 0089FD10 - 00A0132D
这个数值 = 目的地址 - next IP

--------------------------------------------------------

MUL 指令  这里第一版上有一个印刷错误,8位的时候被乘数是放在AL中

将被乘数放AX中
MUL REG
或者
MUL 内存数据 // 不可以跟立即数

-t

AX=0300  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B55  ES=0B55  SS=0B55  CS=0B55  IP=0102   NV UP EI PL NZ NA PO NC
0B55:0102 B002          MOV     AL,02
-t

AX=0302  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B55  ES=0B55  SS=0B55  CS=0B55  IP=0104   NV UP EI PL NZ NA PO NC
0B55:0104 B102          MOV     CL,02
-t

AX=0302  BX=0000  CX=0002  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B55  ES=0B55  SS=0B55  CS=0B55  IP=0106   NV UP EI PL NZ NA PO NC
0B55:0106 F6E1          MUL     CL
-t

AX=0004  BX=0000  CX=0002  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=0B55  ES=0B55  SS=0B55  CS=0B55  IP=0108   NV UP EI PL NZ NA PO NC
0B55:0108 90            NOP

------------------------------------------------------------------------


之后是模块化设计
01. 显示字符串
02. 除法溢出问题
03. 数值显示

这个东西回头写 ~~

------------------------------------------------------------------------
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-4-26 09:55:59 | 显示全部楼层
第十一章 标志寄存器的必要性

    CPU的指令集中肯定要有数据比较的操作。我们知道对于数据 A 和 B 既可以将其看成是有符号数,也可作为无符号数来处理。A 和 B 之间的关系只有三种:相等、大于、小于。今天我们要做的就是设计两个数值大小的比较。

---------------------------------------------------------------------

情况一: A = B

    我们之前的设计,在CUP内部构造了8个寄存器,寄存器就是方便我们使用的变量空间,同时寄存器也为某些指令的执行提供所需要的参数。

    若 A = B ,如何将这个结果告诉程序,来告诉判断指令如何执行呢? 我们可以从内存中取一个变量来存放这个比较结果,当然也可以和之前设计通用寄存器那样,再增加一个通用寄存器来保存这个比较的中间值(比较的结果)。显然结果只有两种可能:相等 or 不等 。所以只需要 1 位(one bit)即可保存。

那我们再设计一个 寄存器 TX 来保存这个比较的中间值  

          7654 3210 高位->低位
0000 0000 0000 0000 寄存器 TX

我们设置 TX 寄存器的第0位来表示其结果是否为零(该位称为置零位),若为则置 1 (结果是零吗?若等于0 则TRUE ),反之则置 0 ;
于是比较是否相等的指令,只需要来判断 TX 的第0位数值就OK了。


CMP   A, B
JE    相等则跳  ( 判断下TX的第0位是否为 1 )
JNE   不相等则跳( 判断下TX的第0位是否为 0 )

---------------------------------------------------------------------

情况二: A 和 B 为无符号数

我们来看一下无符号数 A - B 的情况,相等的情况略去

A == FFFDH
B == FFFEH

我们平时在做减法运算的时候,减数的某位小于被减数的该位,则向高位借1,我们也按该思想来处理计算机中的减法。当减数小于被减数时,也向高位(想象中的)借1。

  1FFFD
-  FFFE
--------

于是我们就可以根据无符号数是否借位来可判断两数的大小。

我们设置 TX 寄存器的第1位来表示其结果的正负(该位称为借位符),若借位则置 1 ;反之则置 0 ;

CMP   A, B
JA    大于则跳 ( 判断下TX的第1位是否为 0 )
JB    小于则跳 ( 判断下TX的第1位是否为 1 )

---------------------------------------------------------------------

情况三: A 和 B 为有符号数

再来看有符号数的比较,我们生活中比较两个数值的大小一般都是相减后判断结果是否大于零。

A - B :  若 > 0  ==> A > B ;若 < 0  ==>  A < B

所以我们只需要对结果做正负的判断,就可以确定其大小关系。

我们设置 TX 寄存器的第2位来表示其结果的正负(该位称为符号位),若为正则置 0 ;若为负则置 1 ;

但在CPU中的还是该情况吗?我们来看一下 8位数据的运算(8位可表示的大小为 -128 ~ +127 之间)。

100 + 100 = ?

  0110 0100 = 100
+ 0110 0100 = 100
------------
  1100 1000 = -56

100 + 100  = -56  这个结果我们显然是无法接受的。

原因就在于在计算结果超出了8位数据所能表示的范围而覆盖了符号位。我们称这种现象叫做溢出。那什么情况下才可能发生溢出呢?只有这两种情况:

低与下限(低于-128) 只能是 负 + 负 (负 - 正)
超过上线(高于 127) 只能是 正 + 正 (正 - 负)

我们注意到 “负 + 负” 和 “正 + 正”这两种情况操作数的符号位是相同的,若溢出后,其结果的符号位则与操作数相反,利用这个规律,我们来判断结果是否溢出。

我们设置 TX 第3位来标记结果是否溢出(该位称为溢出位):若溢出则置 1,未溢出则置 0.

通过上方的分析,我们就可以来比较有符号数的大小:

if(TX第2位 == TX第3位) // (符号位)==(溢出位)  ==> A >=  B ( 注:A=B 时 (符号位)==(溢出位) )
if(TX第2位 != TX第3位) // (符号位)!=(溢出位)  ==> A <   B

CMP   A, B
JG    大于则跳        ( TX的第2位 == 第3位 && 第0位==0 <结果不为0> )
JGE   大于或等于转移  ( TX的第2位 == 第3位 )    // || 第0位==0)
JL    小于则跳        ( TX的第2位 != 第3位 )


---------------------------------------------------------------------

    OK,当我们设计出自己的判断数据大小的模型后,我们再来看书上所介绍的Inter CPU是如何实现两数值比较的。按照我们的推理虚拟出来的TX寄存器其实就是Inter的 Flag 寄存器。我们所定义的低四位就是书上所说的几个标志寄存器。我们是按照自己的推理设计出来了,而CPU的设计者也是按正常人的思路来设计的。在学知识的时候,试着逆向的把知识的由来推演出来,理解上自然就会更加深入。
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-4-26 18:17:47 | 显示全部楼层
终于搞明白我的学习方式/思路了

我认为教学是这样一个流程

概括全局 ==> 提出问题/假设 ==> 分析推理 ==> 解决问题

总的提起来 然后一点一点去瓦解
循序渐进的 由本至末的 去接受新事物
PYG19周年生日快乐!

该用户从未签到

 楼主| 发表于 2009-10-10 23:33:33 | 显示全部楼层
很久以前写的 放在内版了

转出来好了 总结的一般 各位凑合着看吧
PYG19周年生日快乐!
  • TA的每日心情
    开心
    2022-4-18 15:36
  • 签到天数: 207 天

    [LV.7]常住居民III

    发表于 2009-10-10 23:38:26 | 显示全部楼层
    这篇文章于我有如甘露.学习了,谢谢!
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2009-10-10 23:51:58 | 显示全部楼层
    谢谢老大,向老大看齐
    PYG19周年生日快乐!
  • TA的每日心情

    昨天 07:54
  • 签到天数: 1759 天

    [LV.Master]伴坛终老

    发表于 2009-10-10 23:54:09 | 显示全部楼层
    复制下来慢慢看。 /:good
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表