实现C语言运行库——MiniCRT

1.介绍

在《入口函数与程序初始化浅析》一文中我们说过,程序首先运行的代码并不是main的第一行,而是运行库中的某个入口函数。在入口函数对运行库和程序运行环境进行各种初始化,完成初始化后再调用main函数,正式开始实行程序主体部分。执行完后返回到入口函数,进行清理工作。

接下来我们就以书中第13章的MiniCRT为例,尝试一下C运行库的实现。

Let's go!


2.开始

  • 入口函数负责三部分工作:准备好程序运行环境及初始化运行库,调用mian函数执行程序主体,清理程序运行后的各种资源。
  • 运行库为所有程序提供的入口函数应该相同,再链接程序时须要指定该入口函数名。

以下代码为一个基本框架。

1 void mini_crt_entry(void)
2 {
3     // 初始化部分
4     int ret = main();
5     // 结束部分
6     exit(ret);
7 }

entry.c:

 1 //entry.c
 2 #include "minicrt.h"
 3 
 4 #ifdef WIN32
 5 #include <Windows.h>
 6 #endif 
 7 
 8 extern int main(int argc,char* argv[]);
 9 void exit(int);
10 
11 static void crt_fatal_error(const char* msg)
12 {
13     // printf("fatal error: %s",msg);
14     exit(1);
15 }
16 
17 void mini_crt_entry(void)
18 {
19     int ret;
20     // printf("ENTRY mini_crt_entry\n");
21 
22 #ifdef WIN32
23     int flag=0;
24     int argc=0;
25     char* argv[16];   // 最多16个参数
26     char* cl=GetCommandLineA();   // 对于Windows系统来说,用GetCommandLine这个API来返回整个命令行参数字符串
27 
28     // 解析命令行,即将整个命令行字符串分割成若干个参数,以符合argc和argv的格式
29     argv[0]=cl;
30     ++argc;
31     while (*cl)
32     {
33         if (*cl=='\"')   // 字符串中的空格是有效的空格,所以需要设置flag
34         {
35             if (flag==0) flag=1;
36             else flag=0;
37         }
38         else if (*cl==' ' && flag==0)   // 否则剔除空格,但只能处理单空格
39         {
40             if (*(cl+1))
41             {
42                 argv[argc]=cl+1;
43                 ++argc;
44             }
45             *cl='\0';
46         }
47         ++cl;
48     }
49 
50 #else
51     int argc;
52     char** argv;
53 
54     char* ebp_reg=0;
55     // ebp_reg=%ebp;
56     asm("movl %%ebp,%0  \n\t":"=r"(ebp_reg));
57 
58     argc=*(int*)(ebp_reg+4);
59     argv=(char**)(ebp_reg+8);
60     
61 #endif
62     if (!mini_crt_init_heap())
63         crt_fatal_error("heap initialize failed");
64 
65     if (!mini_crt_init_io())
66         crt_fatal_error("IO initialize failed");
67 
68     ret=main(argc,argv);
69     exit(ret);
70 }
71 
72 void exit(int exitCode)
73 {
74     // mini_crt_call_exit_routine();   // 启动退出时,调用注册的全局对象析构函数
75 #ifdef WIN32
76     ExitProcess(exitCode);
77 #else
78     asm("movl %0,%%ebx \n\t"
79         "movl $1,%%eax \n\t"
80         "int $0x80       \n\t"
81         "hlt       \n\t"::"m"(exitCode));
82 #endif
83 }

minicrt.h的实现嘞?不急,在下面呢。


3.堆的实现

在遵循Mini CRT的原则下,我们将Mini CRT堆的实现归纳为以下几条:

  • 实现一个以空闲链表算法为基础的堆空间分配算法;
  • 为了简单起见,堆空间大小固定为32MB,初始化后空间不再扩展或缩小;
  • 在Windows平台下不适用HeapAlloc等堆分配算法,采用VirtualAlloc 向系统直接申请32MB空间,由我们自己的堆分配算法实现malloc
  • 在Linux平台下,使用brk将数据段结束地址向后调整32MB,将这块空间作为堆空间

