随笔-80  评论-24  文章-0  trackbacks-0
      开发WinixJ的第一步工作我打算模仿Linux。
      Linux是先将bootsect、setup、head、system等文件压缩成一个IMAGE文件,然后再dd进软盘,之后bootsect开始加载过程。我打算采用这种方法,因为这种方法可以省去将软盘做成某种格式然后用晦涩的汇编在boot中寻找loader、kernel加载。不过不同的地方也很明显:Linux0.11处理的system文件是a.out格式的二进制文件,这种文件格式非常简单,因此相对容易加载进内存;但是WinixJ在Linux3.0.0.12下开发,此时的GCC已经不支持a.out格式的输出,因此我只能将WinixJ内核编译为ELF格式的二进制文件,而ELF格式的文件相对a.out要复杂的多,因此我们开发了proc_kernel.c文件专门处理ELF格式文件,该程序产生kernel.map输出文件。
先把代码贴出:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <unistd.h>
  5 #include <sys/stat.h>
  6 
  7 typedef    unsigned int    Elf32_Addr;
  8 typedef    unsigned short    Elf32_Half;
  9 typedef unsigned int    Elf32_Off;
 10 typedef unsigned int    Elf32_Sword;
 11 typedef unsigned int    Elf32_Word;
 12 
 13 #define EI_NIDENT    16
 14 #define MAX_BUF_LEN    1024
 15 
 16 //ELF文件的ELF header结构
 17 typedef struct
 18 {
 19     unsigned char    e_ident[EI_NIDENT]; //ELF魔数
 20     Elf32_Half        e_type; //文件类型
 21     Elf32_Half        e_machine; //支持的机器架构
 22     Elf32_Word        e_version; //版本号
 23     Elf32_Addr        e_entry; //程序的入口地址,在编译内核的时候可由ld程序手工指定
 24     Elf32_Off        e_phoff; //program header在文件中的偏移量
 25     Elf32_Off        e_shoff; //section header在文件中的偏移量
 26     Elf32_Word        e_flags; //标志
 27     Elf32_Half        e_ehsize; //ELF头的大小
 28     Elf32_Half        e_phentsize; //每个program header entry的大小
 29     Elf32_Half        e_phnum; //program header entry的数量
 30     Elf32_Half        e_shentsize; //每个section header entry的大小
 31     Elf32_Half        e_shnum; //section header entry的数量
 32     Elf32_Half        e_shstrndx;
 33 } Elf32_Ehdr;
 34 #define ELFHDR_LEN sizeof(Elf32_Ehdr)
 35 
 36 //ELF文件的program header结构
 37 typedef struct
 38 {
 39     Elf32_Word        p_type; //该头所指向的program segment的类型
 40     Elf32_Off        p_offset; //该头所指向的program segment在文件中的偏移
 41     Elf32_Addr        p_vaddr; //该头所指向的program segment加载进内存后的虚拟地址
 42     Elf32_Addr        p_paddr; //该头所指向的program segment加载进内存后的物理地址
 43     Elf32_Word        p_filesz; //该头所指向的program segment在文件中的大小
 44     Elf32_Word        p_memsz; //该头所指向的program segment在内存中的大小
 45     Elf32_Word        p_flags;
 46     Elf32_Word        p_align;
 47 }Elf32_Phdr;
 48 #define PHDR_LEN sizeof(Elf32_Phdr)
 49 
 50 //输出到kernel.map文件中的时候,对每一个program segment,
 51 //都有一个Seghdr开头,表征该段的信息,包括段的大小,以及段
 52 //的起始虚拟地址
 53 typedef struct
 54 {
 55     int memsz;
 56     int vaddr;
 57 }Seghdr;
 58 #define SEGHDR_LEN sizeof(Seghdr)
 59 
 60 #define FILE_NAME_LEN 50
 61 //缓冲区
 62 unsigned char buffer[MAX_BUF_LEN];
 63 char infilename[FILE_NAME_LEN];
 64 char outfilename[FILE_NAME_LEN];
 65 FILE *ifp, *ofp;
 66 
 67 static void usage()
 68 {
 69     fprintf(stderr, "Usage: proc_kernel [-r ../boot/boot] [-w ../Image]\n");
 70 }
 71 
 72 void die()
 73 {
 74     fclose(ifp);
 75     fclose(ofp);
 76     exit(1);
 77 }
 78 
 79 static void init()
 80 {
 81     strcpy(infilename, "../kernel/kernel"); //默认输入文件为在顶层目录的kernel子目录中的内核文件
 82     strcpy(outfilename, "../kernel/kernel.map"); //默认输出到顶层目录的kernel子目录中,文件名为kernel.map
 83 }
 84 
 85 static void proc_opt(int argc, char * const *argv)
 86 {
 87     int ch;
 88     opterr = 0//不显示错误信息
 89 
 90     while ((ch = getopt(argc, argv, "r:w:h")) != -1)
 91     {
 92         switch (ch)
 93         {
 94             case 'r'//指定kernel文件名
 95                 strcpy(infilename, optarg);
 96                 break;
 97             case 'w'//指定输出的系统映像文件名
 98                 strcpy(outfilename, optarg);
 99                 break;
100             case 'h':
101                 usage();
102                 exit(1);
103         }
104     }
105 }
106 
107 static void open_file()
108 {
109     //如果输入的内核文件不存在
110     if (0 != access(infilename, F_OK))
111     {
112         fprintf(stderr, "\"%s\": No such file.\n", infilename);
113         exit(1);
114     }
115 
116     //如果输出的内核映像文件已经存在,报warning
117     if (0 == access(outfilename, F_OK))
118     {
119         fprintf(stderr, "Warning: The file \"%s\" exists.\n", outfilename);
120         fprintf(stderr, "But we will go on \n");
121     }
122 
123     ifp = fopen(infilename, "r+");
124     //如果不能打开输入文件
125     if (NULL == infilename)
126     {
127         fprintf(stderr, "cannot open the file \"%s\".\n", infilename);
128         exit(1);
129     }
130 
131     ofp = fopen(outfilename, "w+");
132     //如果不能创建kernel.map文件
133     if (NULL == ofp)
134     {
135         fprintf(stderr, "cannot create the file \"%s\".\n", outfilename);
136         exit(1);
137     }
138 }
139 
140 int main(int argc, char *const *argv)
141 {
142     int pht_offset, n, i;
143     Elf32_Ehdr elf_header;//保存elf文件头
144     Elf32_Phdr p_header_buf;
145     Seghdr seg_header_buf;
146     unsigned short loadable_seg_num = 0;
147 
148     init();
149     proc_opt(argc, argv);
150     open_file();
151 
152     //读出ELF文件头
153     n = fread((void *)&elf_header, ELFHDR_LEN, 1, ifp);
154 
155     if (n < 1)
156     {
157         fprintf(stderr, "cannot read the \"%s\" file's ELF header!\n", infilename);
158         die();
159     }
160 
161     //输出文件的文件头格式为
162     //0  1  2  3  4  5  6  7  8  9  10  11(以字节为单位)
163     //k  e  r  n  e  l  |入口地址|  |段数|
164     n = fwrite("kernel"61, ofp);
165 
166     if (n < 1)
167     {
168         fprintf(stderr, "cannot write into \"%s\".\n", outfilename);
169         die();
170     }
171 
172     //entry address
173     n = fwrite((void *)(&(elf_header.e_entry)), 41, ofp);
174 
175     if (n < 1)
176     {
177         fprintf(stderr, "cannot write into \"%s\".\n", outfilename);
178         die();
179     }
180 
181     fprintf(stdout, "entry address: %x\n", elf_header.e_entry);
182     //the number of segments
183     n = fwrite("  "21, ofp);
184 
185     if (n < 1)
186     {
187         fprintf(stderr, "cannot write into \"%s\".\n", outfilename);
188         die();
189     }
190 
191     //判断输入文件是否是ELF格式文件
192     //ELF格式的文件头部的前四字节是“.ELF”的ascii码
193     if (((int *)(elf_header.e_ident))[0]!= 0x464c457f)
194     {
195         fprintf(stderr, "\"%s\" is not an ELF file!\n", infilename);
196         die();
197     }
198 
199     //判断文件是否是可执行文件
200     if (elf_header.e_type != 2)
201     {
202         fprintf(stderr, "\"%s\" is not an excutable file!\n", infilename);
203         die();
204     }
205 
206     //该ELF支持的机器架构是否是80386类型
207     if (elf_header.e_machine != 3)
208     {
209         fprintf(stderr, "\"%s\" is not for I386!\n", infilename);
210         die();
211     }
212 
213     //输出内核文件包含的程序段信息
214     fprintf(stdout, "\"%s\"含有的程序段的段数: %d\n", infilename, elf_header.e_phnum);
215 
216     for (i = 0; i < elf_header.e_phnum; ++i)
217     {
218         if (PHDR_LEN != elf_header.e_phentsize)
219         {
220             fprintf(stderr, "program header entry is confused!\n");
221             die();
222         }
223 
224         fseek(ifp, elf_header.e_phoff + i * elf_header.e_phentsize, SEEK_SET);
225         n = fread(&p_header_buf, elf_header.e_phentsize, 1, ifp);
226 
227         if (n < 1)
228         {
229             fprintf(stderr, "cannot read the program header entry!\n");
230             die();
231         }
232 
233         fprintf(stdout, "第%d个段的段头内容:\n", i + 1);
234         fprintf(stdout, "\tp_type:   0x%x\n", p_header_buf.p_type);
235         fprintf(stdout, "\tp_offset: 0x%x\n", p_header_buf.p_offset);
236         fprintf(stdout, "\tp_vaddr:  0x%x\n", p_header_buf.p_vaddr);
237         fprintf(stdout, "\tp_paddr:  0x%x\n", p_header_buf.p_paddr);
238         fprintf(stdout, "\tp_filesz: 0x%x\n", p_header_buf.p_filesz);
239         fprintf(stdout, "\tp_memsz:  0x%x\n", p_header_buf.p_memsz);
240         fprintf(stdout, "\tp_flags:  0x%x\n", p_header_buf.p_flags);
241         fprintf(stdout, "\tp_align:  0x%x\n", p_header_buf.p_align);
242 
243         if (1 != p_header_buf.p_type)//is not PT_LOAD
244         {
245             fprintf(stderr, "this segment is not loadable\n");
246             continue;
247         }
248 
249         loadable_seg_num++;
250         //对每个程序段都在头部加上描述该程序段的信息头
251         //包含程序段的长度以及程序段加载到内存时的虚拟地址
252         seg_header_buf.memsz = p_header_buf.p_filesz;
253         seg_header_buf.vaddr = p_header_buf.p_vaddr;
254 
255         n = fwrite((void *)&seg_header_buf, SEGHDR_LEN, 1, ofp);
256         if (1 != n)
257         {
258             fprintf(stderr, "cannot write the segment length into \"%s\".\n", outfilename);
259             die();
260         }
261 
262         //将段内容写进输出文件中
263         fseek(ifp, p_header_buf.p_offset, SEEK_SET);
264 
265         while (p_header_buf.p_filesz > MAX_BUF_LEN)
266         {
267             n = fread(buffer, 1, MAX_BUF_LEN, ifp);
268 
269             if (MAX_BUF_LEN != n)
270             {
271                 fprintf(stderr, "cannot read the segment from \"%s\".\n", infilename);
272                 die();
273             }
274 
275             p_header_buf.p_filesz -= MAX_BUF_LEN;
276             n = fwrite(buffer, MAX_BUF_LEN, 1, ofp);
277 
278             if (1 != n)
279             {
280                 fprintf(stderr, "cannot write the segment into \"%s\".\n", outfilename);
281                 die();
282             }
283         }
284 
285         if (p_header_buf.p_filesz > 0)
286         {
287             n = fread(buffer, p_header_buf.p_filesz, 1, ifp);
288 
289             if (1 != n)
290             {
291                 fprintf(stderr, "cannot read the segment from \"%s\".\n", infilename);
292                 die();
293             }
294 
295             n = fwrite(buffer, p_header_buf.p_filesz, 1, ofp);
296 
297             if (1 != n)
298             {
299                 fprintf(stderr, "cannot write the segment into \"%s\".\n", outfilename);
300                 die();
301             }
302         }
303     }
304 
305     //将输入文件中可加载段的段数写进输出文件头中
306     fseek(ofp, 10, SEEK_SET);
307     n = fwrite((void *)&loadable_seg_num, 21, ofp);
308 
309     if (1 != n)
310     {
311         fprintf(stderr, "cannot write the entry address into the \"kernel.map\" file!\n");
312         die();
313     }
314 
315     fclose(ifp);
316     fclose(ofp);
317     return 0;
318 }

先看看ELF格式文件的组织形式吧:

整个ELF文件中固定的部分只有ELF头,它表征整个ELF文件的组织形式。其他的包括程序头和节头都不是固定的,甚至可以不包括。程序头和节头分别是从文件执行角度和文件链接角度来看的。因为我们只关心如何将ELF加载进内存,因此我们只关心程序头。每个程序头包括表征一个程序是否可执行以及在文件中的位置和加载到内存中时的起始虚拟地址等信息。我们需要的就是这个信息。
再看看我们想生成的map文件的组织格式:

可见文件格式还是相当简单的,因为我们的目的就是让lorder可以非常轻松的将文件加载进内存指定位置。
红色区域为头部,指定Magic Number:“kernel”,占6字节,之后是内核的入口地址,再然后是可加载程序段的段数。
之后每个可加载程序段都有一个段头,该段头指定段的大小和段在内存中的起始虚拟地址。
我们的程序核心目的就是将原始的ELF格式内核文件转变成我们定义的简单的map文件,以供loader加载。
posted on 2011-11-20 14:35 myjfm 阅读(443) 评论(0)  编辑 收藏 引用 所属分类: 操作系统

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理