研究实验一:搭建一个精简的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 结果如下图