malloc.c:

  1 // malloc.c
  2 #include "minicrt.h"
  3 
  4 typedef struct _heap_header
  5 {
  6     enum {
  7         HEAP_BLOCK_FREE=0xABABABAB,   // magic number of free block
  8         HEAP_BLOCK_USED=0xCDCDCDCD,   // magic number of used block
  9     }type;   // block type FREE/USED
 10 
 11     unsigned size;   // block size including header
 12     struct _heap_header* next;
 13     struct _heap_header* prev;
 14 }heap_header;
 15 
 16 #define ADDR_ADD(a,o) (((char*)(a))+o)
 17 #define HEADER_SIZE (sizeof(heap_header))
 18 
 19 static heap_header* list_head=NULL;
 20 
 21 void free(void* ptr)
 22 {
 23     heap_header* header=(heap_header*)ADDR_ADD(ptr,-HEADER_SIZE);   // 释放ptr起始的一个块
 24     if (header->type!=HEAP_BLOCK_USED)
 25         return;
 26     header->type=HEAP_BLOCK_FREE;
 27     if (header->prev!=NULL && header->prev->type==HEAP_BLOCK_FREE)   // 释放块的前一个块也是空块,合并到前一个块
 28     {
 29         header->prev->next=header->next;
 30         if (header->next!=NULL)
 31             header->next->prev=header->prev;
 32         header->prev->size+=header->size;
 33 
 34         header=header->prev;
 35     }
 36 
 37     if (header->next!=NULL && header->next->type==HEAP_BLOCK_FREE)   // 释放块的后一个块也是空快,后一个块合并过来
 38     {
 39         header->size+=header->next->size;
 40         header->next=header->next->next;
 41     }
 42 }
 43 
 44 void* malloc(unsigned size)
 45 {
 46     heap_header* header;
 47     
 48     if (size==0)
 49         return NULL;
 50 
 51     header=list_head;
 52     while (header!=0)
 53     {
 54         if (header->type==HEAP_BLOCK_USED)
 55         {
 56             header=header->next;
 57             continue;
 58         }
 59 
 60         // 找到一个空闲块
 61         if (header->size>size+HEADER_SIZE &&
 62             header->size<=size+HEADER_SIZE*2)
 63         {
 64             header->type=HEAP_BLOCK_USED;
 65             return ADDR_ADD(header,HEADER_SIZE);
 66         }
 67         // 空闲块空间足够,且剩余的内部碎片分离出来还能继续使用
 68         if (header->size>size+HEADER_SIZE*2)
 69         {
 70             // split
 71             heap_header* next=(heap_header*)ADDR_ADD(header,size+HEADER_SIZE);
 72             next->prev=header;
 73             next->next=header->next;
 74             next->type=HEAP_BLOCK_FREE;
 75             next->size=header->size-(size+HEADER_SIZE);   // 笔误?应为'+'
 76             if (header->next!=NULL)
 77                 header->next->prev=next;
 78             header->next=next;
 79             header->size=size+HEADER_SIZE;
 80             header->type=HEAP_BLOCK_USED;
 81             return ADDR_ADD(header,HEADER_SIZE);
 82         }
 83         header=header->next;
 84     }
 85     return NULL;
 86 }
 87 
 88 #ifndef WIN32
 89 // Linux brk system call
 90 static int brk(void* end_data_segment)
 91 {
 92     int ret=0;
 93     // brk system call number:45
 94     // in /usr/include/asm-i386/unistd.h:
 95     // #define __NR_brk 45
 96         asm("movl $45, %%eax \n\t"
 97             "movl %1, %%ebx  \n\t"
 98             "int $0x80       \n\t"
 99             "movl %%eax, %0  \n\t"
100             :"=r"(ret):"m"(end_data_segment) );
101 }
102 #endif
103 
104 #ifdef WIN32
105 #include <Windows.h>
106 #endif
107 
108 int mini_crt_init_heap()
109 {
110     void* base=NULL;
111     heap_header* header=NULL;
112     // 32MB heap size
113     unsigned heap_size=1024*1024*32;
114 
115 // 以base为起点分配32MB的空间
116 #ifdef WIN32
117     base=VirtualAlloc(0,heap_size,MEM_COMMIT|MEM_RESERVE|PAGE_READWRITE);
118     if (base==NULL)
119         return 0;
120 #else 
121     base=(void*)brk(0);
122     void* end=ADDR_ADD(base,heap_size);
123     end=(void*)brk(end);
124     if (!end)
125         return 0;
126 #endif
127     header=(heap_header*)base;
128     header->size=heap_size;
129     header->type=HEAP_BLOCK_FREE;
130     header->next=NULL;
131     header->prev=NULL;
132 
133     list_head=header;
134     return 1;
135 }

