开学三周,王爽的《汇编语言》(第三版)总算是基本上看完了,本文是总结的第三部分也是最后一部分(大概),主要是书后的综合研究
研究试验1 搭建一个精简的C语言开发环境
TC进行连接需要如下的文件:
- C0S.OBJ
- CS.LIB
- EMU.LIB
- GRAPHICS.LIB
- MATHS.LIB
研究试验2 使用寄存器
- 编一个程序ur1.c 1 
 2
 3
 4
 5
 6
 7
 8
 9main () 
 {
 _AX = 1;
 _BX = 1;
 _CX = 2;
 _AX = _BX + _CX;
 _AH = _BL + _CL;
 _AL = _BH + _CH;
 }
- 用Debug加载ur1.exe,用u命令查看ur1.exe编译后的机器码和汇编代码。
 思考:main函数的代码在什么段中?用Debug怎样找到ur1.exe中main函数的代码?
 回答:main函数的代码在CS段中,需要知道main函数的偏移地址才能找到main函数的代码。
- 用下面的方法打印出ur1.exe被加载运行时,main函数在代码段中的偏移地址。 思考:为什么这个程序能够打印出main函数在代码段中的偏移地址?1 
 2
 3
 4main () 
 {
 printf("%x\n", main);
 }
 回答:由学过的知识可知,C语言中用指针表示地址,这里的printf打印了main函数的入口地址,也即main函数在代码段中的偏移地址。
- 用Debug加载ur1.exe,根据上面打印出的main函数的偏移地址,用u命令察看main函数的汇编代码。仔细找到ur1.c中每条C语句对应的汇编代码。 - 地址 - 汇编语句 - C语句 - 076A:01FA - PUSH BP - ____ - 076A:01FB - MOV BP,SP - ____ - 076A:01FD - MOV AX,0001 - _AX = 1 - 076A:0200 - MOV BX,0001 - _BX = 1 - 076A:0203 - MOV CX,0002 - _CX = 2 - 076A:0206 - MOV AX,BX - ____ - 076A:0208 - ADD AX,CX - _AX = _BX + _CX - 076A:020A - MOV AH,BL - ____ - 076A:020C - ADD AH,CL - _AH = _BL + _CL - 076A:020E - MOV AL,BH - ____ - 076A:0210 - ADD AL,CH - _AL = _BH + _CH - 076A:0212 - POP BP - ____ - 076A:0213 - RET - ____ - 注意:在这里,对于main函数汇编代码开始处的“push bp mov bp,sp”和结尾处的“pop bp”,这里只了解到:这是C编译器安排的为函数中可能使用到bp寄存器而设置的,就可以了。 
- 通过main函数后面有ret指令,我们可以设想:C语言将函数实现为汇编语言中的子程序。研究下面程序的汇编代码,验证我们的设想。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- void f(void); 
 main()
 {
 _AX = 1; _BX = 1; _CX = 2;
 f();
 }
 void f(void)
 {
 _AX = _BX + _CX;
 }- 编译、连接后用Debug查看汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14- PUSH BP 
 MOV BP,SP
 MOV AX,0001
 MOV BX,0001
 MOV CX,0002
 CALL 020B
 POP BP
 RET
 PUSH BP
 MOV BP,SP
 MOV AX,BX
 ADD AX,CX
 POP BP
 RET- 设想正确 
