实现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.(摊手)
浙公网安备 33010602011771号