4.IO与文件操作

与堆的实现一样,我们需要为Mini CRT的IO部分设计一些实现的基本原则:

  • 仅实现基本的文件操作,包括fopen、fread、fwrite、fclose和fseek。
  • 为了简单起见,不实现缓冲(Buffer)机制。
  • 不对Windows下的换行机制进行转换,即"\r\n"与"\n"之间不进行转换。
  • 在Windows下,文件基本操作可以使用API:CreateFile、ReadFile、WriteFile、CloseHandle和SetFilePointer实现。
  • Linux不像Windows那样有API接口,我们必须使用内嵌汇编实现open、read、write、close和seek这几个系统调用。
  • fopen时仅区分"r"、"w"和"+"这几种模式及它们的组合,不对文本模式和二进制模式进行区分,不支持追加模式("a")。

stdio.c:

  1 // stdio.c
  2 #include "minicrt.h"
  3 
  4 int mini_crt_init_io()
  5 {
  6     return 1;
  7 }
  8 
  9 #ifdef WIN32
 10 #include <Windows.h>
 11 
 12 FILE* fopen(const char* filename,const char* mode)
 13 {
 14     Handle hFile=0;
 15     int access=0;
 16     int creation=0;
 17 
 18     if (strcmp(mode,"w")==0){
 19         access!=GENERIC_WRITE;
 20         creation|=CREATE_ALWAYS;
 21     }
 22 
 23     if (strcmp(mode,"w+")==0)
 24     {
 25         access|=GENERIC_WRITE|GENERIC_READ;
 26         creation|=CREATE_ALWAYS;
 27     }
 28 
 29     if (strcmp(mode,"r")==0)
 30     {
 31         access|=GENERIC_READ;
 32         creation+=OPEN_EXISTING;
 33     }
 34 
 35     if (strcmp(mode,"r+")==0)
 36     {
 37         access|=GENERIC_WRITE|GENERIC_READ;
 38         creation|=TRUNCATE_EXISTING;
 39     }
 40 
 41     hFile=CreateFileA(filename,access,0,0,creation,0,0);
 42     if (hFile==INVALID_HANDLE_VALUE)
 43         return 0;
 44 
 45     return (FILE*)hFile;
 46 }
 47 
 48 int fread(void* buffer,int size,int count,FILE* stream)
 49 {
 50     int read=0;
 51     if (!ReadFile((HANDLE)stream,buffer,size*count,&read,0))
 52         return 0;
 53     return read;
 54 }
 55 
 56 int fwrite(const void* buffer,int size,int count,FILE* stream)
 57 {
 58     int written=0;
 59     if (!WriteFile((HANDLE)stream,buffer,size*count,&written,0))
 60         return 0;
 61     return written;
 62 }
 63 
 64 int fclose(FILE* fp)
 65 {
 66     return CloseHandle((HANDLE)fp);
 67 }
 68 
 69 int fseek(FILE* fp,int offset,int set)
 70 {
 71     return SetFilePointer((HANDLE)fp,offset,0,set);
 72 }
 73 
 74 #else   // #ifdef WIN32
 75 
 76 static int open(const char* pathname,int flags,int mode)
 77 {
 78     int fd=0;
 79         asm("movl $5, %%eax \n\t"
 80             "movl %1, %%ebx \n\t"
 81             "movl %2, %%ecx \n\t"
 82             "movl %3, %%edx \n\t"
 83             "int $0x80      \n\t"
 84             "movl %%eax, %0 \n\t":
 85             "=m"(fd):"m"(pathname),"m"(flags),"m"(mode) );
 86 }
 87 
 88 static int read(int fd,void* buffer,unsigned size)
 89 {
 90         int ret=0;
 91        asm("movl $3, %%eax  \n\t"
 92             "movl %1, %%ebx  \n\t"
 93             "movl %2, %%ecx  \n\t"
 94             "movl %3, %%edx  \n\t"
 95             "int $0x80       \n\t"
 96             "movl %%eax, %0  \n\t":
 97             "=m"(ret):"m"(fd),"m"(buffer),"m"(size) );
 98         return ret;
 99 }