研究试验3 使用内存空间
- 编一个程序um1.c: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- main() 
 {
 *(char *)0x2000='a';
 *(int *)0x2000=0xf;
 *(char far *)0x20001000='a';
 _AX=0x2000;
 *(char *)_AX='b';
 _BX=0x1000;
 *(char *)(_BX+_BX)='a';
 *(char far *)(0x20001000+_BX)=*(char *)_AX;
 }- 把um1.c保存在C:\MINIC下,编译,连接生成um1.exe。然后用Debug加载um1.exe,对main函数的汇编代码进行分析,找到每条C语句对应的汇编代码;对main函数进行单步跟踪,察看相关内存单元的内容。 - 地址 - 汇编语句 - C语句 - 相关内存单元 - 076A:01FA - PUSH BP - ____ - ____ - 076A:01FB - MOV BP,SP - ____ - ____ - 076A:01FD - MOV BYTE PTR [2000],61 - *(char *)0x2000=’a’ - 07C4:2000 -> 61 - 076A:0202 - MOV WORD PTR [2000],000F - *(int *)0x2000=0xf - 07C4:2000-07C4:2001 -> 0F 00 - 076A:0208 - MOV BX,2000 - ____ - ____ - 076A:020B - MOV ES,BX - ____ - ____ - 076A:020D - MOV BX,1000 - ____ - ____ - 076A:0210 - ES: - 076A:0211 - MOV BYTE PTR [BX],61 - *(char far *)0x20001000=’a’ - 2000:1000 -> 61 - 076A:0214 - MOV AX,2000 - _AX=0x2000 - ____ - 076A:0217 - MOV BX,AX - ____ - ____ - 076A:0219 - MOV BYTE PTR [BX],62 - *(char *)_AX=’b’ - 07C4:2000 -> 62 - 076A:021C - MOV BX,1000 - _BX=0x1000 - ____ - 076A:021F - ADD BX,BX - ____ - ____ - 076A:0221 - MOV BYTE PTR [BX],61 - *(char *)(_BX + _BX)=’a’ - 07C4:2000 -> 61 - 076A:0224 - MOV BX,AX - ____ - ____ - 076A:0226 - MOV AL,[BX] - ____ - ____ - 076A:0228 - XOR CX,CX - ____ - ____ - 076A:022A - ADD BX,1000 - ____ - ____ - 076A:022E - ADC CX,2000 - ____ - ____ - 076A:0232 - MOV ES,CX - ____ - ____ - 076A:0234 - ES: - 076A:0235 - MOV [BX],AL - Ž(char far *)(0x20001000+BX)=*(char *)AX - 2000:3000 -> 61 - 076A:0237 - POP BP - ____ - ____ - 076A:0238 - RET - ____ - ____ 
- 编一个程序,用一条C语句实现在屏幕的中间显示一个绿色的字符”a”。 - 1 
 2
 3
 4- main() 
 {
 *(int far *)0xb80007d0=0x261
 }
- 分析下面程序中所有函数的汇编代码,思考相关的问题。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14- int a1, a2, a3; 
 void f(void);
 main()
 {
 int b1, b2, b3;
 a1 = 0xa1; a2 = 0xa2; a3 = 0xa3;
 b1 = 0xb1; b2 = 0xb2; b3 = 0xb3;
 }
 void f(void)
 {
 int c1, c2, c3;
 a1 = 0x0fa1; a2 = 0x0fa2; a3 = 0x0fa3;
 c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
 }- 问题:C语言将全局变量存放在哪里?将局部变量存放在哪里?每个函数开头的“push bp mov bp,sp”有何含义? 
 程序中main函数的汇编代码如下:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- PUSH BP 
 MOV BP, SP
 SUB SP, 6
 MOV WORD PTR [01A6], 00A1
 MOV WORD PTR [01A8], 00A2
 MOV WORD PTR [01AA], 00A3
 MOV WORD PTR [BP-6], 00B1
 MOV WORD PTR [BP-4], 00B2
 MOV WORD PTR [BP-2], 00B3
 MOV SP, BP
 POP BP
 RET- 程序中f函数的汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- PUSH BP 
 MOV BP, SP
 SUB SP, 6
 MOV WORD PTR [01A6], 0FA1
 MOV WORD PTR [01A8], 0FA2
 MOV WORD PTR [01AA], 0FA3
 MOV WORD PTR [BP-6], 00C1
 MOV WORD PTR [BP-4], 00C2
 MOV WORD PTR [BP-2], 00C3
 MOV SP, BP
 POP BP
 RET- 答案: 
 C语言将全局变量存放在内存中,将局部变量存放在栈中,每个函数开头的push bp以及mov bp,sp是为了保护与还原现场。
