设为首页 加入收藏

TOP

11.2.3 glibc与MSVC CRT(1)
2013-10-07 00:44:52 来源: 作者: 【 】 浏览:54
Tags:11.2.3 glibc MSVC CRT

11.2.3  glibc与MSVC CRT(1)

运行库是平台相关的,因为它与操作系统结合得非常紧密。C语言的运行库从某种程度上来讲是C语言的程序和不同操作系统平台之间的抽象层,它将不同的操作系统API抽象成相同的库函数。比如我们可以在不同的操作系统平台下使用fread来读取文件,而事实上fread在不同的操作系统平台下的实现是不同的,但作为运行库的使用者我们不需要关心这一点。虽然各个平台下的C语言运行库提供了很多功能,但很多时候它们毕竟有限,比如用户的权限控制、操作系统线程创建等都不是属于标准的C语言运行库。于是我们不得不通过其他的办法,诸如绕过C语言运行库直接调用操作系统API或使用其他的库。Linux和Windows平台下的两个主要C语言运行库分别为glibc(GNU C Library)和MSVCRT(Microsoft Visual C Run-time),我们在下面将会分别介绍它们。

值得注意的是,像线程操作这样的功能并不是标准的C语言运行库的一部分,但是glibc和MSVCRT都包含了线程操作的库函数。比如glibc有一个可选的pthread库中的pthread_create()函数可以用来创建线程;而MSVCRT中可以使用_beginthread()函数来创建线程。所以glibc和MSVCRT事实上是标准C语言运行库的超集,它们各自对C标准库进行了一些扩展。

glibc

glibc即GNU C Library,是GNU旗下的C标准库。最初由自由软件基金会FSF(Free Software Foundation)发起开发,目的是为GNU操作系统开发一个C标准库。GNU操作系统的最初计划的内核是Hurd,一个微内核的构架系统。Hurd因为种种原因开发进展缓慢,而Linux因为它的实用性而逐渐风靡,最后取代Hurd成了GNU操作系统的内核。于是glibc从最初开始支持Hurd到后来渐渐发展成同时支持Hurd和Linux,而且随着Linux的越来越流行,glibc也主要关注Linux下的开发,成为了Linux平台的C标准库。

20世纪90年代初,在glibc成为Linux下的C运行库之前,Linux的开发者们因为开发的需要,从Linux内核代码里面分离出了一部分代码,形成了早期Linux下的C运行库。这个C运行库又被称为Linux libc。这个版本的C运行库被维护了很多年,从版本2一直开发到版本5。如果你去看早期版本的Linux,会发现/lib目录下面有libc.so.5这样的文件,这个文件就是第五个版本的Linux libc。1996年FSF发布了glibc 2.0,这个版本的glibc开始支持诸多特性,比如它完全支持POSIX标准、国际化、IPv6、64-位数据访问、多线程及改进了代码的可移植性。在此时Linux libc的开发者也认识到单独地维护一份Linux下专用的C运行库是没有必要的,于是Linux开始采用glibc作为默认的C运行库,并且将2.x版本的glibc看作是Linux libc的后继版本。于是我们可以看到,glibc在/lib目录下的.so文件为libc.so.6,即第六个libc版本,而且在各个Linux发行版中,glibc往往被称为libc6。glibc在Linux平台下占据了主导地位之后,它又被移植到了其他操作系统和其他硬件平台,诸如FreeBSD、NetBSD等,而且它支持数十种CPU及嵌入式平台。目前最新的glibc版本号是2.8(2008年4月)。

glibc的发布版本主要由两部分组成,一部分是头文件,比如stdio.h、stdlib.h等,它们往往位于/usr/include;另外一部分则是库的二进制文件部分。二进制部分主要的就是C语言标准库,它有静态和动态两个版本。动态的标准库我们及在本书的前面章节中碰到过了,它位于/lib/libc.so.6;而静态标准库位于/usr/lib/libc.a。事实上glibc除了C标准库之外,还有几个辅助程序运行的运行库,这几个文件可以称得上是真正的"运行库"。它们就是/usr/lib/crt1.o、/usr/lib/crti.o和/usr/lib/crtn.o。是不是对这几个文件还有点印象呢?我们在第2章讲到静态库链接的时候已经碰到过它们了,虽然它们都很小,但这几个文件都是程序运行的最关键的文件。

glibc启动文件

