汇编语言 综合研究

来源:互联网 发布:太阳部落景区网络取票 编辑:程序博客网 时间:2024/05/20 19:29

研究实验一:搭建一个精简的C语言开发环

按步骤完成实验结果如下:

 

即 仅仅通过这6个文件就可以实现C语言的编译

 

研究实验二:使用寄存器

再精简C语言开发环境下使用寄存器

编写程序UR1.C

编译生成UR1.EXE 用debug加载

思考:main函数的代码在什么段中?

由于这是一个程序 所以在代码段中。debug下由于不知道该程序段的段地址 所以该程序用下面的方法找到

 

编写程序

 该程序可以打印出main函数在代码段段中的编译地址,为什么?

printf("%x/n",main);

语句为打印语句,main相当于一个指针变量,%x是将该指针的内容以16进制格式打印出来。而指针变量中存着地址,所以上面的程序可以将main函数再代码段中的偏移地址打印出来。结果如下

得到main函数的偏移地址为1fa。

我们即可查看UR1.C在debug下的汇编代码

 


156A:01FA 55            PUSH    BP
156A:01FB 8BEC          MOV     BP,SP
156A:01FD B80100        MOV     AX,0001----------------------_AX=1;
156A:0200 BB0100        MOV     BX,0001----------------------_BX=1;
156A:0203 B90200        MOV     CX,0002----------------------_CX=2;
156A:0206 8BC3          MOV     AX,BX     
156A:0208 03C1          ADD     AX,CX      ----------------------_AX=_BX+_CX;
156A:020A 8AE3          MOV     AH,BL
156A:020C 02E1          ADD     AH,CL      ----------------------_AH=_BL+_CL;
156A:020E 8AC7          MOV     AL,BH
156A:0210 02C5          ADD     AL,CH      ----------------------_AL=_BH+_CH;
156A:0212 5D            POP     BP
156A:0213 C3            RET
156A:0214 C3            RET

 

通过main函数后面有ret指令 我们设想:c语言将函数实现为汇编语言中的子程序。研究下面程序,验证我们的设想。

debug查看

 

验证假设。

另研究:将代码改为

 

void f(void);
void f(void)
{
_AX=_BX+_CX;
}
main()
{
_AX=1;
_BX=1;
_CX=2;
f();
}

 

之后编译后Debug观察结果

 

发现main函数的位置移动到了156a:0203

可以发现编译过程中main指针指向的是程序的开头

并不是程序中main开始的偏移地址

 

研究实验3 使用内存空间

(1)编写程序um1.c

debug查看

 

 

C:/MINIC>debug um1.exe
-u1fa
156A:01FA 55            PUSH    BP
156A:01FB 8BEC          MOV     BP,SP
156A:01FD C606002061    MOV     BYTE PTR [2000],61                                 *(char *)0x2000='a'
156A:0202 C70600200F00  MOV     WORD PTR [2000],000F                         *(int *)0x2000=0xf
156A:0208 BB0020        MOV     BX,2000
156A:020B 8EC3          MOV     ES,BX
156A:020D BB0010        MOV     BX,1000
156A:0210 26            ES:
156A:0211 C60761        MOV     BYTE PTR [BX],61                                         *(char far *)0x20001000='a'
156A:0214 B80020        MOV     AX,2000                                                       _AX=0x2000 
156A:0217 8BD8          MOV     BX,AX
156A:0219 C60762        MOV     BYTE PTR [BX],62                                         *(char *)_AX='b'

156A:021C BB0010        MOV     BX,1000                                                        _BX=0x1000
156A:021F 03DB          ADD     BX,BX
156A:0221 C60761        MOV     BYTE PTR [BX],61                                          *(char *)(_BX+_BX)='a'
156A:0224 8BD8          MOV     BX,AX
156A:0226 8A07          MOV     AL,[BX]
156A:0228 88870010      MOV     [BX+1000],AL                                              *(char far *)(0x20001000+_BX)=*(char *) _AX
156A:022C 5D            POP     BP
156A:022D C3            RET
156A:022E C3            RET

(2)编写一个程序用一条C语句实现在屏幕中间显示一个绿色的字符‘a’

代码如下

 

效果如下

 

(3)分析下面程序中所有额函数的汇编代码

 

debug查看汇编代码

 

 

C:/MINIC>debug um2.exe
-u1fa
156A:01FA 55            PUSH    BP
156A:01FB 8BEC          MOV     BP,SP
156A:01FD 83EC06        SUB     SP,+06
156A:0200 C706A601A100  MOV     WORD PTR [01A6],00A1
156A:0206 C706A801A200  MOV     WORD PTR [01A8],00A2
156A:020C C706AA01A300  MOV     WORD PTR [01AA],00A3
156A:0212 C746FAB100    MOV     WORD PTR [BP-06],00B1
156A:0217 C746FCB200    MOV     WORD PTR [BP-04],00B2
156A:021C C746FEB300    MOV     WORD PTR [BP-02],00B3
156A:0221 8BE5          MOV     SP,BP
156A:0223 5D            POP     BP
156A:0224 C3            RET