- 分析下面程序的汇编代码,思考相关的问题。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- int f(void); 
 int a,b,ab;
 main()
 {
 int c;
 c = f();
 }
 int f(void)
 {
 ab=a+b;
 return ab;
 }- 问题:C语言将函数的返回值存放在哪里? 
 程序中main函数的汇编代码如下:- 1 
 2
 3
 4
 5
 6
 7
 8- PUSH BP 
 MOV BP, SP
 SUB SP, 2
 CALL 020A
 MOV [BP-2], AX
 MOV SP, BP
 POP BP
 RET- 程序中f函数的汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9- PUSH BP 
 MOV BP, SP
 MOV AX, [01A6]
 ADD AX, [01A8]
 MOV [01AA], AX
 MOV AX, [01AA]
 JMP 021C
 POP BP
 RET- 答案: 
 在该情况下,C语言将返回值存放在通用寄存器AX中。
- 下面的程序向安全的内存空间写入从“a”到“h”8个字符,理解程序的含义,深入理解相关的知识。(注意:请自己学习、研究malloc函数的用法) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 main()
 {
 Buffer = (char *)malloc(20);
 Buffer[10] = 0;
 while(Buffer[10]!=8)
 {
 Buffer[Buffer[10]]='a'+Buffer[10];
 Buffer[10]++;
 }
 }- 理解: 
 程序通过malloc分配大小为20字节的空间给并将其地址存入Buffer所指向的空间即0200:0000中。随后,通过一个while循环将这20个字节的空间的0-7个字节分别赋予对应的字符,并且用第10个字节计数。
研究试验4 不用main函数编程
- 把程序F.C保存在C:\MINIC下,对其进行编译,连接,思考相关的问题。 
 问题:
 ①编译和连接哪个环节会出问题?
 ②显示出的错误信息是什么?
 ③这个错误信息可能和哪个文件有关?
 答案:
 ①连接环节出现了错误
 ②在C0S模块中未定义的标号_main
 ③和C0S.OBJ这个文件有关
- 用学习汇编语言时使用的LINK.EXE对TC.EXE生成的F.OBJ文件进行连接,生成F.EXE。用DEBUG加载F.EXE,察看整个程序的汇编代码。思考相关的问题。 
 问题:
 ①F.EXE的程序代码总共有多少字节?
 ②F.EXE的程序能正确返回吗?
 ③F函数的偏移地址是多少?
 答案:
 ①F.EXE的程序代码总共有1DH个字节
 ②F.EXE的程序不能正确的返回
 ③F函数的偏移地址为0
- 写一个程序M.C。 - 1 
 2
 3
 4
 5- main () 
 {
 *(char far *)(0xb8000000+160*10+80)='a';
 *(char far *)(0xb8000000+160*10+81)=2;
 }- 用TC.EXE对M.C进行编译,连接,生成M.EXE,用Debug察看M.EXE整个程序的汇编代码。思考相关的问题。 
 问题:
 (1)M.EXE的程序代码总共有多少字节?
 (2)M.EXE的程序能正确返回吗?
 (3)M.EXE程序中的main函数和F.EXE中的f函数的汇编代码有何不同?
 答案:
 (1)M.EXE的程序代码总共有5F5H个字节
 (2)M.EXE的程序能正确返回
 (3)M.EXE程序中的main函数和F.EXE中的f函数的汇编代码没有不同
- 用Debug对m.exe进行跟踪: 
 (1)找到对main函数进行调用的指令的地址
 (2)找到整个程序返回的指令
 注意:使用g命令和p命令。
 答案:
 (1)对main函数进行调用的指令的地址为076A:011A CALL 01FA
 (2)整个程序返回的指令为076A:0156 INT 21
- 思考如下几个问题: 
 (1)对main函数调用的指令和程序返回的指令是哪里来的?
 (2)没有main函数时,出现的错误信息里有和“C0S”相关的信息;而前面在搭建开发环境时,没有C0S.OBJ文件TC.EXE就无法对程序进行连接。是不是TC.EXE把C0S.OBJ和用户程序的.OBJ一起进行连接生成.EXE文件?
 (3)对用户程序的main函数进行调用的指令和程序返回的指令是否就来自C0S.OBJ文件?
 (4)我们如何看到C0S.OBJ文件中的程序代码呢?
 (5)C0S.OBJ文件里有我们设想的代码吗?
 回答:
 (1)对main函数调用的指令和程序返回的指令是来自于其他的文件而非编译后的文件。
 (2)是的
 (3)是的
 (4)用LINK.EXE对其进行连接即可
 (5)有