crt1.o里面包含的就是程序的入口函数_start,由它负责调用__libc_start_main初始化libc并且调用main函数进入真正的程序主体。实际上最初开始的时候它并不叫做crt1.o,而是叫做crt.o,包含了基本的启动、退出代码。由于当时有些链接器对链接时目标文件和库的顺序有依赖性,crt.o这个文件必须被放在链接器命令行中的所有输入文件中的第一个,为了强调这一点,crt.o被更名为crt0.o,表示它是链接时输入的第一个文件。

后来由于C++(www.cppentry.com)的出现和ELF文件的改进,出现了必须在main()函数之前执行的全局/静态对象构造和必须在main()函数之后执行的全局/静态对象析构。为了满足类似的需求,运行库在每个目标文件中引入两个与初始化相关的段".init"和".finit"。运行库会保证所有位于这两个段中的代码会先于/后于main()函数执行,所以用它们来实现全局构造和析构就是很自然的事情了。链接器在进行链接时,会把所有输入目标文件中的".init"和".finit"按照顺序收集起来,然后将它们合并成输出文件中的".init"和".finit"。但是这两个输出的段中所包含的指令还需要一些辅助的代码来帮助它们启动(比如计算GOT之类的),于是引入了两个目标文件分别用来帮助实现初始化函数的crti.o和crtn.o。

与此同时,为了支持新的库和可执行文件格式,crt0.o也进行了升级,变成了crt1.o。crt0.o和crt1.o之间的区别是crt0.o为原始的,不支持".init"和".finit"的启动代码,而crt1.o是改进过后,支持".init"和".finit"的版本。这一点我们从反汇编crt1.o可以看到,它向libc启动函数__libc_start_main()传递了两个函数指针"__libc_csu_init"和"__libc_csu_fini",这两个函数负责调用_init()和_finit(),我们在后面"C++(www.cppentry.com)全局构造和析构"的章节中还会详细分析。

为了方便运行库调用,最终输出文件中的".init"和".finit"两个段实际上分别包含的是_init()和_finit()这两个函数,我们在关于运行库初始化的部分也会看到这两个函数,并且在C++(www.cppentry.com)全局构造和析构的章节中也会分析它们是如何实现全局构造和析构的。crti.o和crtn.o这两个目标文件中包含的代码实际上是_init()函数和_finit()函数的开始和结尾部分,当这两个文件和其他目标文件安装顺序链接起来以后,刚好形成两个完整的函数_init()和_finit()。我们用objdump可以查看这两个文件的反汇编代码:

$ objdump -dr /usr/lib/crti.o
crti.o:     file format elf32-i386
Disassembly of section .init:
00000000 <_init>:
0:   55                      push   %ebp
1:   89 e5                   mov    %esp,%ebp
3:   53                      push   %ebx
4:   83 ec 04                sub    $0x4,%esp
7:   e8 00 00 00 00          call   c <_init+0xc>
c:   5b                      pop    %ebx
d:   81 c3 03 00 00 00       add    $0x3,%ebx
f: R_386_GOTPC  _GLOBAL_OFFSET_TABLE_
13:   8b 93 00 00 00 00       mov
0x0(%ebx),%edx
15: R_386_GOT32 __gmon_start__
19:   85 d2                   test   %edx,%edx
1b:   74 05                   je     22 <_init+0x22>
1d:   e8 fc ff ff ff          call   1e <_init+0x1e>
1e: R_386_PLT32 __gmon_start__
Disassembly of section .fini:
00000000 <_fini>:
0:   55                      push   %ebp
1:   89 e5                   mov    %esp,%ebp
3:   53                      push   %ebx
4:   83 ec 04                sub    $0x4,%esp
7:   e8 00 00 00 00          call   c <_fini+0xc>
c:   5b                      pop    %ebx
d:   81 c3 03 00 00 00       add    $0x3,%ebx
f: R_386_GOTPC  _GLOBAL_OFFSET_TABLE_
$ objdump -dr /usr/lib/crtn.o
crtn.o:     file format elf32-i386
Disassembly of section .init:
00000000 <.init>:
0:   58                      pop    %eax
1:   5b                      pop    %ebx
2:   c9                      leave
3:   c3                      ret
Disassembly of section .fini:
00000000 <.fini>:
0:   59                      pop    %ecx
1:   5b                      pop    %ebx
2:   c9                      leave
3:   c3                      ret
【责任编辑:云霞 TEL:(010)68476606】

回书目   上一节   下一节

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇11.2.3 glibc与MSVC CRT(2) 下一篇11.3.1 CRT的多线程困扰

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: