惶恐中放上小弟的第一篇分析作品,水平有限,文笔不济,希望各位见谅并提出意见和建议
一、启动之前
VS的便捷同时也掩盖了一个操作系统从源代码转换到二进制文件的中间流程,所以首先先从源代码编译下手,先弄清楚VS是怎么把C#源代码编译成可引导启动的二进制代码。
本人使用的源代码包为cosmos-72205.zip
对于VS需要额外安装:VS2010 SDK
二、MSBuild
编译过LINUX的朋友应该都熟悉Make文件吧,在这里面可以清楚的看到使用编译器把C源代码编译成二进制源代码的流程,中间干了什么都可以看得清清楚楚。但使用VS的朋友一般都是程序写好之后直接F5就运行了,后面做了什么我们根本不知道。对于COSMOS的源代码,VS只是帮我们编译成了IL的代码,这种代码无法在没有CLR环境中运行,所以VS还需要把IL代码编译为针对CPU的本地代码。这中间的流程都由MSBuild这个东西控制。对于MSBuild的介绍可以直接查看MSDN文档。总的来说这个东西的地位就相当于Make文件的地位吧,指导VS如何生成可以引导的二进制文件。
三、编译Cosmos
首先先运行解压出来的文件夹里的Build/VSIP/install.bat文件,这个好像是用安装Cosmos的编译环境的。安装完成,直接双击Source/cosmos.sln文件(在这之前需要先安装VS2010 SDK)即可浏览全部源代码文件(激动~~~~选择COSMOS的主要原因)。打开之后得先改一下启动项目,在次修改成Breakpoints这个项目
四、Cosmos的编译过程
我们可以看到,工程里面设为启动项的不是常规的C#项目,而已Cosmos项目,该项目在运行Build/VSIP/install.bat时安装到VS2010中,编译的时候会调用该项目自己的MSBuild文件来控制其编译过程,可以在C:\Program Files\MSBuild\Cosmos该目录下找到一个Cosmos.target文件,用写字板打开,找到如下内容:
<UsingTask TaskName="Cosmos.Build.MSBuild.IL2CPU" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
<UsingTask TaskName="Cosmos.Build.MSBuild.NAsm" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
<UsingTask TaskName="Cosmos.Build.MSBuild.MakeISO" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
<UsingTask TaskName="Cosmos.Build.MSBuild.Ld" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
<UsingTask TaskName="Cosmos.Build.MSBuild.ReadNAsmMapToCosmosMap" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
<UsingTask TaskName="Cosmos.Build.MSBuild.ExtractMapFromElfFile" AssemblyFile="$(VSIPDir)\Cosmos.Build.MSBuild.dll" />
个人理解,Cosmos项目编译时应该会加载这些dll进内存,在Cosmos项目源代码中到对应的项目,可以发现它们都直接或者间接继承自AppDomainIsolatedTask类,查看MSDN,这个继承该类的类都重写了execute方法,MSBuild在加载这些程序集时应该是会自动运行这些程序集中继承自AppDomainIsolatedTask的类重写的execute方法(运行的顺序似乎并不是按照上面的书写顺序),那么接下来我们只要再分析每一个execute方法,就可以明白这个C#源代码程序是如何编译成为本地CPU二进制格式的文件了(以上各个类运行时需要的参数可以在该文件的接下来的部分找到,如下:
<IL2CPU DebugMode="$(DebugMode)"
TraceAssemblies="$(TraceAssemblies)"
DebugCom="1"
UseNAsm="true"
References="@(ReferencePath)"
OutputFilename="$(TargetDir)$(MSBuildProjectName).asm"
EnableLogging="true"
EmitDebugSymbols="$(DebugSymbols)"/>
<NAsm InputFile="$(TargetDir)$(MSBuildProjectName).asm"
OutputFile="$(TargetDir)$(MSBuildProjectName).obj"
IsELF="$(IsELF)"
ExePath="$(NasmFile)" />
<!--ELF only-->
<Ld CosmosBuildDir="$(CosmosDir)\Build"
WorkingDir="$(TargetDir)"
Arguments="-Ttext 0x500000 -Tdata 0x200000 -e Kernel_Start -o '$(TargetDir)$(MSBuildProjectName).bin' '$(TargetDir)$(MSBuildProjectName).obj'"
Condition="$(IsELF) == 'true'"/>
<Delete Files="$(TargetDir)$(MSBuildProjectName).obj" Condition="$(IsELF) == 'true'"/>
<ExtractMapFromElfFile InputFile="$(TargetDir)$(MSBuildProjectName).bin"
DebugInfoFile="$(TargetDir)$(MSBuildProjectName).cpdb"
WorkingDir="$(TargetDir)"
CosmosBuildDir="$(CosmosDir)\Build"
Condition="$(IsELF) == 'true'"/>
<CreateItem Include="$(TargetDir)$(MSBuildProjectName).bin" Condition="$(IsELF) == 'true'">
<Output TaskParameter="Include"
ItemName="TempFilesToCopy"/>
</CreateItem>
<Copy SourceFiles="@(TempFilesToCopy)"
DestinationFiles="@(TempFilesToCopy->'$(TargetDir)\%(Filename).obj')"
Condition="$(IsELF) == 'true'"/>
<Delete Files="$(TargetDir)$(MSBuildProjectName).bin" Condition="$(IsELF) == 'true'"/>
<!--End of ELF only-->
<!--binary only-->
<ReadNAsmMapToCosmosMap InputBaseDir="$(TargetDir)"
DebugInfoFile="$(TargetDir)$(MSBuildProjectName).cpdb"
Condition="$(IsELF) == 'false'"/>
<!--end of binary only-->
<!--todo: update cxdb to cxdbg-->
<MakeISO InputFile="$(TargetDir)$(MSBuildProjectName).obj"
OutputFile="$(TargetDir)$(MSBuildProjectName).iso"
CosmosBuildDir="$(CosmosDir)\Build" />)。
4.1先从Cosmos.Build.MSBuild.IL2CPU开始。
Cosmos.Build.MSBuild.IL2CPU继承自AppDomainIsolatedTask类,并重写了execute方法,在execute方法中调用实例化的IL2CPUTask的类的execute方法。而IL2CPUTask类的execute方法则负责获取程序的入口点,读取所有的程序集文件。对于小弟的这个水平来说,由于IL2CPU编译器过于复杂,以下分析请大家以批判的角度来阅读:读取程序集文件后,应该是调用IL2CPU编译器来把IL代码转换成本地CPU的汇编代码文件.asm和支持调试用的符号文件.cpdb。到此Cosmos.Build.MSBuild.IL2CPU的任务就算完成了。接下来的事情交由Cosmos.Build.MSBuild.Nasm处理。
4.2 Cosmos.Build.MSBuild.Nasm
Cosmos.Build.MSBuild.Nasm直接继承自Cosmos.Build.MSBuild.BaseToolTask,而BaseToolTask又直接继承自AppDomainIsolatedTask,查看他们的execute方法内的代码,可以知道Nasm这个类应该是使用Nasm.exe(一个跨平台的汇编开发工具)这个工具把上一步转换出来的.asm文件编译成elf格式的(关于elf格式可以参看小弟空间里的另一篇文章http://www.cnblogs.com/li0803/archive/2010/11/15/1877961.html)目标文件.obj,到此Cosmos.Build.MSBuild.Nasm这个类的工作也结束了,接下来由Cosmos.Build.MSBuild.MakeISO这个类来处理
4.3 Cosmos.Build.MSBuild.MakeISO
Cosmos.Build.MSBuild.MakeISO与Nasm类似,直接继承自BaseToolTask,间接继承自AppDomainIsolatedTask,并重写了自己的execute方法,作用是使用mkisofs.exe这个工具把上一步生成的.obj文件链接成一个ISO文件,以便虚拟机可以加载运行。其实到这一步已经得到了一个可以运行的COSMOS操作系统文件了,但得到的只是一个ISO文件,只能再虚拟机种运行,无法安装到实际的机子上,也不方便进行调试,所以COSMOS还要MSBuild做了一下的事情来生成一个.bin的二进制文件和一些支持在VS下对操作系统的源代码进行调试的.cpdb符号表文件。不知道这么说对不对,大家就仅当参考吧,不对的话也希望大家可以帮忙指正一下,呵呵:)
4.4 Cosmos.Build.MSBuild.Ld
Cosmos.Build.MSBuild.Ld也与Nasm类似,直接继承自BaseToolTask,间接继承自AppDomainIsolatedTask,并重写了自己的execute方法,作用是把Nasm类中编译出来的.obj目标文件使用ld.exe工具链接成一个独立的ELF格式的本地CPU可运行的二进制文件(有关编译和链接的只是可以参考《程序员的自我修养》),然后再交由Cosmos.Build.MSBuild.ExtractMapFromElfFile继续处理
4.5 Cosmos.Build.MSBuild.ExtractMapFromElfFile
Cosmos.Build.MSBuild.ExtractMapFromElfFile同样是直接继承自BaseToolTask,间接继承自AppDomainIsolatedTask,并重写了自己的execute方法,在execute方法中调用objdump.exe来处理上一步生成的文件。小弟也是第一次接触objdump.exe这个东西,从源代码上看应该是分析上一步编译出来的.bin文件,然后生成支持调试用的.cpdb符号表文件吧,接着再交由Cosmos.Build.MSBuild.ReadNAsmMapToCosmosMap做最后的处理。
4.6 Cosmos.Build.MSBuild.ReadNAsmMapToCosmosMap
Cosmos.Build.MSBuild.ReadNAsmMapToCosmosMap类直接继承自AppDomainIsolatedTask类,并重写自己的execute方法,作用是从Nasm任务中生成的.obj文件中读取出地址符号映射表(什么东西?不太懂),写入自带的firebird数据库中,便于源代码跟踪调试用。
到此,COSMOS的编译工作就算是全部完成了