WinixJ---tools/proc_kernel.c文件详解

WinixJ---tools/proc_kernel.c文件详解
      开发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 " 6 1 , 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)),  4 1 , 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( "    " 2 1 , 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,  2 1 , 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加载。

你可能感兴趣的:(WinixJ---tools/proc_kernel.c文件详解)