用一个简单的程序来研究如何用tc.exe来对这个程序进行编译、连接,生成正确的可以运行的程序。
f()
{
*(char far *)(0xb8000000+160*12+80)='a';
*(char far *)(0xb8000000+160*12+81)=2;
}
将此函数保存在最简开发环境下,会发现:编译无错误,连接时提示错误并显示与C0S.obj有关
用masm开发环境下的link.exe进行连接,生成.exe文件,并debug程序,-r命令查看,可见CX值为0x001d(此表示程序占用1d个字节),且f的偏移地址为0;
将f换成main,用tc.exe重新编译连接,生成m.exe,并debug程序,同理用-r命令查看,可见CX值为0x0eb8(此表示程序占用eb8个字节),且main的偏移地址为1fa;
熟练使用debug的操作命令,从头开始跟踪,查看从什么时候开始调用了1fa偏移地址的main函数(注意用-p和-g命令),
耐心一点,很快就会发现的。
可以发现:没有main函数时,出现的错误信息里有和“c0s”相关的信息;而前面在搭建开发环境时,没有c0s.obj文件tc.exe就无法对程序进行连接。
由此可以知道
对main函数调用的指令和程序返回的指令是从c0s.obj来的;tc.exe把c0s.obj和用户程序的.obj文件一起进行连接生成.exe文件;
利用masm中的link.exe可以使c0s.obj生成c0s.exe,对比m.exe并从头查看,可以发现到了一定代码,m.exe直接调用main函数,而c0s.exe调用了一个紧接着的地址。
总结c0s.obj的工作过程:
①c0s.obj里的程序先运行,进行相关的初始化,比如,申请资源、设置DS、SS等寄存器;
②c0s.obj里的程序调用main函数,从此用户程序开始运行;
③用户程序从main函数返回到c0s.obj的程序中;
④c0s.obj的程序接着运行,进行相关的资源释放,环境恢复等工作;
⑤c0s.obj的程序调用DOS的int 21h例程的4ch号功能,程序返回。
C程序必须从main函数开始,是C语言的规定,这个规定不是在编译时保证的,因为tc.exe对f.c的编译是通过的,也不是连接的时候保证的,因为用link.exe可以使文件生成f.exe.
如下机制保证了程序必须从main函数开始。
C语言系统提供了用户写的应用程序正确运行所必需的初始化的程序返回等相关程序,这些程序放在相关的.obj文件(比如,c0s.obj)中
需要将这些文件和用户的.obj文件一起进行连接,才能生成可正确运行的.exe文件。
连接在用户.obj文件前面的由C语言开发系统提供的.obj文件里的程序要对main函数进行调用。
基于这种机制,只要修改c0s.obj,让它调用其他函数,编程时就可以不写main函数了。
我们用汇编语言编写一个程序c0s.asm,然后把它编译成为c0s.obj,替代最简目录下的c0s.obj.
程序如下
assume cs:code
data segment
db 128 dup(0)
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ss,ax
mov sp,128
call s
mov ax,4c00h
int 21h
s:
code ends
end start
重新对f.c进行编译连接,此时编译连接能通过,并且f.exe可以正确运行。但是请注意,如果此时用新的c0s.obj进行编译连接,而编写的.c文件中既有main函数又有
其他的函数,c0s只是默认连接第一个函数的。
知道了调用main函数的原理,也就明白了32程序的函数入口winmain的原理...