156A:0225 55            PUSH    BP
156A:0226 8BEC          MOV     BP,SP
156A:0228 83EC06        SUB     SP,+06
156A:022B C706A601A10F  MOV     WORD PTR [01A6],0FA1

156A:0231 C706A801A20F  MOV     WORD PTR [01A8],0FA2
156A:0237 C706AA01A30F  MOV     WORD PTR [01AA],0FA3
156A:023D C746FAC100    MOV     WORD PTR [BP-06],00C1
156A:0242 C746FCC200    MOV     WORD PTR [BP-04],00C2
156A:0247 C746FEC300    MOV     WORD PTR [BP-02],00C3
156A:024C 8BE5          MOV     SP,BP
156A:024E 5D            POP     BP
156A:024F C3            RET
156A:0250 C3            RET

 

 

对其单步跟踪:

 

 

发现其全局变量存放在DS段中  验证结论。

 

 

验证成功。

 

 

观察局部变量存放地址。继续单步追踪程序。

 

发现其局部变量存在于SS段中,偏移地址以bp为基,   bp为栈顶偏移地址。验证结论   

 

 

假设正确。

 

进一步研究BP SP 的作用  再MAIN函数中添加语句

 

f();

 

 

对于子函数单步跟踪,观察内存使用情况。

 

发现此子函数数据所使用的栈空间地址发生变化。

图a

 

对于局部变量的内存存储地址规律进行进一步的研究 。对于程序再添加一个子函数。

 

void d(void)

{

int d1,d2,d3;

d1=0xd1;d2=0xd2;d3=0xd3;

}

 

并在main函数中调用 观察。在执行完void d(void)之后发现如下结果。

图b

发现当第二个子程序使用完毕后将第一个子程序的变量存储抹去,main函数中变量没有变化。

结论 对于没有返回值的子函数 C语言对其变量不做保存。所使用的占空间相同

 

对于main函数中变量存储规律进行研究,改变main函数

 

 

 

debug下跟踪查看内存。

发现C语言中对于非全局变量均存储与同一个栈中。

研究push bp      mov bp,sp

发现在函数开头的push bp运行完成后 栈中写入的分别为BP(FFE0H) 0f59h    CX(156ah )  IP(01fBh)   BP(ffe4h)

当运行完mov bp,sp后栈空间原存放BP的位置更新为最新BP中数据,IP也更新了。

再执行一步。sub sp,0ch

即发现sp现在所指向的栈位置处开始入栈 0F59H  CS(156AH)  IP(0200H)  BP(FFE4H)

而中间隔出来的空间正好可以存放6个此函数中所需的int型数据

也就是.C在编译之后自动运算出当前函数变量所需的栈空间并在栈底自动push入CS IP BP

当函数放入栈中后调用子函数F()前     栈中状态。

发现对于CS IP BP的入栈是随时入栈的  即执行一步即入一次栈。

执行call 0237h

发现栈段内的内容发生了更新为:

0233H 0F59H AX(156AH)  IP(0237H)  BP(FFE4H)

多出的2字节内容为call指令下一步程序偏移地址Ip

继续单步执行,

 

栈从SS:SP开始变化,BP(由push语句入栈)  0F59H CS IP BP

进入子程序后 查看,

 

发现其遵守前面发现的规则。

执行至RET指令。

 

 

其过程可以看出     

当子函数运行 mov sp,bp执行完毕后 之前子函数变量所用到的栈段进行了释放

当子函数运行 pop bp, 执行完毕后 sp自动+2   将之前存放bp寄存器的栈段也进行了释放

当子函数运行 ret   执行完毕后 sp自动+2 将之前存放call指令下一条指令偏移地址的栈段中的空间也行了释放

 

 

然后进行main函数结束过程

 

当main函数运行 mov sp,bp执行完毕后 之前main函数变量所用到的栈段进行了释放

当main函数运行 pop sp, 执行完毕后 sp自动+2   将之前存放bp寄存器的栈段也进行了释放

当main函数运行 ret   执行完毕后 sp自动+2 将之前存放call指令下一条指令偏移地址的栈段中的空间也行了释放

主函数运行完毕 主函数在栈中的空间也全部释放

此处图片不再上传   可自行跟踪检测。

 

由以上研究可以发现

push bp

mov bp,sp

的作用sp为存储bp cs ip 等各种地址 的栈中起始地址   而SP有无法自我入栈 所以将SP给BP存储   进行执行完每个函数之后释放栈空间用

注:1)sp开始存放各种地址的栈段内容是时刻擦写更新的。

      2)地址入栈为正序入栈, 变量入栈为逆序入栈。        对于变量特殊的入栈方式的用意待研究。

 

(4)分析下边程序的汇编代码。

debug下查看汇编代码

发现在子函数最后将全局变量AB的值给了AX寄存器

而在main函数中发现AX将其存储的数据给了main函数中的变量在栈空间内的存储单元

即返回值通过AX传递 并且只能有一个返回值。

(5)下边的程序向安全的内存空间写入从'a'到'h'的8个字符,理解程序的含义,深入理解相关的知识。(注意:自行学习、研究malloc函数的用法)

 

debug查看汇编代码

 

 

 

C:/MINIC>debug um5.exe
-u1fa
156A:01FA 55            PUSH    BP
156A:01FB 8BEC          MOV     BP,SP
156A:01FD B81400        MOV     AX,0014
156A:0200 50            PUSH    AX
156A:0201 E8D902        CALL    04DD
156A:0204 59            POP     CX

 

 


156A:0205 BB0002        MOV     BX,0200
156A:0208 8EC3          MOV     ES,BX
156A:020A 33DB          XOR     BX,BX                              XOR 寄存器置零
156A:020C 26            ES:
156A:020D 8907          MOV     [BX],AX                           0200:0000=0760H
156A:020F BB0002        MOV     BX,0200
156A:0212 8EC3          MOV     ES,BX
156A:0214 33DB          XOR     BX,BX
156A:0216 26            ES:
156A:0217 8B1F          MOV     BX,[BX]                                                 BX=0760H
156A:0219 C6470A00      MOV     BYTE PTR [BX+0A],00                       DS:076A= 00H(计数器地址)

 

作用为设置计数器初值 该计数器作用

1):计数      8次结束

2):改变写入内存空间的字符a---h    每次递加1

3):改变写入字符的内存空间    每次递加1

 

 

156A:021D EB3C          JMP     025B
156A:021F BB0002        MOV     BX,0200                                             
156A:0222 8EC3          MOV     ES,BX
156A:0224 33DB          XOR     BX,BX
156A:0226 26            ES:
156A:0227 8B1F          MOV     BX,[BX]                                                   ES:0000=0760H                                       
156A:0229 8A470A        MOV     AL,[BX+0A]                                           DS:076A=00H====>AL
156A:022C 0461          ADD     AL,61                                                      AL='a'

 

置字符

                                 计数器+1


156A:022E BB0002        MOV     BX,0200                                                
156A:0231 8EC3          MOV     ES,BX
156A:0233 33DB          XOR     BX,BX
156A:0235 26            ES:
156A:0236 8B1F          MOV     BX,[BX]
156A:0238 50            PUSH    AX
156A:0239 53            PUSH    BX

 

1):将设置的字符入栈(AL)

2):将存放的内存空间的首地址入栈

 


156A:023A BB0002        MOV     BX,0200
156A:023D 8EC3          MOV     ES,BX
156A:023F 33DB          XOR     BX,BX
156A:0241 26            ES:
156A:0242 8B1F          MOV     BX,[BX]
156A:0244 8A470A        MOV     AL,[BX+0A]
156A:0247 98            CBW                                                                        CBW 将al中的值给ax

 

将计数单元的数值放入AX中

 

 


156A:0248 5B            POP     BX
156A:0249 03D8          ADD     BX,AX                                                         置存放字符的地址 
156A:024B 58            POP     AX                                                               
156A:024C 8807          MOV     [BX],AL                                                      将字符放入单元内

 

1):设置每次存放字符的位置

2):将前边设置好的字符放入该位置

 

 


156A:024E BB0002        MOV     BX,0200
156A:0251 8EC3          MOV     ES,BX
156A:0253 33DB          XOR     BX,BX
156A:0255 26            ES:
156A:0256 8B1F          MOV     BX,[BX]
156A:0258 FE470A        INC     BYTE PTR [BX+0A]   

 

计数单元内数据+1

 

156A:025B BB0002        MOV     BX,0200

156A:025E 8EC3          MOV     ES,BX
156A:0260 33DB          XOR     BX,BX
156A:0262 26            ES:
156A:0263 8B1F          MOV     BX,[BX]                                             ES:0000=0760H
156A:0265 807F0A08      CMP     BYTE PTR [BX+0A],08                    DS:076A=00H    CMP    08H
156A:0269 75B4          JNZ     021F

 

while判别函数

 

 

 156A:026B 5D            POP     BP
156A:026C C3            RET
156A:026D C3            RET

 

 

 

注:1):申请的内存空间为 DS:0760开始的20个单元

      2):0200:0000地址所存放的数据位是    malloc函数申请下来的内存空间的首偏移地址    可以看做一个返回值。

      3):第十一个单元为计数单元

      4):写入的地址为 DS:0760-0767    结果如下图