- 用LINK.EXE对C:\MINIC目录下的C0S.OBJ进行连接,生成C0S.EXE。 
 用Debug分别察看C0S.EXE和M.EXE的汇编代码。注意:从头开始察看,两个文件中的程序代码有和相同之处?
 两个程序的代码基本相同,且都是在011A调用了CALL指令,在0156调用了INT 21中断
- 用Debug找到M.EXE中调用main函数的CALL指令的偏移地址,从这个偏移地址开始向后察看10条指令;然后用Debug加载C0S.EXE,从相同的偏移地址开始向后察看10条指令,对两处的指令进行对比。 
 M.EXE和C0S.EXE在偏移地址011A之后的10条指令除了跳转指令的跳转地址有所不同外几乎完全相同。
- 下面,我们用汇编语言编一个程序C0S.ASM,然后把它编译为C0S.OBJ,替代C:\MINIC目录下的C0S.OBJ。 
 程序C0S.ASM:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- cs:code 
 data
 db 128 dup (0)
 data ends
 code
 start: mov ax, data
 mov ds, ax
 mov ss, ax
 mov sp, 108
 call s
 mov ax, 4c00h
 int 21h
 s:
 code ends
 end start
- 在C:\MINIC目录下,用TC.EXE将F.C重新进行编译,连接,生成F.EXE。这次能通过连接吗?F.EXE可以正确运行吗?用Debug察看F.EXE的汇编代码。 
 能通过连接,可以正确运行,汇编代码如下:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- MOV AX,076A 
 MOV DS,AX
 MOV SS,AX
 MOV SP,0080
 CALL 0012
 MOV AX,4C00
 INT 21
 MOV BP,SP
 MOV BX,B800
 MOV ES,BX
 MOV BX,0690
 ES:
 MOV BYTE PTR [BX],61
 MOV BX,B800
 MOV ES,BX
 MOV BX,0691
 ES:
 MOV BYTE PTR [BX],02
 POP BP
 RET
 ...
- 在新的C0S.OBJ的基础上,写一个新的F.C,向安全的内存空间写入从“a”到“h”的8个字符,分析、理解F.C。 
 程序F.C:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 f()
 {
 Buffer = 0;
 Buffer[10] = 0;
 while(Buffer[10]!=8)
 {
 Buffer[Buffer[10]]='a'+Buffer[10];
 Buffer[10]++;
 }
 }- 汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60- MOV AX,076A 
 MOV DS,AX
 MOV SS,AX
 MOV SP,0080
 CALL 0012 ;调用f函数
 INT 21
 PUSH BP ;0012
 MOV BP,SP
 MOV BX,0200
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV WORD PTR [BX],0000 ;设置Buffer=0,即将0200:0000处的内存单元改为0
 MOV BX,0200
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
 MOV BYTE PTR [BX+0A],00 ;设置Buffer[10]=0 即将DS:000A处的内存单元改为0
 JMP 006D ;while 循环条件判断
 MOV BX,0200 ;0031
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
 MOV AL,[BX+0A] ;AL等于Buffer[10],即将DS:000A内存字节单元的内容存入AL中
 ADD AL,61 ;为AL加上'a'
 MOV BX,0200
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
 PUSH AX
 PUSH BX
 MOV BX,0200
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
 MOV AL,[BX+0A] ;将Buffer[10]的值传入AL,即将DS:000A处的内存字节单元存入AL中
 CBW ;将AL扩展至16位
 POP BX ;取回BX
 ADD BX,AX ;BX现在值为Buffer[10]
 POP AX ;取回AX
 MOV [BX],AL ;将所要设置的数值'a'+Buffer[10]存入Buffer[Buffer[10]]中
 MOV BX,0200
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX] ;将Buffer解引用,即将0200:0000处的内存字单元存入BX中
 INC BYTE PTR [BX+0A] ;将Buffer[10]自增
 MOV BX,0200 ;006D
 MOV ES,BX
 XOR BX,BX
 ES:
 MOV BX,[BX]
 CMP BYTE PTR [BX+0A],08 ;判断Buffer[10]是否等于8
 JNZ 0031 ;不等于则继续
 POP BP ;等于则返回
 RET
