这一部分继续深入研究用汇编解释c的原理,尝试不用main()函数编写可以正确运行的程序
1、首先编写一个简单的程序
f() { *(char far *)(0xb8000000+160*10+80) = 'a'; *(char far *)(0xb8000000+160*10+81) = 2; }
在TC中编译,连接这个程序。出现了一个连接错误
Linker Error:Undefined symbol ‘_main’in module C0S(未定义的符号_main在模块C0S中)
这句话说明,c语言的入口函数main函数是被C0S.obj所调用。
现在我们用汇编语言所使用的连接器Link.exe来连接c语言编译后的.obj文件得到一个.exe文件,用debug调试这个.exe文件
从反编译后的指令看到,偏移地址为0开始的程序就是我们编写的程序。在资源管理器中看到,这个文件只有541个字节。但是执行后不能正确返回
2、正常的程序
下面编写一个正常的程序
main() { *(char far *)(0xb8000000+160*10+80) = 'a'; *(char far *)(0xb8000000+160*10+81) = 2; }
用TC环境编译连接这个程序,执行后屏幕*显示一个绿色的a,并且正确返回。用Debug分析
与上一个不能正确返回的程序比较,这个程序中我们编写的程序位于偏移地址为01FA的位置,这个程序有4.17k字节。但是我们编写的部分两个程序是完全一样的。
继续深入探究,从0偏移地址开始反汇编上面的程序,找到CALL 0aFA 这样的指令
如图,C0S模块在这里调用了main函数
继续跟踪执行的过程,可以找到函数返回的代码处
这就找到了调用我们程序的指令和返回操作系统的指令,但是这些程序是哪儿来的呢。刚才连接错误指向了C0S.obj,猜想是连接器将C0S和我们的程序编译后的.obj文件连接到一起,也就是调用main函数的指令来自于c0s.obj文件
3、分析C0S.obj文件
用Link.exe连接c0s.obj文件,出现几个错误,不用理它。用debug调试生成的c0s.exe文件
观察上边两个程序的代码,发现两个程序中代码非常相似
继续在两个程序中查找main的入口函数
如图,在m.exe中,call 01fa调用了main函数;在c0s.exe中,相同位置也有一次调用,但是因为没有main函数,连接器找不到main的位置,直接指向了下一条指令
4、生成.exe程序的过程
从上文分析和探索可以看出,tc.exe将c0s.obj和用户.obj一同连接,生成.exe 。所生成的exe程序运行过程如下:
①c0s.obj里的程序先运行,进行相关的初始化。如申请资源,设置ds,ss等相关寄存器
②c0s.obj里的程序调用main函数,从此用户程序开始运行
③用户程序运行结束从main程序返回到c0s.obj的程序中
④c0s.obj的程序接着运行,进行相关的资源释放,环境恢复的工作
⑤c0s.obj的程序调用DOS的int 21h例程的4ch号中断功能,程序返回
由以上资料,c开发系统提供了c程序所必须的初始化和程序返回等相关程序,这些程序放在相关.obj文件中,这些程序和用户写的程序编译后的.obj文件进行连接,用户程序才可正确运行。运行时由c0s.obj文件中的程序调用main函数来运行用户程序
通过这些分析,我们只要重写一个c0s.obj文件即可不用main函数编程
5、不调用用main函数的c0s.obj
编写这样一个汇编程序c0s.asm
assume cs:code
data segment
db 128 dup (0)
data endscode segment
start:
mov ax,data
mov ds,ax
mov ss,ax
mov sp,128
call smov ax,4c00h
int 21hs:
code endsend start
将这个程序编译成c0s.obj文件,然后替代TC中的c0s.obj
再次连接程序,程序连接成功。运行后成功返回。
用Debug调试这个程序并反汇编
发现编写的用户程序(f程序)是作为一个子程序出现在程序中的,这个程序被c0s.obj中的指令所调用,调用完成后返回操作系统。
这个程序模拟了c0s.obj的主要功能
6、继续深入探究
编写程序
#define Buffer ((char *)*(int far *)0x02000000) f() { Buffer = 0; Buffer[10] = 0; while (Buffer[10] != 8) { Buffer[Buffer[10]] = 'a'+Buffer[10]; Buffer[10]++; } }
这个程序与上一节中程序类似,所不同的是安全空间不是从内存空间中用malloc()来申请,而是直接赋值成为了0,也就是指向内存区域的指针为ds:0。其它详细部分参考使用内存空间的综合研究
验证这个猜想,可以运行程序到返回之前,查看ds段是否写入了a-h,如图
如图,程序确实写入到了ds段