100 
101 static int write(int fd,const void* buffer,unsigned size)
102 {
103         int ret=0;
104         asm("movl $4, %%eax  \n\t"
105             "movl %1, %%ebx  \n\t"
106             "movl %2, %%ecx  \n\t"
107             "movl %3, %%edx  \n\t"
108             "int $0x80       \n\t"
109             "movl %%eax, %0  \n\t":
110             "=m"(ret):"m"(fd),"m"(buffer),"m"(size) );
111         return ret;
112 }
113 
114 static int close(int fd)
115 {
116     int ret=0;
117         asm("movl $6, %%eax  \n\t"
118             "movl %1, %%ebx  \n\t"
119             "int $0x80       \n\t"
120             "movl %%eax, %0  \n\t":
121             "=m"(ret):"m"(fd) );
122         return ret;
123 }
124 
125 static int seek(int fd,int offset,int mode)
126 {
127         int ret=0;
128         asm("movl $19, %%eax  \n\t"
129             "movl %1, %%ebx  \n\t"
130             "movl %2, %%ecx  \n\t"
131             "movl %3, %%edx  \n\t"
132             "int $0x80       \n\t"
133             "movl %%eax, %0  \n\t":
134             "=m"(ret):"m"(fd),"m"(offset),"m"(mode) );
135         return ret;
136 }
137 
138 FILE* fopen(const char* filename,const char* mode)
139 {
140     int fd=-1;
141     int flags=0;
142     int access=00700;   // 创建文件的权限
143 // 来自于/usr/include/bits/fcntl.h
144 // 注意:以0开始的数字是八进制的
145 #define O_RDONLY  00
146 #define O_WRONLY  01
147 #define O_RDWR    02
148 #define O_CREAT   0100
149 #define O_TRUNC   01000
150 #define O_APPEND  02000
151 
152         if (strcmp(mode,"w")==0)
153             flags|=O_WRONLY|O_CREAT|O_TRUNC;
154 
155         if (strcmp(mode,"w+")==0)
156             flags|=O_RDWR|O_CREAT|O_TRUNC;
157 
158         if (strcmp(mode,"r")==0)
159             flags|=O_RDONLY;
160 
161         if(strcmp(mode,"r+")==0)
162             flags|=O_RDWR|O_CREAT;
163 
164         fd=open(filename,flags,access);
165         return (FILE*)fd;
166 }
167 
168 int fread(void* buffer,int size,int count,FILE* stream)
169 {
170     return read((int)stream,buffer,size*count);
171 }
172 
173 int fwrite(const void* buffer,int size,int count,FILE* stream)
174 {
175     return write((int)stream,buffer,size*count);
176 }
177 
178 int fclose(FILE* fp)
179 {
180     return close((int)fp);
181 }
182 
183 int fseek(FILE* fp,int offset,int set)
184 {
185     return seek((int)fp,offset,set);
186 }
187 
188 #endif

5.字符串相关操作

包括计算字符串相关长度、比较两个字符串、整数与字符串之间的相互转换等。