研究试验5 函数如何接受不定数量的参数
用C:\MINIC下的TC.EXE完成下面的试验。
- 写一个程序A.C: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- void showchar(char a, int b); 
 main()
 {
 showchar('a', 2);
 }
 void showchar(char a, int b)
 {
 *(char far *)(0xb8000000+160*10+80)=a;
 *(char far *)(0xb8000000+160*10+81)=b;
 }- 用TC.EXE对A.C进行编译,连接,生成A.EXE。用Debug加载A.EXE,对函数的汇编代码进行分析。解答这两个问题:main函数是如何给showchar传递参数的?showchar是如何接受参数的? 
 main函数的汇编代码如下:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- PUSH BP 
 MOV BP,SP
 MOV AX,0002
 PUSH AX
 MOV AL,61
 PUSH AX
 CALL 020B ;调用showchar函数
 POP CX
 POP CX
 POP BP
 RET- showchar函数的汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- PUSH BP 
 MOV BP,SP
 MOV AL,[BP+4]
 MOV BX,B800
 MOV ES,BX
 MOV BX,0690
 ES:
 MOV [BX],AL ;f函数的第1条语句
 MOV AL,[BP+6]
 MOV BX,B800
 MOV ES,BX
 MOV BX,0691
 ES:
 MOV [BX],AL ;f函数的第2条语句
 POP BP
 RET- 答案: 
 main函数通过将对应的参数压栈来给showchar传递参数,并且在函数返回后通过POP操作将参数退栈。
 showchar通过SS:BP以及寻址在栈中取得对应的参数。
- 写一个程序B.C: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18- void showchar(int,int,...); 
 main()
 {
 showchar(8,2,'a','b','c','d','e','f','g','f');
 }
 void showchar(int n,int color,...)
 {
 int a;
 for(a=0;a!=n;a++)
 {
 *(char far *)(0xb8000000+160*10+80+a+a)=*(int *)(_BP+8+a+a);//加8是因~为call的时候将CS、IP压栈
 *(char far *)(0xb8000000+160*10+81+a+a)=color;
 }
 }- 分析程序B.C,深入理解相关的知识。 
 思考:showchar函数是如何知道要显示多少个字符的?printf函数是如何知道有多少个参数的?
 待显示字符的个数为showchar函数的第一个参数,showchar函数以此得知要显示的字符的个数。
 通过对printf的一个参数所指向的字符串的分析,printf函数以此得知参数的个数。
- 实现一个简单的printf函数,只需支持“%c %d”即可。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70- void myprintf(char *, ...); 
 main()
 {
 myprintf("%d %c abc %%\n", -12345, 48);
 }
 void myprintf(char * str, ...)
 {
 int count = 0;
 int position = 0;
 while (*str != 0)
 {
 if (*str == 37)
 {
 str++;
 if (*str == 68 || *str == 100)
 {
 int index;
 char flag = 0;
 int length = 0;
 int num = *(int *)(_BP + 6 + count);
 if (num < 0) {
 flag = 1;
 num *= -1;
 }
 while (num != 0)
 {
 num = num / 10;
 length++;
 }
 num = *(int *)(_BP + 6 + count);
 if (flag == 1) num *= -1;
 position += length + flag - 1;
 for (index = 0 ; index < length; ++index)
 {
 *(char far *)(0xb8000000 + 1600 + position * 2) = num % 10 + 48;
 num = num / 10;
 position--;
 }
 if (flag == 1)
 {
 *(char far *)(0xb8000000 + 1600 + position * 2) = '-';
 position--;
 }
 position += length + flag + 1;
 count += 2;
 str++;
 }
 else if (*str == 67 || *str == 99)
 {
 *(char far *)(0xb8000000 + 1600 + position * 2) = *(char *)(_BP + 6 + count);
 count++;
 position++;
 str++;
 }
 else
 {
 *(char far *)(0xb8000000 + 1600 + position * 2) = '%';
 position++;
 }
 }
 else
 {
 *(char far *)(0xb8000000 + 1600 + position * 2) = *str;
 position++;
 str++;
 }
 }
 }
