- UID
- 2198
注册时间2005-6-29
阅读权限255
最后登录1970-1-1
副坛主
该用户从未签到
|
楼主 |
发表于 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
这一章介绍内容不多,最后一个图表题也是对二维数组的考察,比较简单,回头再写。
**************************************************************************************
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& |
|