string.c:

 1 char* itoa(int n,char* str,int radix)
 2 {
 3     char digit[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 4         char* p=str;
 5         char* head=str;
 6 
 7         if (!p || radix<2 || radix>36)   //radix代表是几进制
 8             return p;
 9         if (radix!=10 && n<0)
10             return p;
11 
12         if (n==0)   //如果要转换的数字n为0,则直接在输出字符串中直接输出
13         {
14             *p++='0';
15             *p=0;
16             return p;
17         }    
18         if (radix==10 && n<0)   //如果是10进制,且为负数,则先添加负号,然后转正留待后续处理
19         {
20             *p++='-';
21             n=-n;
22         }
23 
24         while (n)
25         {
26             *p++=digit[n%radix];
27             n/=radix;
28         }
29         *p=0;   // 数字转换完了,末尾添加0
30 
31         // 上面的数字字符串是倒序的,这里将数字字符串倒过来
32         for (--p;head<p;++head,--p)
33         {
34             char temp=*head;
35             *head=*p;
36             *p=temp;
37         }
38         return str;
39 }
40 
41 int strcmp(const char* src,const char* dst)
42 {
43         int ret=0;
44         unsigned char* p1=(unsigned char*)src;
45         unsigned char* p2=(unsigned char*)dst;
46         while (!(ret=*p1-*p2) && *p2)
47             ++p1,++p2;
48 
49         if (ret<0)
50             ret=-1;
51         else if (ret>0)
52             ret=1;
53         return(ret);
54 }
55 
56 char* strcpy(char *dest,const char* src)
57 {
58         char* ret=dest;
59         while (*src)
60             *dest++=*src++;
61         *dest='\0';   //字符串拷贝完后,手动在末尾添加\0
62         return ret;   //返回copy后的字符串的首字符
63 }
64 
65 unsigned strlen(const char* str)
66 {
67         int cnt=0;
68         if (!str)
69             return 0;
70         for (;(int)*str!=204 && *str!='\0';++str)
71             ++cnt;
72         return cnt;
73 }

6.格式化字符串

其实在《操作系统真象还原》中我已经实现过了。

实现CRT中一个如雷贯耳的函数,那就是printf。printf是一个典型的变长参数函数,即参数数量不固定。我们将这一节实现的相关内容列举如下:

  • printf实现仅支持%d、%s,且不支持格式控制(比如%08d)。
  • 实现fprintf和vfprintf,实际上printf是fprintf的特殊形式,即目标文件为标准输出的fprintf。
  • 实现与文件字符串操作相关的几个函数,fputc和fputs。

printf.c:

  1 #include "minicrt.h"
  2 
  3 int fputc(int c,FILE* stream)
  4 {
  5         if (fwrite(&c,1,1,stream)!=1)
  6             return EOF;
  7         else
  8             return c;
  9 }
 10 int fputs(const char* str,FILE* stream)
 11 {
 12         int len=strlen(str);
 13         if (fwrite(str,1,len,stream)!=len)
 14             return EOF;
 15        else
 16             return len;
 17 }
 18 
 19 #ifndef WIN32
 20 #define va_list  char*
 21 #define va_start(ap,arg) (ap=(va_list)&arg+sizeof(arg))
 22 #define va_arg(ap, t)    (*(t*) ((ap+=sizeof(t))-sizeof(t)))
 23 #define va_end(ap)       (ap=(va_list)0)
 24 #else
 25 #include <Windows.h>
 26 #endif
 27 
 28 // MiniCRT 中并不支持特殊的格式操作,仅支持%d和%s两种简单的转换
 29 int vfprintf(FILE* stream,const char* format,va_list arglist)
 30 {
 31         int translating=0;
 32         int ret=0;    // 记录最终输出的字符个数
 33         const char* p=0;
 34         for (p=format;*p && *p!='\0';++p)
 35         {
 36             switch (*p)
 37             {
 38                 case '%':
 39                     if (!translating)
 40                             translating=1;   // translating置1,表示后面的字符需要解析
 41                     else
 42                     {
 43                             if (fputc('%',stream)<0)
 44                                 return EOF;
 45                             ++ret;
 46                             translating=0;
 47                     }
 48                     break;
 49                 case 'd':
 50                     if (translating)   // %d
 51                     {
 52                             char buf[16]={0};
 53                             translating=0;
 54                             itoa( va_arg(arglist,int),buf,10);
 55                             if (fputs(buf,stream)<0)
 56                                 return EOF;
 57                             ret+=strlen(buf);
 58                     }
 59                     else if (fputc('d',stream)<0)
 60                             return EOF;
 61                     else
 62                             ++ret;
 63                     break;
 64                 case 's':
 65                     if (translating)   // %s
 66                     {
 67                             const char* str=va_arg(arglist,const char*);
 68                             translating=0;
 69                             if (fputs(str,stream)<0)
 70                                 return EOF;
 71                             ret+=strlen(str);
 72                     }
 73                     else if (fputc('s',stream)<0)
 74                             return EOF;
 75                     else 
 76                             ++ret;
 77                     break;
 78                 default:
 79                     if (translating)
 80                             translating=0;
 81                     if (fputc(*p, stream)<0)
 82                             return EOF;
 83                     else
 84                             ++ret;
 85                     break;
 86             }
 87         }
 88         return ret;
 89 }
 90 
 91 int printf(const char* format,...)
 92 {
 93         va_list(arglist);
 94        va_start(arglist,format);
 95         return vfprintf(stdout,format,arglist);
 96 }
 97 
 98 int fprintf(FILE* stream,const char* format,...)
 99 {
100         va_list(arglist);
101         va_start(arglist,format);
102         return vfprintf(stream,format,arglist);
103 }

7.如何使用Mini CRT

我们将所有相关的常数定义、宏定义,以及Mini CRT所实现的函数声明等放在minicrt.h头文件里。当用户程序使用Mini CRT时,仅需要#include "minicrt.h"即可,而无须像标准的CRT一样,需要独立的包含相关文件,比如"stdio.h"、"stdlib.h"等。

minicrt.h:

 1 // minicrt.h
 2 #ifndef __MINI_CRT_H__
 3 #define __MINI_CRT_H__
 4 
 5 /*定义C++相关的函数,以使得函数的兼容性得到满足,但是依旧按照C的修饰规则来进行导出函数名修饰,即使用extern "C"*/
 6 #ifdef __cplusplus
 7 extern "C" {
 8 #endif
 9 
10 // malloc
11 #ifndef NULL
12 #define NULL (0)
13 #endif
14 
15 void free(void* ptr);
16 void* malloc(unsigned size);
17 static int brk(void* end_data_segment);
18 int mini_crt_init_heap();
19 
20 // 字符串
21 char* itoa(int n, char* str, int radix);
22 int strcmp(const char* src, const char* dst);
23 char* strcpy(char* dest, const char* src);
24 unsigned strlen(const char* sr);
25 
26 // 文件与IO
27 typedef int FILE;
28 
29 #define EOF (-1)
30 
31 /*FILE* 这个类型在Windows下实际上是内核句柄,要通过GetStdHandle的Windows API获得
32 而在Linux下则是文件描述符,标准输入输出是0,1,2,并不是指向FILE结构的地址*/
33 #ifdef  WIN32
34 #define stdin  ((FILE*) (GetStdHandle(STD_INPUT_HANDLE)))
35 #define stdout ((FILE*) (GetStdHandle(STD_OUTPUT_HANDLE)))
36 #define stderr ((FILE*) (GetStdHandle(STD_ERROR_HANDLE)))
37 #else
38 #define stdin  ((FILE*)0)
39 #define stdout ((FILE*)1)
40 #define stderr ((FILE*)2)
41 #endif
42 
43 int mini_crt_init_io();   // 省略了缓冲等诸多内容,miniCRT的IO基本无需初始化(即无需给打开文件列表进行空间分配),故而这个函数其实就是个空函数
44 FILE* fopen(const char* filename,const char* mode);
45 int fread(void* buffer,int size,int count,FILE* stream);
46 int fwrite(const void* bufffer,int size,int count,FILE* stream);
47 int fclose(FILE* fp);
48 int fseek(FILE* fp,int offset,int set);
49 
50 // printf
51 int fputc(int c,FILE* stream);
52 int fputs(const char* str,FILE* stream);
53 int printf(const char* format,...);
54 int fprintf(FILE* stream,const char* format,...);
55 
56 // internal
57 void do_global_ctors();
58 void mini_crt_call_exit_routine();
59 
60 // atexit
61 typedef void (*atexit_func_t) (void);
62 int atexit(atexit_func_t func);
63 
64 #ifdef  __cplusplus
65 }
66 #endif
67 
68 #endif   // __MINI_CRT_H

再专门编写一段测试代码,用于测试Mini CRT的功能。

test.c:

 1 #include "minicrt.h"
 2 
 3 int main(int argc,char* argv[]) 
 4 {
 5     int i;
 6     FILE* fp;
 7     char** v=(char**)malloc(argc*sizeof(char*) );
 8     char** tempArgv=argv;
 9     int tempArgc=argc;
10 
11     for (i=0;i<argc;++i)
12     {
13         v[i]=(char*)malloc(strlen(tempArgv[i])+1);
14         strcpy(v[i],tempArgv[i]);
15     }
16 
17     fp=fopen("test.txt","w");
18     for (i=0;i<tempArgc;++i)
19     {
20         int len=strlen(v[i]);
21         fwrite(&len,1,sizeof(int),fp);
22         fwrite(v[i],1,len,fp);
23     }
24     fclose(fp);
25 
26     fp=fopen("test.txt","r");
27     for (i=0;i<tempArgc;++i)
28     {
29         int len;
30         char* buf;
31         fread(&len,1,sizeof(int),fp);
32         buf=(char*)malloc(len+1);
33         fread(buf,1,len,fp);
34         buf[len]='\0';
35         printf("%d %s\n",len,buf);
36         free(buf);
37         free(v[i]);
38     }
39     fclose(fp);
40 }

接下来的问题是如何编译得到库文件了。由于动态库的实现比静态库要复杂,所以Mini CRT仅仅以静态库的形式提供给最终用户,在Linux下它是minicrt.a。

Linux下的命令

1 $gcc -c -fno-builtin -nostdlib -fno-stack-protector entry.c malloc.c stdio.c string.c printf.c -m32 -g
2 $ar -rs minicrt.a malloc.o printf.o stdio.o string.o
3 $gcc -c -ggdb -fno-builtin -nostdlib -fno-stack-protector test.c -m32 -g
4 $ld -static -e mini_crt_entry entry.o test.o minicrt.a -o test -m elf_i386
  • -fno-builtin参数:关闭GCC的内置函数功能,默认情况下GCC会把strlen、strcmp等函数展开成它内部的实现。
  • -nostdlib:表示不使用任何来自Glibc、GCC的库文件和启动文件,它包含了-nostartfiles这个参数。
  • -fno-stack-protector:关闭堆栈保护功能,最新版本的GCC在处理变长参数函数的情况下会要求实现对堆栈的保护函数。如果不关闭,我们在使用Mini CRT时会发生"__stack__chk_fail"函数未定义的错误。
  • 由于系统是64位ubuntu,故而需要在上面注明-m32或m elf_i386。(好像经常遇到这个问题了,这一点谢谢末尾链接中的博客博主的提醒)

啰嗦一句,正是由于在第四条命令实行静态链接时我们要求"-e mini_crt_entry",所以程序的入口函数被指定为entry.c中的"mini_crt_entry"。

最后执行一下吧。

./test arg1 a+b=c 666

效果如下:

成功地分别输出了三个arg的长度和arg串自身。

Windows下的编译执行我就不试了(怠惰),大家有兴趣的话自己试试吧。


参考博客:

程序员的自我修养:MiniCRT自制C语言运行库_墨篙和小奶猫的博客-CSDN博客


结束语:

ok,到此这本《程序员的自我修养——链接、装载与库》就算是再次读完了。

为什么是“再次呢”?因为这已经算是我第三次阅读本书了,第一次是在上学期,为了更好地攻读操作系统课,就去读了这本书,发现还是挺有意思的,但受限于自己的基础还比较薄弱,没有很好地消化吸收,感觉就是匆匆掠过一般,没记住什么东西,可能是因为书中多为底层理论,较少有实践吧。

第二次是在寒假结束的第一个星期,感觉自己阅读了近一个假期的xv6源码,应该可以再次挑战整本书了。怎么说呢,确实看得懂、明白、清晰多了,但还是觉得只能了解到ELF、静态/动态链接的基本概念和整体框架,没有深入其中,那是我就觉得看这本书不能急躁,一定要耐心慢慢看,多上机操作才行,后续还要再深入地看。

于是有了这个系列的文章。

这次因为课都差不多上完了,课余时间比较多,我计划用两个星期的时间再看本书。差不多吧,中间又花了些时间做编译原理的实验。阅读过程中不仅过了些笔记,还找到很好的文章可以跟着去上手实验一下。通过自己去Linux下看ELF,看链接后的各种数据及其结构,才能更直观地感受到知识就在眼前,仿佛揭开了一层迷雾。而且看到经过一步步操作后的结果和自己预想中的一样,那种感觉,我想经常经常刷算法题的人一定是懂得——就像看到AC一样!

说实话,挺惭愧的,大三快结束了,随着《操作系统真象还原》的完成和本次的实践,我第一次感觉实验(课)是很有用的,或者说是这样的作用,甚至比理论本身更重要,有的时候真的不知道前面的实验课都是怎么混过来的——所以我还专门开了一个系列去记录《编译原理》的实验过程——算是督促自已吧。回想以前的实验课,大概除了C/C++、数据结构、算法、操作系统、信息安全和半个计网,其它的lab都是靠同学带飞的,我最最讨厌的实验报告也是同学的、学长的、网上的、chatGPT的东拼西凑。。。额,只能说幸好实验不考试吧。我也不知道怎么回事儿,感觉身边很少有喜欢实验课的同学,好像所有人上实验课的目的都是:赶紧做完实验,然后,写报告!是的,至少我非常讨厌实验报告,这比做实验本身还费劲、费时间,为什么有些老师的评判标准是报告的篇幅啊,太离谱了。另外,我甚至听说有同学可以完全不做实验,就凭同学的讲述和自己的理解直接写实验报告,结果还被老师评高分的事儿发生,太魔幻了,好吧,或许这也是一种本事吧。所以~all right, it's life.(摊手)

posted @ 2023-06-12 23:02  Hell0er  阅读(283)  评论(0)    收藏  举报