push offset msg1
call printf
add esp,4
ret
main endp
end start
程序的意思很简单就是在屏幕上打印出某个东西的价格,如过要你拿笔和纸算,拿你肯定很快就能算出来,但你让电脑怎么算呢?当电脑执行到第一个语句的时候,也就是num=10,
它就把10放到某个地方并且记住这个值,寄存器或者内存,呵呵,它也就这两个地方,为什么要这么做呢?因为后面要用它来计算啊,为了算出这个值,电脑好的办法就是放在它的内存里,为什么不是寄存器?因为寄存器太少了,就那么几个,呵呵,所以了它就把10存在一个叫num的内存里,注意了哦,num是程序里的变量名,是存中里的一个位置的名称,它的值是10,你可能会问,不起名不行么?行,等下在调试器中你看到的就是没名的。来看看它在调试器中的样子:
a db 12h
b dw 1234h
c1 dd 12345678h
msg1 db "the number is=%xh",0dh,0ah,0
.code
start:
call main
ret
main proc
mov al,a
cbw
cwde
push eax
push offset msg1
call printf
add esp,8
ret
main endp
end start
首先,你得想a,b,c1三个变量在程序中到底是怎么存的,是12 12 34 12 34 56 78,还是 78 56 34 12 34 12 12呢?呵呵,用调试器载入程序看看就知道了:
哈哈,看到了没,正确的是这个:00403000: 12 34 12 78 56 34 12 74-68,这是为什么?
还有就是这个程序打印的三个结果又是什么呢?是12h和0012h和00000012h吗?如果是,那你就错了哦,应该是:the number is=12h the number is=3412h the number is=78123412h
fld f3
fstp qword ptr f
push dword ptr f+4
push dword ptr f+8
push offset msg1
call printf
add esp,12
ret
main endp
end start
程序很简单,就是分别在屏幕上打印三个浮点值,如下图:
在这里我要说明下,我只有把32位和80位的转换为64位的,才能打印成功,这可能是库函数printf的原因,怎么转换呢?
32位浮点转换64位浮点:首先得借助一个64位的浮点局部变量:
local f:real8
fld dword ptr f1
fstp f
第一句定义了f位一个64位的浮点局部变量,第二句就是把32位浮点数转换为80位的,然后第三句就是把80位的转换位64位的。
80位浮点转换位64位浮点数:同样借助一个64位的浮点局部变量:
fld f3
fstp qword ptr f
第二句就是把80位的浮点转换位60位的。
printf proto C :VARARG
.data
f1 real8 19.2
f2 dword 20
msg1 db "the floating to int number is=%d",0dh,0ah,0
msg2 db "the int to floating number is=%f",0dh,0ah,0
.code
start:
call main
ret
main proc
local f:real8
fld f1
fistp dword ptr f
push dword ptr f
push offset msg1
call printf
add esp,8
浮点数转换成整数:
fld f1
fistp word ptr f
首先我们还是借助了一个64位的局部变量,先把浮点数装入浮点寄存器,然后用装换整行的指令变成整数再存入一个局部变量就行了。
整数转换成浮点数:
fild f2
fstp f
先把整数用装换指令装入浮点寄存器,然后把浮点数存到一个局部变量就可以了。
我在后面会详细说名浮点数的运算和浮点寄存器的。
平方根指令:fsqrt
这个指令就一种形式就是fsqrt,就是把第0个浮点寄存器st0 的值变成平方根值然后存在st0中。
再回到例题中,我们应该先算s值,然后再算平方根下面的值然后求平方根就行了。
S值是三边长除以2,转换成浮点指令就是:fld a ;先把a值放到浮点寄存器st0中
再就是 fadd b ;这个就是a+b,结果存在st0中
然后 fadd c ;同上,结果存在st0中
现在算出了三边长的和,在除以2就ok了 fiv two;结果在st0中,
最后把st0里面的值用fstp s 存到s中就把s值算出来了。
再来算根号下面的值,这里有乘法和减法,我们先算减法:fld s
fsub,a;这个就是s-a,结果在st0里
再 fld s
fsub b;这个结果还是在st0里,但是上面那个s-a已经被推到st(1)这个浮点堆栈了啊,记住了。
再 fld s
fsub c;这个结果还是在st0里,
那么s-a被推到st(2)了,s-b被推到st(1)了,s-c的值在st(0)中,这里千万不能及乱了啊,下一步算乘法,先算s*(s-c), mul s;果在st0li
fmul st(0),st(1)这一步是算s*(s-a)*(s-b)
最后算s*(s-a)*(s-b)*(s-a) fmul st(0),st(2),
然后把s*(s-a)*(s-b)*(s-a)的结果再求平方根 fsqrt
再把这个平方根的值弹出到area中。
这是按照上面的想法写出的程序:
.386
.model flat,stdcall
option casemap:none
includelib msvcrt.lib
printf proto C :VARARG
scanf proto C :VARARG
.data
msg1 db "%f,%f,%f",0
msg2 db "area=%7.2f",0ah,0dh,0
msg3 db "please input three floating numbers",0ah,0dh,0
two real4 2.0
.code
start:
call main
ret
main proc
local a:real4
local b:real4
local c1:real4
local s:real4
local area:real8
.data
msg1 db "%f,%f,%f",0
msg2 db "area=%7.2f",0ah,0dh,0
msg3 db "please input three floating numbers",0ah,0dh,0
two real4 2.0
.code
start:
call main
ret
main proc
local a:real4
local b:real4
local c1:real4
local s:real4
local area:real8 ;这里是变量定义
Y_equal_minus_one:
push -1
push offset msg2
call printf
add esp,8
jmp out_this_program ;这里如果不跳转的话就会执行到Y_equal_zero了,这明显是不行的,所要跳转
Y_equal_zero:
xor eax,eax
push eax
push offset msg2
call printf
add esp,8
jmp out_this_program ;这里如果不跳转的话就会执行到Y_equal_zero了,这明显是不行的,所要跳转
Y_equal_one:
push 1
push offset msg2
call printf
add esp,8 ;这里就不要再跳转了,直接执行到下面就行了
out_this_program:
ret
main endp
end start
下面是运行结果:
C:\w\b\ch4\ch4-3>ch4-3
please input a int number
0
the Y value is 0
C:\w\b\ch4\ch4-3>ch4-3
please input a int number
-56
the Y value is -1
C:\w\b\ch4\ch4-3>ch4-3
please input a int number
56
the Y value is 1
像这种判断嵌套的地方,就是不停的比较,然后根据判断结果跳转到要执行的地方,下面我们来看看浮点数的判断,在比较完两个浮点数之后,我们紧接着得用fstsw将FPU的状态寄存器里面的比较结果存到一个16位的内存位置或者是个16位的寄存器,但最好是存在ax中,因为紧接着就得用sahf指令把浮点数比较的结果存到标志寄存器EFLAGS中,然后我们就可用JCC条件跳转指令来实现跳转了,具体的化来看看例子:
例子 ch4-4:
这个一元二次方程怎么求解,大家应该很熟悉了吧,就不多说了,下面来看看怎么实现浮点数的比较,代码如下:
.386
.model flat,stdcall
option casemap:NONE
includelib msvcrt.lib
printf proto c:VARARG
scanf proto c:VARARG
.data
zero dq 0.0000001
four dw 4
msg1 db "please input three numbers",0ah,0dh,0
msg2 db "%f,%f,%f",0
msg3 db "The equation is not a quadratic" ,0ah,0dh,0
msg4 db "The equation has two cpmplex roots: %8.4f+%8.4fi",0ah,0dh,0
msg5 db "The equation has two cpmplex roots: %8.4f-%8.4fi",0ah,0dh,0
msg6 db "The equation has two equal roots: %8.4f",0ah,0dh,0
msg7 db "The equation has two real roots: %8.4f,%8.4f",0ah,0dh,0
.code
start:
call main
ret
main proc
local a:dword
local b:dword
local c1:dword
local d:dword
local disc:dword
local x1:qword
local x2:qword
local realpart:qword
local imagpart:qword
local x:qword ;一些局部变量定义
out_this_program:
ret
main endp
end start
下面是运行结果:
C:\w\b\ch4\ch4-4>ch4-4
please input three numbers
1,2,1
The equation has two equal roots: -1.0000
C:\w\b\ch4\ch4-4>ch4-4
please input three numbers
2,6,1
The equation has two real roots: -0.1771, -2.8229
C:\w\b\ch4\ch4-4>ch4-4
please input three numbers
1,2,2
The equation has two cpmplex roots: -1.0000+ 1.0000i
The equation has two cpmplex roots: -1.0000- 1.0000i
从这里你可以看见浮点数的比较和整数的比较有很大的不同之处了吧?
下面我们来看看直接跳转表
直接跳转表类似于c语言里面的case语句,实现了程序的多分支跳转
先看例子,例子 ch4-5:给出一百分成绩,要求输出成绩等级‘A’,’B‘,C,D,E,90分以上就输出’A‘,80~89分为’B‘,70~79为’C’,60~69为’D‘,60以下为’E‘。
首先我们把打印成绩等级的代码写成直接定址表,方便程序的判断和跳转,代码如下:
.386
.model flat,stdcall
option casemap:NONE
includelib msvcrt.lib
printf proto c:VARARG
scanf proto c:VARARG
.data
onehunderd dword 100.0
one dword 1.0
msg1 db "please input a score",0ah,0dh,0
msg2 db "the score leveal is %c",0ah,0dh,0
msg3 db "%d",0
.code
start:
call main
ret
main proc
local score:dword
local s:dword
local p:dword
local w:dword
local d:dword
local f:qword
includelib msvcrt.lib
printf proto c:VARARG
scanf proto c:VARARG
.data
msg1 db "The sum of 1!+2!+3!...+20! is:%e",0ah,0dh,0
.code
start:
call main
ret
main proc
local sum:qword
local temp:dword
local n:dword
mov n,1
fldz
fstp sum
fld1
fstp temp
again:
cmp n,20
ja outagain
fimul n
fadd sum
fst sum
inc n
jmp again
outagain:
fstp sum
push dword ptr sum+4
push dword ptr sum+8
push offset msg1
call printf
add esp,12
ret
main endp
end start
运行结果如下:
C:\w\b\ch5\ch5-1>ch5-1
The sum of 1!+2!+3!...+20! is:-1.#QNAN0e+000
呵呵,错的不行,唉,仔看看程序,实在没看出哪里错了,只好调试了,用cdb -2 C:\w\b\ch5\ch5-1\ch5-1 打开程序,然后用rm f 打开我们想要看的寄存器,然后用g @$exentry跳到我们的这个程序,然后单步跟踪程序发现ch5_1!main+0x1d:
00401023 da4df0 fimul dword ptr [ebp-10h] ss:0023:0013ffac=00000001这句时,发现st0是个不定值,st0= 0.183338657867652323710e+0834,所以乘出来当然是错的哦,然后就不停的改,调试,最后终于正确了,结果如下:
C:\w\b\ch5\ch5-1>ch5-1
The sum of 1!+2!+3!...+20! is:2.561326e+018
最后正确的代码:
.386
.model flat,stdcall
option casemap:NONE
includelib msvcrt.lib
printf proto c:VARARG
scanf proto c:VARARG
.data
msg1 db "The sum of 1!+2!+3!...+20! is:%e",0ah,0dh,0
.code
start:
call main
ret
main proc
local sum:qword
local temp:dword
local n:dword
mov n,1 ;初始化循环变量n
fldz
fstp sum ;初始化存和的变量
fld1
fstp temp ;初始化计算阶乘的变量
again:
cmp n,20
ja outagain ;不满足循环条件就退出循环
fld temp
fild n
fmulp st(1),st
fst temp
fld sum
faddp st(1),st
fstp sum
inc n
jmp again ;这一段就是计算 temp=temp*n,sum=sum+temp,就相当于算出了1!+2!+3!。。。20!
local i:dword ;定义了一个循环变量,就是数组的下标
local a[10]:byte ;定义了一个10个字节的数组
local b[10]:dword ;定义了一个10个双字的数组
local p :byte ;定义了一个字节变量,这里可以把它看成是字节指针
local p1 :dword ;定义了一个双字变量,这里可以把它看成是双字指针
mov i,0
mov ebx,i
again:
cmp i,9
mov eax,i
ja out_again
mov byte ptr a[ebx],al ;这句就是用下标访问数组了,和下面的比较下,想想为什么
inc i
jmp again ;这一段代码主要是往a数组里面依次存入0,1,2,。。。9
out_again:
mov i,0
mov ebx,i
again1:
cmp i,9
mov eax,i
ja print_arry
mov b[ebx*4],eax ;根据下标i访问数组,和上面那句比较下看看
inc i
jmp out_again
print_arry:
local i:dword ;定义了一个循环变量,就是数组的下标
local a[10]:byte ;定义了一个10个字节的数组
local b[10]:dword ;定义了一个10个双字的数组
local p :byte ;定义了一个字节变量,这里可以把它看成是字节指针
local p1 :dword ;定义了一个双字变量,这里可以把它看成是双字指针
mov i,0
again:
cmp i,9
mov ebx,i
mov eax,i
ja out_again
mov byte ptr a[ebx],al ;这句就是用下标访问数组了,和下面的比较下,想想为什么
inc i
jmp again ;这一段代码主要是往a数组里面依次存入0,1,2,。。。9
out_again:
mov i,0
again1:
cmp i,9
mov eax,i
mov ebx,i
ja print_arry
mov b[ebx*4],eax ;根据下标i访问数组,和上面那句比较下看看
inc i
jmp again1
print_arry:
out_this_program:
push offset msg2
call printf
add esp,4 ;这里打印回车换行
ret
main endp
end start
呵呵,你可以和前面的代码比较看看,如果你看不出第一个程序的问题,最好的是你把第一个代码编译运行,自己调试着看看,保你收获多多。
下面看看用指针试试:看例子ch6-2:
例 ch6-2:用指针输出数组的全部元素
代码如下:
.386
.model flat,stdcall
option casemap:NONE
includelib msvcrt.lib
printf proto c:VARARG
.data
msg1 db "%d",0
msg2 db 0ah,0dh,0
.code
start:
call main
ret
main proc
local i:dword ;定义了一个循环变量,就是数组的下标
local a[10]:byte ;定义了一个10个字节的数组
local b[10]:dword ;定义了一个10个双字的数组
local p :dword ;定义了一个双字变量,这里可以把它看成是字节指针,至于怎么会这样,请看下面代码
local p1 :dword ;定义了一个双字变量,这里可以把它看成是双字指针
mov i,0
again:
cmp i,9
mov ebx,i
mov eax,i
ja out_again
mov byte ptr a[ebx],al ;这句就是用下标访问数组了,和下面的比较下,想想为什么
inc i
jmp again ;这一段代码主要是往a数组里面依次存入0,1,2,。。。9
out_again:
mov i,0
again1:
cmp i,9
mov eax,i
mov ebx,i
ja print_arry
mov b[ebx*4],eax ;根据下标i访问数组,和上面那句比较下看看
inc i
jmp again1
out_this_program:
push offset msg2
call printf
add esp,4 ;这里打印回车换行
ret
main endp
end start
但是我们得到了很奇怪的结果,没办法啦,只有调试咯,经调试发现mov bl,byte ptr p 这句访问数组元素根本访问不到,因为ch6_2!main+0x3f:
00401045 8d45f2 lea eax,[ebp-0Eh]
0:000>
eax=0013ffae ebx=0000000a ecx=0013ffb0 edx=7c90e514 esi=00000000 edi=00000000
eip=00401048 esp=0013ff7c ebp=0013ffbc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ch6_2!main+0x42:
00401048 8945c4 mov dword ptr [ebp-3Ch],eax ss:0023:0013ff80=852c3fe0
0:000>
eax=0013ffae ebx=0000000a ecx=0013ffb0 edx=7c90e514 esi=00000000 edi=00000000
eip=0040104b esp=0013ff7c ebp=0013ffbc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
因为a的首地址是0013ffae,当执行这句mov bl,byte ptr p 时,你可以看到调试器是这样的:
00401048 8945c4 mov dword ptr [ebp-3Ch],eax ss:0023:0013ff80=852c3fe0
哈哈,它访问了0013ff80 里面的元素了,唉,怎么办呢?
我想只能用第一个那种寄存器间接寻址方式了,呵呵,又回到第一种访问方式了。
发现第二个错误就是这个程序调用了printf之后,就改变了eax,ecx,ebx的值,反正就是我们这里用到的寄存器都被它改了。然后修改后的程序如下:
.386
.model flat,stdcall
option casemap:NONE
includelib msvcrt.lib
printf proto c:VARARG
.data
msg1 db "%d",0
msg2 db 0ah,0dh,0
.code
start:
call main
ret
main proc
local i:dword ;定义了一个循环变量,就是数组的下标
local a[10]:byte ;定义了一个10个字节的数组
local b[10]:dword ;定义了一个10个双字的数组
local p :dword ;定义了一个双字变量,这里可以把它看成是字节指针,至于怎么会这样,请看下面代码
local p1 :dword ;定义了一个双字变量,这里可以把它看成是双字指针
mov i,0
again:
cmp i,9
mov ebx,i
mov eax,i
ja out_again
mov byte ptr a[ebx],al ;这句就是用下标访问数组了,和下面的比较下,想想为什么
inc i
jmp again ;这一段代码主要是往a数组里面依次存入0,1,2,。。。9
out_again:
mov i,0
again1:
cmp i,9
mov eax,i
mov ebx,i
ja print_arry
mov b[ebx*4],eax ;根据下标i访问数组,和上面那句比较下看看
inc i
jmp again1