在大约一年前提到过,我想做这样一件事:打通windows上的int 80中断,让原生的linux程序也可以在windows上跑。中间因为公司项目紧,再加上idt的一个小问题困惑了我很久,所以搁置了一段时间。最近觉得周末实在闲的慌又把这项目捡起来了,并且在某次抽烟的时候突然想到“每个处理器都有自己的idt表”这一小常识,idt的问题也就很顺利的解决了。接下来的事情就变得很顺了,按照原定计划先把所有的系统调用转到用户态然后从cygwin1.dll里再过一遍,大约熬了两天夜,一个小小的demo就完成了。
代码参考了很多的开源项目,比如cygwin, glibc, linux kernel, 还有一个死了很久的项目 LINE。这事说起来也好玩,我最初想做这个项目的时候,压根没到sourceforge上去找过,包括实现原理,项目名称等都是自己想出来的。结果做了一半到sourceforge上一搜,居然有个一摸一样想法的东西早在2001年就存在了,连名字都想的一样。然后再到google上一搜,无数人都想到过类似的方案了,而且名字无一例外的打算叫成“LINE”。不得不说在开源的世界里想真正的创点新还真是困难。所幸sourceforge上的这个项目老的不成样了,是2001年的,看代码只能在win98上跑,而且是半成品,连编译都通不过,所以我做的这些事情也不是完全没价值。
不过看我这个项目仍然继承了sourceforge上这个LINE的很多东西,比如把系统调用反射回用户态这事,我原来的打算是插一个APC反射回去,看了下他们的做法,是直接把调用栈上保存的返回地址改成了用户态的一个SyscallHandler,有点类似缓冲区溢出注入的技术,这个方法毫无疑问的更合理更优雅。再比如,绝大多数的系统调用转回到用户态后只是简单的把参数包一包就转cygwin上去了,这部分代码属于纯体力劳动,我就很不客气的也照抄过来了。总之,之前我把这个项目定性为我的“创新”,现在我把它定性为LINE项目的后续开发,虽然少了重新发明轮子的快感,不过坦白我也已经过了追求这种快感的年龄了。
在开发过程中碰到的主要问题有三个:idt的问题,某些较新的linux系统调用没法实现的问题,以及路径问题。我之前设置int 80中断的时候,都是在一个IOCTL里直接就调sidt指令,以为这么做就能改掉系统idt表,实际上由于每个处理器有自己的idt表,这么干只能改IOCTL发起者所在处理器上的idt表,到了运行调试的时候一个int 80中断的发起者到底是不是在该处理器上根本没法保证,所以有很长一段时间里我就处于这么一个状况:设置好idt后一调试,好的;再调试,异常了。搞的我很莫名其妙。后来我在IOCTL处理函数里往每个处理器都插一个DPC,然后在DPC里改idt,这种时好时坏的情况就再也没发生了。
等到用户态的响应函数写的差不多,简单的linux程序比如hello world等都能跑了后,我就开始从原生linux上抓真正的程序过来试。结果一跑问题就来了:linux 2.5.29 及以后的版本新增了几个系统调用set_thread_area, get_thread_area 等等,是为了对NPTL做支持,这几个函数没办法在windows上模拟出来(至少我现在没想到办法),所以我就留空了,直接返回-ENOSYS (事实上190号以后的系统调用我全留空了)。但是现在的glibc还非用它不可,set_thread_area返回-ENOSYS后,glibc就直接打印error退出了。没办法,我只好在虚拟机上装了个redhat 6.0, 然后把那上面的程序和类库考出来用。RH 6.0 那可是史前时代的东西了,不会用到那么高级的东西,应该能跑。
最后还剩一个问题:windows的路径和*nix的路径表示完全不一致,比如linux上的程序想打开/lib/libc.so.6这个文件,在windows上根本就找不到。解决这个问题的方法倒是简单粗暴:我在open等文件操作的系统调用里做了一个小改动,凡是以‘/’打头的路径名,我就把第一个’/’去掉让他变成相对路径,然后再用windows的open函数就能顺利的打开了。直到目前为止这个小技巧还是奏效的,从RH 6.0上拷过来的基本命令都能跑,有图为证:
当然,这东西现在还只是demo而已,问题还不少。比如跑bash还是有点小问题:bash能启动,但是在bash里打ls命令又是文件未找到。再比如只能在32位机器上跑,再比如很不稳定等。总之,后续还有很多工作要做。
源代码目前还没脸放出来,等整理完后会放到google code上,沿用GPL v2。