C++ 编译器和调试器以及静态库动态库使用汇总

fenglin

贡献于2012-02-28

字数:52427 关键词: C/C++开发 C/C++

经常的,有朋友问到有关unix下面调试的技术。我整理了大多数的unix系统下面的常用的调试工具的调试技术的文章。希望对大家有所帮助。 另外静态库、动态库也是问的频率比较高的问题。在这里也做了总结。 ######大多数unix系统下面的调试器的使用方法如下:###### ***************gdb介绍********************* GNU 的调试器称为 gdb,该程序是一个交互式工具,工作在字符模式。在 X Window 系统中,有一个 gdb 的 前端图形工具,称为 xxgdb。gdb 是功能强大的调试程序,可完成如下的调试任务: * 设置断点; * 监视程序变量的值; * 程序的单步执行; * 修改变量的值。 在可以使用 gdb 调试程序之前,必须使用 -g 选项编译源文件。可在 makefile 中如下定义 CFLAGS 变量: CFLAGS = -g 运行 gdb 调试程序时通常使用如下的命令: gdb progname 在 gdb 提示符处键入help,将列出命令的分类,主要的分类有: * aliases:命令别名 * breakpoints:断点定义; * data:数据查看; * files:指定并查看文件; * internals:维护命令; * running:程序执行; * stack:调用栈查看; * statu:状态查看; * tracepoints:跟踪程序执行。 键入 help 后跟命令的分类名,可获得该类命令的详细清单。 *********gdb 的常用命令*************** 命令                        解释 break NUM               在指定的行上设置断点。 bt                      显示所有的调用栈帧。该命令可用来显示函数的调用顺序。 clear                   删除设置在特定源文件、特定行上的断点。其用法为:clear FILENAME:NUM。 continue                继续执行正在调试的程序。该命令用在程序由于处理信号或断点而                         导致停止运行时。 display EXPR            每次程序停止后显示表达式的值。表达式由程序定义的变量组成。 file  FILE               装载指定的可执行文件进行调试。 help NAME               显示指定命令的帮助信息。 info break              显示当前断点清单,包括到达断点处的次数等。 info files              显示被调试文件的详细信息。 info func               显示所有的函数名称。 info local              显示当函数中的局部变量信息。 info prog               显示被调试程序的执行状态。 info var                显示所有的全局和静态变量名称。 kill                    终止正被调试的程序。 list                    显示源代码段。 make                    在不退出 gdb 的情况下运行 make 工具。 next                    在不单步执行进入其他函数的情况下,向前执行一行源代码。 print EXPR              显示表达式 EXPR 的值。 ******gdb 使用范例************************ ----------------- 清单  一个有错误的 C 源程序 bugging.c ----------------- #include  #include  static char buff [256]; static char* string; int main () {     printf ("Please input a string: ");     gets (string);     printf ("\nYour string is: %s\n", string); } ----------------- 上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了一个未经过初 始化的字符串地址 string,因此,编译并运行之后,将出现 Segment Fault 错误: $ gcc -o test -g test.c $ ./test Please input a string: asfd Segmentation fault (core dumped) 为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行: 1.运行 gdb bugging 命令,装入 bugging 可执行文件; 2.执行装入的 bugging 命令; 3.使用 where 命令查看程序出错的地方; 4.利用 list 命令查看调用 gets 函数附近的代码; 5.唯一能够导致  gets 函数出错的因素就是变量 string。用 print 命令查看 string 的值; 6.在 gdb 中,我们可以直接修改变量的值,只要将 string 取一个合法的指针值就可以了,为此,我们在第  11 行处设置断点; 7.程序重新运行到第 11 行处停止,这时,我们可以用 set variable 命令修改 string 的取值; 8.然后继续运行,将看到正确的程序运行结果。 运行 gcc/egcs  **********运行 gcc/egcs*********************** GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。  如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有 两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。 清单 factorial.c ----------------------- #include  #include  int factorial (int n) {     if (n <= 1)         return 1;     else         return factorial (n - 1) * n; } ----------------------- ----------------------- 清单  main.c ----------------------- #include  #include  int factorial (int n); int main (int argc, char **argv) {     int n;     if (argc < 2) {         printf ("Usage: %s n\n", argv [0]);         return -1;     }     else {         n = atoi (argv[1]);         printf ("Factorial of %d is %d.\n", n, factorial (n));     }     return 0; } ----------------------- 利用如下的命令可编译生成可执行文件,并执行程序: $ gcc -o factorial main.c factorial.c $ ./factorial 5 Factorial of 5 is 120. GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C+ + 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。 但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完 完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。 假设我们有一个如下的 C++ 源文件(hello.C): #include  void main (void) {     cout << "Hello, world!" << endl; } 则可以如下调用 g++ 命令编译、连接并生成可执行文件: $ g++ -o hello hello.C $ ./hello Hello, world! **********************gcc/egcs 的主要选项*********                     gcc 命令的常用选项 选项                解释 -ansi                只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,                     例如 asm 或 typeof 关键词。 -c                  只编译并生成目标文件。 -DMACRO             以字符串“1”定义 MACRO 宏。 -DMACRO=DEFN        以字符串“DEFN”定义 MACRO 宏。 -E                  只运行 C 预编译器。 -g                  生成调试信息。GNU 调试器可利用该信息。 -IDIRECTORY         指定额外的头文件搜索路径DIRECTORY。 -LDIRECTORY         指定额外的函数库搜索路径DIRECTORY。 -lLIBRARY           连接时搜索指定的函数库LIBRARY。 -m486               针对 486 进行代码优化。 -o FILE             生成指定的输出文件。用在生成可执行文件时。 -O0                 不进行优化处理。 -O 或 -O1           优化生成代码。 -O2                 进一步优化。 -O3                 比 -O2 更进一步优化,包括 inline 函数。 -shared             生成共享目标文件。通常用在建立共享库时。 -static             禁止使用共享连接。 -UMACRO             取消对 MACRO 宏的定义。 -w                  不生成任何警告信息。 -Wall               生成所有警告信息。 #######SCO UNIX下面dbaxtra的调试技术######### 在sco unix下编程大多离不开C语言,即使是数据库应用也有很多是与c搭配使用的,例如informix esql/c 就可以在c语言中嵌入sql 语句。很多人认为在unix下写程序是件很痛苦的事情,其中一个很重要原因是不知道在unix下怎样调试程序。其实在sco unix源码调试器是dbxtra或dbXtra,linux下是gdb。它们类似turbo c的调试器,可以跟踪源码变量。在unix 下调试程序有如下传统方法  ---- 一、在要调试语句之前,输出要调试的变量,利用printf()函数。  ---- 二、写日志文件,把结果输出到文件中避免屏幕混乱,利用fprintf()函数。  ---- 三、利用sco 内置调试器dbxtra或dbXtra。  ---- dbxtra 适用字符界面,在sco unix的图形界面用dbXtra。(编按:请注意大小写)  以下是dbxtra基本命令: c               cont      在断点后继续执行 d               delete    删除所设断点 h               help      帮助 e               edit      编辑源程序 n                next      源程序区的内容向下翻一屏。 p               print     显示变量 q               quit      退出dbxtra r               run       运行程序,直到遇上设置的断点 rr              rerun     再次运行 s               step      单步运行 st              stop      设置断点 j               status    显示当前断点  t        where  显示当前状态,列出所有设置的变量值 di              display   开显示窗,用于查看变量 ud              undisplay 删除显示窗的条目 f               forward   源程序区的内容向上 翻一屏。 B               backward  源程序区的内容向下 翻一屏。 Stopi           stop inst 设置断点 tracei          trace inst跟踪子程序  dbxtra [options] [objectfile ] ---- dbxtra 在启动时有个参数-Idir值得一提.我们在编写一个较大程序的时候,通常源程序和编译生成的可执行文件都放在不同的目录中,这样便于管理。默认dbxtra将在可执行文件所在的目录下找匹配c的源程序。当我们启动时,指定-I参数,dbxtra就会到我们指定的目录下找匹配的c程序。 例如:  ---- dbxtra -I"\work\c" program1  ---- 源程序在用cc编译时要带上-g 参数,这样是加上符号表等调试信息。只有这样编译过的文件,dbxtra才可以调试。调试信息使源代码和机器码关联。  ---- 下面这个C程序输出结果和我们的预想结果不一样,说明某些地方有错误。我们用调试器来调试它:  ---- 程序一:  t.c main() {  int   i=10 ,*p1;    float j=1.5,*p2;    p1=&    p2=&    p2=p1;    printf("%d,%d\n",*p1,*p2); } 首先带上-g参数编译  cc -g -o t t.c 启动调试器           dbxtra t 屏幕显示: 1.main() 2.{  int   i=10 ,*p1; 3.   float j=1.5,*p2; 4.   p1=& 5.   p2=& 6.   p2=p1; 7.   printf("%d,%d\n",*p1,*p2); 8.} C[browse] File:t.c    Func.-      Readubg symbolic information  Type    'help' for help (dbxtra) (dbxtra) 设置断点: (dbxtra)stop at 5 运行: (dbxtra) run 程序自动在第5行停下。 这时我们可以看变量的值。 (dbxtra) print *p1 单步执行。 (dbxtra)  step  程序将执行第5行源码,指针将移到第6行。 (dbxtra)  print *p2 (dbxtra) step 程序执行了第6行源码后,将指针移到第7行。 (dbxtra) print *p1 , *p2 ---- 我们发现 在执行了第6行源码后,*p1,*p2的值就不对了,所以问题就出在第6行上。仔细检查后发现指针p1指向整型,指针p2指向实型。它们之间的赋值要进行强制类型转换。这种错误在C程序中是很常见的。  ---- 有时我们在调试一些程序时,要在整个程序运行中时刻监视莫些变量的值,例如程序一中我们要时刻了解*p1,*p2的值,除了在每一行程序执行完后,打print *p1,*p2外,还可以开一个显示窗口。  ---- (dbxtra)display *p1,*p2  ---- 用undisplay 删掉不想要的变量。  ----  有些程序运行时要带参数,mycat /etc/passwd 在调试时候  ---- (dbxtra) run '/etc/passwd'  ---- 再运行时,无需再写一遍参数。  ---- (dbxtra) rerun  ---- 在涉及到curses库编程或屏幕有大量的人机界面时,为了调试方便,我们可以把程序输出结果重定向到个虚屏。  ---- (dbxtra) run >/dev/tty03  ---- 当然要先把tty03 disable 掉。(disable tty03)  #######创建和使用静态库######### 详细的使用情况,请大家man手册,这里只介绍一下。静态库相对的比较简单。 创建一个静态库是相当简单的。通常使用 ar 程序把一些目标文件(.o)组合在一起, 成为一个单独的库,然后运行 ranlib,以给库加入一些索引信息。 ########创建和使用共享库######### 特殊的编译和连接选项 -D_REENTRANT         使得预处理器符号 _REENTRANT 被定义,这个符号激活一些宏特性。 -fPIC                选项产生位置独立的代码。由于库是在运行的时候被调入,因此这个                      选项是必需的,因为在编译的时候,装入内存的地址还不知道。如果                      不使用这个选项,库文件可能不会正确运行。 -shared              选项告诉编译器产生共享库代码。 -Wl,-soname          -Wl 告诉编译器将后面的参数传递到连接器。而 -soname 指定了                      共享库的 soname。 # 可以把库文件拷贝到 /etc/ld.so.conf 中列举出的任何目录中,并以  root 身份运行 ldconfig;或者 # 运行 export LD_LIBRARY_PATH='pwd',它把当前路径加到库搜索路径中去。 #######使用高级共享库特性######### 1. ldd 工具 ldd 用来显示执行文件需要哪些共享库, 共享库装载管理器在哪里找到了需要的共享库. 2. soname 共享库的一个非常重要的,也是非常难的概念是  soname——简写共享目标名(short for shared object name)。这是一个为共享库(.so)文件而内嵌在控制数据中的名字。如前面提到的,每一个程序都有一个需要使用的库的清单。这个清单的内容是一系列库的 soname,如同 ldd 显示的那样,共享库装载器必须找到这个清单。 soname 的关键功能是它提供了兼容性的标准。当要升级系统中的一个库时,并且新库的 soname 和老的库的 soname 一样,用旧库连接生成的程序,使用新的库依然能正常运行。这个特性使得在 Linux 下,升级使用共享库的程序和定位错误变得十分容易。  在 Linux 中,应用程序通过使用 soname,来指定所希望库的版本。库作者也可以通过保留或者改变 soname 来声明,哪些版本是相互兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。 查看/usr/local/lib 目录,分析 MiniGUI 的共享库文件之间的关系 3. 共享库装载器 当程序被调用的时候,Linux 共享库装载器(也被称为动态连接器)也自动被调用。它的作用是保证程序所需要的所有适当版本的库都被调入内存。共享库装载器名字是 ld.so 或者是 ld-linux.so,这取决于 Linux libc 的版本,它必须使用一点外部交互,才能完成自己的工作。然而它接受在环境变量和配置文件中的配置信息。 文件 /etc/ld.so.conf 定义了标准系统库的路径。共享库装载器把它作为搜索路径。为了改变这个设置,必须以 root 身份运行 ldconfig 工具。这将更新 /etc/ls.so.cache 文件,这个文件其实是装载器内部使用的文件之一。 可以使用许多环境变量控制共享库装载器的操作(表1-4+)。                         表 1-4+ 共享库装载器环境变量 变量                       含义 LD_AOUT_LIBRARY_PATH       除了不使用 a.out 二进制格式外,与 LD_LIBRARY_PATH 相同。 LD_AOUT_PRELOAD            除了不使用 a.out 二进制格式外,与 LD_PRELOAD 相同。 LD_KEEPDIR                 只适用于 a.out 库;忽略由它们指定的目录。 LD_LIBRARY_PATH            将其他目录加入库搜索路径。它的内容应该是由冒号                            分隔的目录列表,与可执行文件的 PATH 变量具有相同的格式。                            如果调用设置用户 ID 或者进程 ID 的程序,该变量被忽略。 LD_NOWARN                  只适用于 a.out 库;当改变版本号是,发出警告信息。 LD_PRELOAD                 首先装入用户定义的库,使得它们有机会覆盖或者重新定义标准库。                            使用空格分开多个入口。对于设置用户 ID 或者进程 ID 的程序,                            只有被标记过的库才被首先装入。在 /etc/ld.so.perload 中指定                            了全局版本号,该文件不遵守这个限制。 4. 使用 dlopen     另外一个强大的库函数是 dlopen()。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。     可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。     当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。 ****************LINUX动态链接库的使用*********** 一、编写合格的动态链接库头文件      C语言的头文件,可供一个或多个程序引用,里面一般定义程序所需的常量,自定义类型及函数原型说明等.其中的函数原型说明,则供编译器检查语法,用于排除引用参数时类型不一致的错误.只有编写合格的动态链接库头文件,程序员才能正确使用动态链接库内的函数.    动态链接库头文件要采用C语言标准格式,其中的动态函数原型定义,不必象上文介绍的那样用(*动态函数名)的描述形式.请看下面的例子每行开始的数字为所在行行号,为笔者添加,供注解使用)    1 /* adatetime.h : 纵横软件制作中心雨亦奇(zhsoft@371.net)编写, 2002-03-06. */  2   3 #ifndef __DATETIME_H  4   5 #define __DATETIME_H  6   7 /* 日期结构 */  8 typedef struct  9 {  10 int year;  11  int mon;  12 int day;  13 }DATETYPE;  14   15 /* 时间结构 */  16 typedef struct  17 {  18 char hour;  19 char min;  20 char sec;  21 }TIMETYPE;  22   23 int getdate(DATETYPE *d); /* 取当前日期 */  24 int gettime(TIMETYPE *t); /* 取当前时间 */  25   26 #endif  27     注:与上文的datetime.h文件比较,从该头文件第23,24行可以看到,动态函数getdate,gettime的原型定义改变了,不再使用(*getdate),(*gettime)的格式了(这种格式使用较为罗嗦).      二、正确编译与命名动态链接库      为了让GCC编译器生成动态链接库,编译时须加选项-shared.(这点须牢记)    LINUX系统中,为了让动态链接库能被系统中其它程序共享,其名字应符合“lib*.so*”这种格式.如果某个动态链接库不符合此格式,则LINUX的动态链接库自动装入程序(ld.so)将搜索不到此链接库,其它程序也无法共享之.    格式中,第一个*通常表示为简写的库名,第二个*通常表示为该库的版本号.如:在我的系统中,基本C动态链接库的名字为libc.so.6,线程pthread动态链接库的名字为libpthread.so.0等等.本文例子所生成的动态链接库的名字为libmy.so,虽没有版本号,但也符合所要求的格式.    生成该动态链接库的维护文件makefile-lib内容如下:    1 # makefile : 纵横软件制作中心雨亦奇编写, 2002-03-07.  2   3 all : libmy.so  4   5 SRC = getdate.c gettime.c  6   7 TGT = $(SRC:.c=.o)  8    9 $(SRC) : adatetime.h  10 @touch $@  11   12 %.o : %.c  13 cc -c $?  14   15 # 动态链接库(libmy.so)生成  16 libmy.so : $(TGT)  17 cc -s -shared -o $@ $(TGT)  18     运行命令:    $ make -f makefile-lib  $    即生成libmy.so库.    注: 维护文件中,第17行用-shared选项以生成动态链接库,用-s选项以去掉目标文件中的符号表,从而减小文件长度.      三、共享动态链接库      3.1 动态链接库配置文件    为了让动态链接库为系统所使用,需要维护动态链接库的配置文件--/etc/ld.so.conf.此文件内,存放着可被LINUX共享的动态链接库所在目录的名字(系统目录/lib,/usr/lib除外),各个目录名间以空白字符(空格,换行等)或冒号或逗号分隔.一般的LINUX发行版中,此文件均含一个共享目录/usr/X11R6/lib,为X window窗口系统的动态链接库所在的目录.    下面看看我的系统中此文件的内容如何:    # cat /etc/ld.so.conf  /usr/X11R6/lib  /usr/zzz/lib  #    由上可以看出,该动态库配置文件中,增加了一个/usr/zzz/lib目录.这是我自己新建的共享库目录,下面存放我新开发的可供系统共享的动态链接库.    3.2 动态链接库管理命令    为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig.此执行程序存放在/sbin目录下.    ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.    ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.    ldconfig命令行用法如下:    ldconfig [-v|--verbose] [-n] [-N] [-X] [-f CONF] [-C CACHE] [-r ROOT] [-l] [-p|--print-cache] [-c FORMAT] [--format=FORMAT] [-V] [-?|--help|--usage] path...    ldconfig可用的选项说明如下:    (1) -v或--verbose : 用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字.    (2) -n : 用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.    (3) -N : 此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.    (4) -X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.    (5) -f CONF : 此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf.    (6) -C CACHE : 此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,此文件存放已排好序的可共享的动态链接库的列表.    (7) -r ROOT : 此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.如用-r /usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.    ( -l : 通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用此项.    (9)  -p或--print-cache : 此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.    (10) -c FORMAT 或 --format=FORMAT : 此选项用于指定缓存文件所使用的格式,共有三种ld(老格式),new(新格式)和compat(兼容格式,此为默认格式).    (11) -V : 此选项打印出ldconfig的版本信息,而后退出.    (12) -? 或 --help 或 --usage : 这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出.    举三个例子:    例1:    # ldconfig -p  793 libs found in cache `/etc/ld.so.cache'  libzvt.so.2 (libc6) => /usr/lib/libzvt.so.2  libzvt.so (libc6) => /usr/lib/libzvt.so  libz.so.1.1.3 (libc6) => /usr/lib/libz.so.1.1.3  libz.so.1 (libc6) => /lib/libz.so.1  ......  #    注: 有时候用户想知道系统中有哪些动态链接库,或者想知道系统中有没有某个动态链接库,这时,可用-p选项让ldconfig输出缓存文件中的动态链接库列表,从而查询得到.例子中,ldconfig命令的输出结果第1行表明在缓存文件/etc/ld.so.cache中找到793个共享库,第2行开始便是一系列共享库的名字及其全名(绝对路径).因为实际输出结果太多,为节省篇幅,以......表示省略的部分.      例2:    # ldconfig -v  /lib:  liby.so.1 -> liby.so.1  libnss_wins.so -> libnss_wins.so  ......  /usr/lib:  libjscript.so.2 -> libjscript.so.2.0.0  libkspell.so.2 -> libkspell.so.2.0.0  ......  /usr/X11R6/lib:  libmej-0.8.10.so -> libmej-0.8.10.so  libXaw3d.so.7  -> libXaw3d.so.7.0  ......  #    注: ldconfig命令在运行正常的情况下,默认不输出什么东西.本例中用了-v选项,以使ldconfig在运行时输出正在扫描的目录及搜索到的共享库,用户可以清楚地看到运行的结果.执行结束后,ldconfig将刷新缓存文件/etc/ld.so.cache.    例3:    # ldconfig /usr/zhsoft/lib  #    注: 当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下"ldconfig 目录名"这个命令.此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库.本例让系统共享了/usr/zhsoft/lib目录下的动态链接库.需要说明的是,如果此目录不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目录里面,则再度运行ldconfig时,此目录下的动态链接库可能不被系统共享了.  3.3 动态链接库如何共享    了解了以上知识,我们可以采用以下三种方法来共享动态链接库注:均须在超级用户状态下操作,以我的动态链接库libmy.so共享过程为例)    (1)拷贝动态链接库到系统共享目录下,或在系统共享目录下为该动态链接库建立个连接(硬连接或符号连接均可,常用符号连接).这里说的系统共享目录,指的是LINUX动态链接库存放的目录,它包含/lib,/usr/lib以及/etc/ld.so.conf文件内所列的一系列目录.    # cp libmy.so /lib  # ldconfig  #    或:    # ln -s `pwd`/libmy.so /lib  # ldconfig  #    (2)将动态链接库所在目录名追加到动态链接库配置文件/etc/ld.so.conf中.    # pwd >> /etc/ld.so.conf  # ldconfig  #     (3)利用动态链接库管理命令ldconfig,强制其搜索指定目录,并更新缓存文件,便于动态装入.    # ldconfig `pwd`  #    需要说明的是,这种操作方法虽然有效,但效果是暂时的,供程序测试还可以,一旦再度运行ldconfig,则缓存文件内容可能改变,所需的动态链接库可能不被系统共享了.与之相比较,前两种方法是可靠的方法,值得业已定型的动态链接库共享时采用.前两种方法还有一个特点,即最后一条命令都是ldconfig,也即均需要更新一下缓存文件,以确保动态链接库的共享生效.      四、含有动态函数的程序的编译      4.1 防止编译因未指定动态链接库而出错    当一个程序使用动态函数时,编译该程序时就必须指定含所用动态函数的动态链接库,否则编译将会出错退出.如本文示例程序ady.c的编译(未明确引用动态链接库libmy.so):    # cc -o ady ady.c  /tmp/ccL4FsJp.o: In function `main':  /tmp/ccL4FsJp.o(.text+0x43): undefined reference to `gettime'  collect2: ld returned 1 exit status  #    注: 因为ady.c所含的动态函数getdate,gettime不在系统函数库中,所以连接时出错.    4.2 编译时引用动态链接库的几种方式    (1)当所用的动态链接库在系统目录(/lib,/usr/lib)下时,可用编译选项-l来引用.即:    # cc -lmy -o ady ady.c  #    注:编译时用-l选项引用动态链接库时,库名须使用其缩写形式.本例的my,表示引用libmy.so库.若引用光标库libncurses.so,须用-lncurses.注意,-l选项与参数之间不能有空格,否则会出错.    (2)当所用的动态链接库在系统目录(/lib,/usr/lib)以外的目录时,须用编译选项-L来指定动态链接库所在的目录(供编译器查找用),同时用-l选项指定缩写的动态链接库名.即:    # cc -L/usr/zzz/lib -lmy -o ady ady.c  #    (3)直接引用所需的动态链接库.即:    # cc -o ady ady.c libmy.so  #    或    # cc -o ady ady.c /lib/libmy.so  #    等等.其中,动态链接库的库名可以采用相对路径形式(文件名不以/开头),也可采用绝对路径形式(文件名以/开头).      五、动态链接程序的运行与检查      5.1 运行    编译连接好含动态函数的程序后,就可以运行它了.动态链接程序因为共享了系统中的动态链接库,所以其空间占用很小.但这并不意味功能的减少,它的执行与静态连接的程序执行,效果完全相同.在命令提示符下键入程序名及相关参数后回车即可,如下例:    $ ady  动态链接库高级应用示范  当前日期: 2002-03-11  当前时间: 19:39:06  $    5.2 检查    检查什么?检查动态链接程序究竟需要哪些共享库,系统中是否已有这些库,没有的话,用户好想办法把这些库装上.    怎么检查呢?这里,告诉你一个实用程序--ldd,这个程序就是专门用来检查动态链接程序依赖哪些共享库的.    ldd命令行用法如下:    ldd [--version] [-v|--verbose] [-d|--data-relocs] [-r|--function-relocs] [--help] FILE...    各选项说明如下:    (1)  --version : 此选项用于打印出ldd的版本号.    (2) -v 或 --verbose : 此选项指示ldd输出关于所依赖的动态链接库的尽可能详细的信息.    (3) -d 或 --data-relocs : 此选项执行重定位,并且显示不存在的函数.    (4) -r 或 --function-relocs : 此选项执行数据对象与函数的重定位,同时报告不存在的对象.    (5) --help : 此选项用于打印出ldd的帮助信息.    注: 上述选项中,常用-v(或--verbose)选项.    ldd的命令行参数为FILE...,即一个或多个文件名(动态链接程序或动态链接库).    例1:    $ ldd ady  libmy.so => ./libmy.so (0x40026000)  libc.so.6 => /lib/libc.so.6 (0x40028000)  /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)  $    注: 每行=>前面的,为动态链接程序所需的动态链接库的名字,而=>后面的,则是运行时系统实际调用的动态链接库的名字,所需的动态链接库在系统中不存在时,=>后面将显示"not found",括号所括的数字为虚拟的执行地址.本例列出ady所需的三个动态链接库,其中libmy.so为自己新建的动态链接库,而libc.so.6与/lib/ld-linux.so.2均为系统的动态链接库,前一个为基本C库,后一个动态装入库(用于动态链接库的装入及运行).    例2:    $ ldd -v ady  libmy.so => ./libmy.so (0x40026000)  libc.so.6 => /lib/libc.so.6 (0x40028000)  /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)    Version information:  ./ady:  libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6  libc.so.6 (GLIBC_2.0) => /lib/libc.so.6  ./libmy.so:  libc.so.6 (GLIBC_2.1.3) => /lib/libc.so.6  libc.so.6 (GLIBC_2.0) => /lib/libc.so.6  /lib/libc.so.6:  ld-linux.so.2 (GLIBC_2.1.1) => /lib/ld-linux.so.2  ld-linux.so.2  (GLIBC_2.2.3) => /lib/ld-linux.so.2  ld-linux.so.2 (GLIBC_2.1) => /lib/ld-linux.so.2  ld-linux.so.2 (GLIBC_2.2) => /lib/ld-linux.so.2  ld-linux.so.2 (GLIBC_2.0) => /lib/ld-linux.so.2  $    注:本例用-v选项以显示尽可能多的信息,所以例中除列出ady所需要的动态链接库外,还列出了程序所需动态链接库版本方面的信息.    小结: 在LINUX动态链接库的高级应用中,关键有两点,一是如何让动态链接库为LINUX系统所共享,二是编译连接程序时如何做.让动态链接库为系统所共享,主要是用ldconfig管理命令,维护好系统共享库的缓存文件/etc/ld.so.cache.编译连接时如何做?注意连接上所用的动态链接库就可以了.LINUX动态链接库的高级应用,用一用就明白:其实,就是这么简单! ********几种不同UNIX系统常用的动态连接库建立的参数说明***** 创建共享库和链接可执行文件类似:首先把源代码编译成目标文件, 然后把目标文件链接起来.目标文件需要创建成 位置无关码(position-independent code) (PIC),概念上就是在可执行程序装载它们的时候, 它们可以放在可执行程序的内存里的任何地方, (用于可执行文件的目标文件通常不是用这个方式编译的.) 链接动态库的命令包含特殊标志,与链接可执行文件的命令是有区别的. --- 至少理论上如此.在一些系统里的现实更恶心.  在下面的例子里,我们假设你的源程序代码在 foo.c 文件里并且将创建成名字叫 foo.so的共享库.中介的对象文件将叫做 foo.o,除非我们另外注明.一个共享库可以 包含多个对象文件,不过我们在这里只用一个.  BSD/OS  创建 PIC 的编译器标志是 -fpic.创建共享库的链接器标志是 -shared.  gcc -fpic -c foo.c ld -shared -o foo.so foo.o 上面方法适用于版本 4.0 的 BSD/OS.  FreeBSD  创建 PIC 的编译器标志是 -fpic.创建共享库的链接器标志是 -shared.  gcc -fpic -c foo.c gcc -shared -o foo.so foo.o 上面方法适用于版本 3.0 的 FreeBSD.  HP-UX   创建 PIC 的系统编译器标志是 +z.如果使用 GCC 则是 -fpic. 创建共享库的链接器标志是 -b.因此  cc +z -c foo.c 或  gcc -fpic -c foo.c 然后  ld -b -o foo.sl foo.o HP-UX 使用 .sl 做共享库扩展,和其它大部分系统不同.  IRIX  PIC 是缺省,不需要使用特殊的编译器选项. 生成共享库的链接器选项是 -shared.  cc -c foo.c ld -shared -o foo.so foo.o Linux  创建 PIC 的编译器标志是 -fpic.在一些平台上的一些环境下, 如果 -fpic 不能用那么必须使用-fPIC. 参考 GCC 的手册获取更多信息. 创建共享库的编译器标志是 -shared.一个完整的例子看起来象:  cc -fpic -c foo.c cc -shared -o foo.so foo.o NetBSD  创建 PIC 的编译器标志是 -fpic.对于 ELF 系统, 带 -shared 标志的编译命令用于链接共享库. 在老的非 ELF 系统里,使用ld -Bshareable.  gcc -fpic -c foo.c gcc -shared -o foo.so foo.o OpenBSD  创建 PIC 的编译器标志是 -fpic. ld -Bshareable 用于链接共享库.  gcc -fpic -c foo.c ld -Bshareable -o foo.so foo.o Solaris  创建 PIC 的编译器命令是用 Sun 编译器时为 -KPIC 而用 GCC 时为 -fpic.链接共享库时两个编译器都可以用 -G 或者用 GCC 时还可以是 -shared.  cc  -KPIC -c foo.c cc -G -o foo.so foo.o 或  gcc -fpic -c foo.c gcc -G -o foo.so foo.o Tru64 UNIX  PIC 是缺省,因此编译命令就是平常的那个. 带特殊选项的 ld 用于链接:  cc -c foo.c ld -shared -expect_unresolved '*' -o foo.so foo.o 用 GCC 代替系统编译器时的过程是一样的;不需要特殊的选项.  UnixWare  SCO 编译器创建 PIC 的标志是-KPIC GCC 是 -fpic. 链接共享库时 SCO 编译器用 -G 而 GCC 用-shared.  cc -K PIC -c foo.c cc -G -o foo.so foo.o or  gcc -fpic -c foo.c gcc -shared -o foo.so foo.o 技巧: 如果你想把你的扩展模块打包,用在更广的发布中,那么你应该考虑使用 GNU Libtool 制作共享库.它把平台之间的区别封装成 了一个通用的并且非常强大的接口.严肃的包还要求考虑有关库版本, 符号解析方法和一些其他的问题.  生成的共享库文件然后就可以装载到 PostgreSQL里面去了.在给 CREATE FUNCTION 命令声明文件名的时候,我们必须声明 共享库文件的名字而不是中间目标文件的名字.请注意你可以在 CREATE FUNCTION 命令上忽略 系统标准的共享库扩展 (通常是.so或.sl), 并且出于最佳的兼容性考虑也应该忽略. ************sdb的使用****************** 首先来看看在哪些情况下需要对程序进行调试。 第一种情况(这是大多数用户都会碰到的),程序在运行过程中忽然跳了出来,屏幕上显示一个xxxx-core dumped消息,然后Shell提示符就又显示出来了,其中xxxx表示出错原因。这种情况的出现一般是系统核心认为进程的执行出现了异常,如进程试图去访问一块不允许它访问的存储区域(Memory Fault,Segmentation Fault);或者扫描某个无终止符的字符串(Bus Error);或者浮点运算溢出或被0除(Arithmetic Exception),等等。此时操作系统会把进程当时的内存映象写到当前目录下的一个名叫core的文件中。这种情况下我们可以使用sdb来检查此core文件,以决定出错的地点以及程序执行的状态,如函数间的调用关系、变量的值,等等。 第二种情况,程序可能并没有什么异常行为,但就是怎么也得不到正确的输出结果。这时需要在该进程运行过程中对之进行调试。这种情况下我们可以使用sdb逐条语句地跟踪程序的执行过程,并在执行过程中检查有关变量的值的变化情况。 上述两种情况并不是绝然分开的。实际上它们可以结合在一起使用。例如,当我们利用core文件对某个已终止的进程进行调试时,可以在sdb中重新启动相应程序的运行,然后对语句的执行进行一些控制。这样我们就能够知道在出现异常之前哪个程序到底是如何动作的。 为了使sdb能够很好地对程序进行调试,在编译程序时应指示编译程序和链接程序在目标代码中加入调试用的各种信息,如程序中的变量名、函数名及其在源程序中的行号等。我们知道,使用-g选项可以完成这一点。如我们可以用如下命令编译前一章给出的有毛病的程序代码: $ cc -o myprog myprog.c myfunc.c myprog.c: myfunc.c: $ ls -l myprog -rwx-xr-x 1 yxz users 4224 Sep 1 10:17 myprog $ cc -g -o myprog myprog.c myfunc.c myprog.c myfunc.c $ ls -l myprog total 26 -rwxr-xr-x 1 yxz users 5404 Sep 1 10:21 myprog $ 这时我们会发现,新生成的myprog比不带-g 选项生成的myprog要大的多。故在程序调试完成之后应将可执行程序中的调试用信息去掉。最简单的方法当然是使用不带-g 选项的cc命令重新编译一遍。另外UNIX系统提供了另外一个名为strip的工具,使用此命令也可以将程序中的调试信息去掉。 现在我们可以试着运行一下那个有问题的程序myprog。在shell提示符下输入: $  myprog 1 111 Arithmetic Exception -core dumped $ 我们看到,程序由于异常而推出了,并且在当前目录下将生成一个名为core 的文件。这个文件有时非常庞大。在文件系统的维护中,有一条就是要定期找出各目录下的core 文件并将其删除掉。 发生此种情况时可以使用sdb来对之进行调试。输入: $ sdb myprog 即可进入sdb调试程序。 sdb将接受三个参数: 待调试的可执行文件名;  待调试的core文件名,一般缺省是core;  由冒号分隔的一个目录表,sdb将在这些目录表中去查找有关的源文件。此目录表的缺省设置是当前目录  有时当前目录下的core文件可能并不是待调试的程序的core 文件,此时用这个core 文件进行调试就是不合适的了。为防止这一点,可在命令行中指定第二个参数为减号(-),如下所示: $ sdb myprog - 这里的"-"告诉sdb忽略当前目录下的core文件。 第三种情况,我们试用对活动过程(正在运行的进程)进行调试的情况。例如,假定某个程序正在后台运行,但我们注意到该程序的某些部分执行起来非常慢,这时我们可以在不杀死这个进程的情况下对之进行调试: $ sdb /proc/1111 这里1111为待调试进程的进程号,用户可以用PS命令得到。系统在/proc目录下用文件的形式保存了每一个活动进程的信息,而文件名正好就是相应的进程号。 指定的进程将在执行时遇到第一个系统调用或调用sdb后收到某个软中断信号时暂停其运行,我们就可以在sdb中检查变量的值、设置断点、恢复执行,等等。在退出sdb时,控制又返回程序,执行进程又从其原停止的地方继续执行。 第四种情况,一般情况下当被调试的活动进程在收到某个软中断信号时sdb会停止该进程。为了防止这一点,可以使用-s  选项。例如: $ sdb -s 14 myprog 将告诉sdb不要因为软中断信号14(闹钟报警信号)而使进程的执行停止。此时该信号被传给相应进程。在程序接收并处理多个软中断信号的情况下,可以使用多个-s选项。 在sdb命令行中还有其他一些选项,对此我们不再一一列举,读者可以参考命令帮助。 **************sdb命令的使用 ************* 同我们前面介绍过的mail,ftp一类工具类似,sdb也是一个命令解释程序。也就是说,用户在sdb提示符(一个星号*)下输入sdb  能够识别的命令,sdb将根据被调试的程序的具体情况给出响应。  例如,在运行myprog出错,生成core文件之后进入sdb时,sdb将给出如下的响应:  $ sdb myprog  12: return ((100/atoi(ValueInput))? TESTOK:! TESTOK);  *  sdb给出来的实际上是程序出错所在的函数,在源程序文件中的行号以及出错那一行的语句。  在sdb的使用中要注意三个“当前”概念:  (1)当前文件 即当前将要被执行的语句所在的那个源程序文件  (2)当前函数 即当前将要被执行的语句所在的那个函数  (3)当前行 这个概念只有在编译时加入-g选项才会有,它指的是将要被执行的那条语句。与当前行相应,有一个行号的概念。  它指的是每条语句在程序中位于第几行。注意行号是从文件头开始计算的,第一行的行号为1,空白行和注释也包括在内。  在用core文件进行调试时,当前行和当前函数分别被设成是程序出错时所执行的那条语句所在地行和函数(如同上面显示出来  的那样)。但如果在编译时未加-g选项,显示出来的将只有函数名和函数的地址了。  在对活动进程进行排错时,sdb将把当前函数和当前行分别设成是main()函数和main()函数的第一个可执行的语句行。  不论是哪种情况,sdb都将显示出*提示符。在此提示符之下我们可以输入各种sdb命令,以控制程序的执行或观察变量的变化   情况,等等。在下面的几个小节中我们将分别详细讨论这些问题。  源程序的显示和搜索  程序出错一般来说不只是出错的那条语句本身造成的。事实上出现错误经常是前面或相关的代码执行了不正确的操作或少了某  些必要的处理。因此调试过程中经常要观察一下源程序中的语句,或者在程序中搜索某个符号出现在什么地方。其中字符串的  搜索功能同vi基本上是相同的,而文件的显示则同另外一个我们没有具体讨论的编辑器ed类似。下面我们将具体介绍这些命令。  1.源程序的显示  在用core进入sdb之后,在*提示符后输入w命令,该命令指示sdb显示源程序中的当前行为中心的前后10行的内容并保持当前行  不变:  * w  7:int  8: TestInput(char * ValueInput)  9: {while ( * ValueInput)  10: if (! isdigit( * ValueInput)) return (! TESTOK);  11: else ValueInput++;  12: return ((100/atoi(ValueInput))? TESTOK:! TESTOK);  13: }  *  我们看到,在进入sdb时,当前行是第12行,以该行为中心的10行内容正好就是上面所显示出来的。其他可以显示源程序语  句的sdb命令如下:  P  显示当前行  l 显示对应于当前指令的那条语句  Z 显示当前行开始的下面10条语句  Ctrl+D 显示当前行之后(不包括当前行)的第10条语句  n 显示第n条语句,这里n是一个数  注意这些命令显示出的是源程序语句还是汇编语句(后面我们将要介绍)取决于最近一次显示出的是什么。  2.改变当前行  在用户显示语句时,当前行也会相应地发生变化。例如,Z命令将使当前行向程序尾移动9行,而Ctrl+D则使当前行向后移  动10行。  在使用数字来显示某行语句时将使该行语句成为当前行。而在*提示符之后按一下回车,当前行将下移一行。例如,接着上面  的例子,输入:  * 8p  8: TEstInput(char * ValueInput)  * 回车  9: { while ( * ValueInput)}  *  这里8p实际上是两条命令的组合。它使当前行移至源文件的第八行,然后再显示出新的当前行。按回车键将使当前行后移一行。  3.改变当前源文件  在vi中我们可以用e命令对另外某个文件进行编辑。sdb也提供了e命令,可以用此命令来改变当前文件,如:  * e myprog.c  current file is now myprog.c  *  8p  8: main(int argc,char * argv[])  *  我们看到,当前文件改变之后,sdb将第一行设为是当前行。如果此文件的第一行是个函数,那么该函数便成为当前函数。  否则将临时出现没有当前函数的情况。  在上一节中,我们介绍过在命令行中可以指定源文件搜索目录名列表(缺省情况为当前目录)。如果某个文件不在此搜索  目录中,则可以用e命令将其加入:  * e Another SourceDir  这里Another SourceDir是一个目录名。如果要显示该目录下的某个文件,只需要输入:  * e FileName.c  当然直接使用:  * e Another SourceDir/FileName.c  也能达到同样的效果。  使用:  * e FunctionName  将使包含函数FunctionName的文件名成为当前文件,而当前函数不言而喻将成为FunctionName。当前行则理所当然的是该  函数的第一行。同一程序中函数名在各模块中的唯一性保证了这一点是能够成功的,但如果包含指定函数的文件不在当前  搜索目录列表中,则必须用e命令将其加入。  4.字符串的搜索  在vi中,我们可以在命令方式下使用“/“或者“?”命令,从当前位置向后或者向前搜索某个字符串,在sdb中也同样可  以完成这一点。使用这两个命令我们可以查找源程序中某个或某类符号的出现。之所以说某类,是因为我们可以用正规表  达式来指定待搜索的串(也即在搜索串中可以使用*,?,[,],-,^这类特殊字符)。   例如,为了查找myprog.c中argv出现在那些行上,可输入:  * /argv/  8: main(ini argc,char * argv[])  sdb将从当前行开始向文件尾搜索,到达文件尾之后又从文件头开始直至搜索到某个匹配的串或到达当前行为止。  与/相反,?命令将从当前行向文件头方向搜索,因此如果我们将上述/argv/换成:  * ? argv?  14: printf("The %dth value' %s'\tis BAD! \n",i,argv[i]);  *  所得的结果一般是不同的。  /或?命令之后的/或?并不是必须的。另外如果要在同一方向上继续搜索上次搜索过的串,只需要直接输入/或者?即可。 ***************ld是怎么连接的********************** 由於静态与共享程式库两者间不相容的格式的差异性与动词*link*过量使用於指称*编译完成後的事情*与*当编译好的程式使用时所发生的事情*这两件事上头,使得这一章节变得复杂了许多。( and, actually, the overloading of the word `load' in a comparable but opposite sense)不过,再复杂也就是这样了,所以阁下不必过於担心。  为了稍微减轻读者的困惑,我们称执行期间所发生的事为*动态载入*,这一主题会在下一章节中谈到。你也会在别的地方看到我把动态载入描述成*动态连结*,不过不会是在这一章节中。换句话说,这一章节所谈的,全部是指发生在编译结束後的连结。  6.1 共享程式库 vs静态程式库  建立程式的最後一个步骤便是连结;也就是将所有分散的小程式组合起来,看看是否遗漏了些什麽。显然,有一些事情是很多程式都会想做的---例如,开启档案,接著所有与开档有关的小程式就会将储存程式库的相关档案提供给你的程式使用。在一般的Linux系统上,这些小程式可以在/lib与/usr/lib/目录底下找到。  当你用的是静态的程式库时,连结器会找出程式所需的模组,然後实际将它们拷贝到执行档内。然而,对共享程式库而言,就不是这样了。共享程式库会在执行档内留下一个记号,指明*当程式执行时,首先必须载入这个程式库*。显然,共享程式库是试图使执行档变得更小,等同於使用更少的记忆体与磁碟空间。Linux内定的行为是连结共享程式库,只要Linux能找到这些共享程式库的话,就没什麽问题;不然,Linux就会连结静态的了。如果你想要共享程式库的话,检查这些程式库(*.sa  for a.out, *.so for ELF)是否住在它们该在的地方,而且是可读取的。  在Linux上,静态程式库会有类似libname.a这样的名称;而共享程式库则称为libname.so.x.y.z,此处的x.y.z是指版本序号的样式。共享程式库通常都会有连结符号指向静态程式库(很重要的)与相关联的.sa档案。标准的程式库会包含共享与静态程式库两种格式。  你可以用ldd(List Dynamic Dependencies)来查出某支程式需要哪些共享程式库。 $ ldd /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18  这是说在我的系统上,WWW浏览器*lynx*会依赖libc.so.5 (the C library)与libncurses.so.1(终端机萤幕的控制)的存在。若某支程式缺乏独立性, ldd就会说‘statically linked’或是‘statically linked (ELF)’。  6.2 终极审判(‘sin() 在哪个程式库里?’)  nm 程式库名称应该会列出此程式库名称所参考到的所有符号。这个指令可以应用在静态与共享程式库上。假设你想知道tcgetattr()是在哪儿定义的:你可以如此做,  $ nm libncurses.so.1 |grep tcget U tcgetattr  *U*指出*未定义*---也就是说ncurses程式库有用到tegetattr(),但是并没有定义它。你也可以这样做,  $ nm libc.so.5 | grep tcget 00010fe8 T __tcgetattr 00010fe8 W tcgetattr 00068718 T tcgetpgrp  *W*说明了*弱态(weak)*,意指符号虽已定义,但可由不同程式库中的另一定义所替代。最简单的*正常*定义(像是tcgetpgrp)是由*T*所标示:  标题所谈的问题,最简明的答案便是libm.(so|a)了。所有定义在的函数都保留在maths程式库内;因此,当你用到其中任何一个函数时,都需要以-lm的参数连结此程式库。  6.3 X档案?  ld: Output file requires shared library `libfoo.so.1`  ld与其相类似的命令在搜寻档案的策略上,会依据版本的差异而有所不同,但是唯一一个你可以合理假设的内定目录便是/usr/lib了。如果你希望身处它处的程式库也列入搜寻的行列中,那麽你就必须以-L选项告知gcc或是ld。   要是你发现一点效果也没有,就赶紧察看看那档案是不是还乖乖的躺在原地。就a.out而言,以-lfoo参数来连结,会驱使ld去寻找libfoo.sa(shared stubs);如果没有成功,就会换成寻找libfoo.a(static)。就ELF而言, ld会先找libfoo.so,然後是libfoo.a。libfoo.so通常是一个连结符号,连结至libfoo.so.x。  6.4 建立你自己的程式库 控制版本  与其它任何的程式一样,程式库也有修正不完的bugs的问题存在。它们也可能产生出一些新的特点,更改目前存在的模组的功效,或是将旧的移除掉。这对正在使用它们的程式而言,可能会是一个大问题。如果有一支程式是根据那些旧的特点来执行的话,那怎麽办?  所以,我们引进了程式库版本编号的观念。我们将程式库*次要*与*主要*的变更分门别类,同时规定*次要*的变更是不允许用到这程式库的旧程式发生中断的现象。你可以从程式库的档名分辨出它的版本(实际上,严格来讲,对ELF而言仅仅是一场天大的谎言;继续读将下去,便可明白为什麽了): libfoo.so.1.2的主要版本是1,次要版本是2。次要版本的编号可能真有其事,也可能什麽都没有---libc在这一点上用了*修正程度*的观念,而订出了像libc.so.5.2.18这样的程式库名称。次要版本的编号内若是放一些字母、底线、或是任何可以列印的ASCII字元,也是很合理的。  ELF与a.out格式最主要的差别之一就是在设置共享程式库这件事上;我们先看ELF,因为它比较简单一些。  ELF?它到底是什麽东东ㄋㄟ?  ELF(Executable and Linking Format)最初是由USL(UNIX System Laboratories)发展而成的二进位格式,目前正应用於Solaris与System V Release 4上。由於ELF所增涨的弹性远远超过Linux过去所用的a.out格式,因此GCC与C程式库的发展人士於1995年决定改用ELF为Linux标准的二进位格式。  怎麽又来了?  这一节是来自於‘/news-archives/comp.sys.sun.misc’的文件。  ELF(“Executable Linking Format”)是於SVR4所引进的新式改良目的档格式。ELF比起COFF可是多出了不少的功能。以ELF而言,它*是*可由使用者自行延伸的。ELF视一目的档为节区(sections),如串列般的组合;而且此串列可为任意的长度(而不是一固定大小的阵列)。这些节区与COFF的不一样,并不需要固定在某个地方,也不需要以某种顺序排列。如果使用者希望补捉到新的资料,便可以加入新的节区到目的档内。ELF也有一个更强而有力的除错法式,称为DWARF(Debugging  With Attribute Record Format)□目前Linux并不完全支援。DWARF DIEs(Debugging Information Entries)的连结串列会在ELF内形成 .debug的节区。DWARF DIEs的每一个 .debug节区并非一些少量且固定大小的资讯记录的集合,而是一任意长度的串列,拥有复杂的属性,而且程式的资料会以有□围限制的树状资料结构写出来。DIEs所能补捉到的大量资讯是COFF的 .debug节区无法望其项背的。(像是C++的继承图。)  ELF档案是从SVR4(Solaris 2.0 ?)ELF存取程式库(ELF access library)内存取的。此程式库可提供一简便快速的介面予ELF。使用ELF存取程式库最主要的恩惠之一便是,你不再需要去察看一个ELF档的qua了。就UNIX的档案而言,它是以Elf*的型式来存取;呼叫elf_open()之後,从此时开始,你只需呼叫elf_foobar()来处理档案的某一部份即可,并不需要把档案实际在磁碟上的image搞得一团乱。  ELF的优缺点与升级至ELF等级所需经历的种种痛苦,已在ELF-HOWTO内论及;我并不打算在这儿涂浆糊。ELF HOWTO应该与这份文件有同样的主题才是。  ELF共享程式库  若想让libfoo.so成为共享程式库,基本的步骤会像下面这样:  $ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o $ ln -s libfoo.so.1.0 libfoo.so.1 $ ln -s libfoo.so.1 libfoo.so $ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH  这会产生一个名为libfoo.so.1.0的共享程式库,以及给予ld适当的连结(libfoo.so)还有使得动态载入程式(dynamic loader)能找到它(libfoo.so.1)。为了进行测试,我们将目前的目录加到LD_LIBRARY_PATH里。  当你津津乐道於程式库制做成功之时,别忘了把它移到如/usr/local/lib的目录底下,并且重新设定正确的连结路径。libfoo.so.1与libfoo.so.1.0的连结会由ldconfig依日期不断的更新,就大部份的系统来说,ldconfig会在开机过程中执行。libfoo.so的连结必须由手动方式更新。如果你对程式库所有组成份子(如标头档等)的升级,总是抱持著一丝不□的态度,那麽最简单的方法就是让libfoo.so -> libfoo.so.1;如此一来,ldconfig便会替你同时保留最新的连结。要是你没有这麽做,你自行设定的东东就会在数日後造成千奇百怪的问题出现。到时候,可别说我没提醒你啊!  $ su # cp libfoo.so.1.0 /usr/local/lib # /sbin/ldconfig # ( cd /usr/local/lib ; ln -s libfoo.so.1 libfoo.so   版本编号、soname与符号连结  每一个程式库都有一个soname。当连结器发现它正在搜寻的程式库中有这样的一个名称,连结器便会将soname箝入连结中的二进位档内,而不是它正在运作的实际的档名。在程式执行期间,动态载入程式会搜寻拥有soname这样的档名的档案,而不是程式库的档名。因此,一个名为libfoo.so的程式库,就可以有一个libbar.so的soname了。而且所有连结到libbar.so的程式,当程式开始执行时,会寻找的便是libbar.so了。   这听起来好像一点意义也没有,但是这一点,对於了解数个不同版本的同一个程式库是如何在单一系统上共存的原因,却是关键之钥。Linux程式库标准的命名方式,比如说是libfoo.so.1.2,而且给这个程式库一个libfoo.so.1的soname。如果此程式库是加到标准程式库的目录底下(e.g. /usr/lib),ldconfig会建立符号连结libfoo.so.1 -> libfoo.so.1.2,使其正确的image能於执行期间找到。你也需要连结libfoo.so -> libfoo.so.1,使ld能於连结期间找到正确的soname。  所以罗,当你修正程式库内的bugs,或是添加了新的函数进去(任何不会对现存的程式造成不利的影响的改变),你会重建此程式库,保留原本已有的soname,然後更改程式库档名。当你对程式库的变更会使得现有的程式中断,那麽你只需增加soname中的编号---此例中,称新版本为libfoo.so.2.0,而soname变成libfoo.so.2。紧接著,再将libfoo.so的连结转向新的版本;至此,世界又再度恢复了和平!  其实你不须要以此种方式来替程式库命名,不过这的确是个好的传统。ELF赋予你在程式库命名上的弹性,会使得人气喘呼呼的搞不清楚状况;有这样的弹性在,也并不表示你就得去用它。  ELF总结:假设经由你睿智的观察发现有个惯例说:程式库主要的升级会破坏相容性;而次要的升级则可能不会;那麽以下面的方式来连结,所有的一切就都会相安无事了。  gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor  a.out---旧旧的格式□  建立共享程式库的便利性是升级至ELF的主要原因之一。那也是说,a.out可能还是有用处在的。上ftp站去抓 ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz;解压缩後你会发现有20页的文件可以慢慢的读哩。我很不喜欢自己党派的偏见表现得那麽的淋璃尽致,可是从上下文间,应该也可以很清楚的嗅出我从来不拿石头砸自己的脚的脾气吧!  ZMAGIC vs QMAGIC  QMAGIC是一种类似旧格式的a.out(亦称为ZMAGIC)的可执行档 格式,这种格式会使得第一个分页无法map。当0-4096的□围内没有mapping存在时,则可允许NULL dereference trapping更加的容易。所产生的边界效应是你的执行档会比较小(大约少1K左右)。   只有即将作废的连结器有支援ZMAGIC,一半已埋入棺材的连结器有支援这两种格式;而目前的版本仅支援QMAGIC而已。事实上,这并没有多大的影响,那是因为目前的核心两种格式都能执行。  *file*命令应该可以确认程式是不是QMAGIC的格式的。  档案配置  一a.out(DLL)的共享程式库包含两个真实的档案与一个连结符号。就*foo*这个用於整份文件做为□例的程式库而言,这些档案会是libfoo.sa与libfoo.so.1.2;连结符号会是libfoo.so.1,而且会指向libfoo.so.1.2。这些是做什麽用的?  在编译时,ld会寻找libfoo.sa。这是程式库的*stub*档案。而且含有所有执行期间连结所需的exported的资料与指向函数的指标。  执行期间,动态载入程式会寻找libfoo.so.1。这仅仅是一个符号连结,而不是真实的档案。故程式库可更新成较新的且已修正错误的版本,而不会损毁任何此时正在使用此程式库的应用程式。在新版---比如说libfoo.so.1.3---已完整呈现时,ldconfig会以一极微小的操作,将连结指向新的版本,使得任何原本使用旧版的程式不会感到丝毫的不悦。  DLL程式库(我知道这是无谓的反覆---所以对我提出诉讼吧!)通常会比它们的静态副本要来得大多。它们是以*洞(holes)*的形式来保留空间以便日後的扩充。这种*洞*可以不占用任何的磁碟空间。一个简单的cp呼叫,或是使用makehole程式,就可以达到这样效果。因为它们的位址是固定在同一位置上,所以在建立程式库後,你可以把它们拿掉。不过,千万不要试著拿掉ELF的程式库。  ``libc-lite''?  libc-lite是轻量级的libc版本。可用来存放在磁碟片上,也 **********************动态载入过程*************** Linux有共享程式库,如果之前你已坐著读完上一章节,想必现在一听到像这样的说词,便会立刻感到头昏。有一些照惯例而言是在连结时期便该完成的工作,必须延迟到载入时期才能完成。 7.2 错误讯息  把你连结的错误寄给我!我不会做任何的事,不过我可以把它们写起来**  can't load library: /lib/libxxx.so, Incompatible version  (a. out only) 这是指你没有xxx程式库的正确的主要版本。可别以为随随 便便弄个连结到你目前拥有的版本就可以了,如果幸运的话,就只会造成你的程式分页错误而已。去抓新的版本.ELF类似的情况会造成像下面这样的讯息:   ftp: can't load library 'libreadline.so.2'  warning using incompatible library version xxx  (a. out only)你的程式库的次要版本比起这支程式用来编译的还要旧。程式依然可以执行。只是可能啦!我想,升个级应该没什麽伤害吧!  7.3 控制动态载入器的运作  有一组环境变数会让动态载入器有所反应。大部份的环境变数对ldd的用途要比起对一般users的还要来得更多。而且可以很方便的设定成由ldd配合各种参数来执行。这些变数包括,  LD_BIND_NOW --- 正常来讲,函数在呼叫之前是不会让程式寻找的。设定这个旗号会使得程式库一载入,所有的寻找便会发生,同时也造成起始的时间较慢。当你想测试程式,确定所有的连结都没有问题时,这项旗号就变得很有用。 LD_PRELOAD可以设定一个档案,使其具有*覆盖*函数定义的能力。例如,如果你要测试记忆体分配的方略,而且还想置换*malloc*,那麽你可以写好准备替换的副程式,并把它编译成mallolc.,然後: $ LD_PRELOAD=malloc.o; export LD_PRELOAD $ some_test_program LD_ELF_PRELOAD 与 LD_AOUT_PRELOAD 很类似,但是仅适用於正确的二进位型态。如果设定了 LD_something_PRELOAD 与 LD_PRELOAD ,比较明确的那一个会被用到。 LD_LIBRARY_PATH是一连串以分号隔离的目录名称,用来搜寻共享程式库。对ld而言,并没有任何的影响;这项只有在执行期间才有影响。另外,对执行setuid与setgid的程式而言,这一项是无效的。而LD_ELF_LIBRARY_PATH与LD_AOUT_LIBRARY_PATH这两种旗号可根据各别的二进位型式分别导向不同的搜寻路径。一般正常的运作下,不应该会用到LD_LIBRARY_PATH;把需要搜寻的目录加到/etc/ld.so.conf/里;然後重新执行ldconfig。 LD_NOWARN 仅适用於a.out。一旦设定了这一项(LD_NOWARN=true; export LD_NOWARN),它会告诉载入器必须处理fatal-warnings(像是次要版本不相容等)的警告讯息。 LD_WARN仅适用於ELF。设定这一项时,它会将通常是致命讯息的“Can*t find library”转换成警告讯息。对正常的操作而言,这并没有多大的用处,可是对ldd就很重要了。 LD_TRACE_LOADED_OBJECTS仅适用於ELF。而且会使得程式以为它们是由ldd所执行的: $ LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18  7.4 以动态载入撰写程式  如果你很熟悉Solaris 2.x所支援的动态载入的工作的话,你会发现Linux在这点上与其非常的相近。这一部份在H.J.Lu的ELF程式设计文件内与dlopen(3)的manual page(可以在ld.so的套件上找到)上有广泛的讨论。这里有个不错的简单□例:以-ldl连结。  #include   #include  main() { void *libc; void (*printf_call)(); if(libc=dlopen("/lib/libc } ****不同的硬件环境下给GCC指定哪些参数才可以得到最佳的性能**** [code:1:42d9ccecec] 一、1.2版(gcc 2.9.x版) i386 (Intel), do you really want to install gentoo on that? CHOST="i386-pc-linux-gnu" CFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer" i486 (Intel), do you really want to install gentoo on that? CHOST="i486-pc-linux-gnu" CFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer" Pentium, Pentium MMX+, Celeron (Mendocino) (Intel) CHOST="i586-pc-linux-gnu" CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer" Pentium Pro/II/III/4, Celeron (Coppermine), Celeron (Willamette?) (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer" Eden C3/Ezra (Via) CHOST="i586-pc-linux-gnu" CFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer" Quote : I did the original gentoo install using 1.2, with gcc 2.95 using -march=i586. i686 won't work. K6 or beyond (AMD) CHOST="i586-pc-linux-gnu" CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer" (A Duron will report "Athlon" in its /proc/cpuinfo) Athlon (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=k6  -O3 -pipe -fomit-frame-pointer" For the following, i don't know of any flag that enhance performances..., do you ? PowerPC CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer" CXXFLAGS="-O3 -pipe -fomit-frame-pointer" Sparc CHOST="sparc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer" CXXFLAGS="-O3 -pipe -fomit-frame-pointer" Sparc 64 CHOST="sparc64-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer" CXXFLAGS="-O3 -pipe -fomit-frame-pointer" 二、1.4版(gcc 3.x版): i386 (Intel), do you really want to install gentoo on that ? CHOST="i386-pc-linux-gnu" CFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer" i486 (Intel), do you really want to install gentoo on that ? CHOST="i486-pc-linux-gnu" CFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer" Pentium 1 (Intel) CHOST="i586-pc-linux-gnu" CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer" Pentium MMX (Intel) CHOST="i586-pc-linux-gnu" CFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer" Pentium  PRO (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer" Pentium II (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer" Celeron (Mendocino), aka Celeron1 (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer" Pentium III (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer" Celeron (Coppermine) aka Celeron2 (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer" Celeron (Willamette?) (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer" Pentium 4 (Intel) CHOST="i686-pc-linux-gnu" CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer" Eden C3/Ezra (Via) CHOST="i586-pc-linux-gnu" CFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer" quote : the ezra doesn't have any special instructions that you could optimize for, just consider is a K6-3...basically a p2 with 3dnow K6  (AMD) CHOST="i586-pc-linux-gnu" CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer" K6-2 (AMD) CHOST="i586-pc-linux-gnu" CFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer" K6-3 (AMD) CHOST="i586-pc-linux-gnu" CFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer" Athlon (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer" Athlon-tbird, aka K7 (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer" Athlon-tbird XP (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer" Athlon 4(AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer" Athlon XP (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer" Athlon MP (AMD) CHOST="i686-pc-linux-gnu" CFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer" CXXFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer" 603  (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" 603e (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" 604 (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" 604e (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" 750 aka as G3 (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer -fsigned-char" Note: do not use -march= 7400, aka G4 (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer -fsigned-char -maltivec" CXXFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer -fsigned-char -maltivec" Note: do not use -march= 7450, aka G4 second generation (PowerPC) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer -fsigned-char -maltivec" CXXFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer -fsigned-char  -maltivec" Note: do not use -march= PowerPC (If you don't know which one) CHOST="powerpc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char" Sparc CHOST="sparc-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer" CXXFLAGS="-O3 -pipe -fomit-frame-pointer" Sparc 64 CHOST="sparc64-unknown-linux-gnu" CFLAGS="-O3 -pipe -fomit-frame-pointer" CXXFLAGS="-O3 -pipe -fomit-frame-pointer"  [/code:1:42d9ccecec] ********gcc常用的编译选项对代码的影响 ******** ★ 前言  本文讨论gcc的一些常用编译选项对代码的影响。当然代码变了,  它的内存布局也就会变了,随之exploit也就要做相应的变动。  gcc的编译选项实在太多,本文检了几个最常用的选项。  ★ 演示程序  [alert7@redhat62 alert7]$ cat > test.c  #include  void hi(void)  {  printf("hi");  }  int main(int argc, char *argv[])  {  hi();  return 0;  }  ★ 一般情况  [alert7@redhat62 alert7]$ gcc -o test test.c  [alert7@redhat62 alert7]$ wc -c test  11773 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483e4 : push %ebp  0x80483e5 : mov %esp,%ebp  0x80483e7 : call 0x80483d0  0x80483ec : xor %eax,%eax  0x80483ee : jmp 0x80483f0  0x80483f0 : leave  0x80483f1 : ret  ....  End of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483d0 : push %ebp  0x80483d1 : mov %esp,%ebp  0x80483d3 : push $0x8048450  0x80483d8 : call 0x8048308  0x80483dd : add $0x4,%esp  0x80483e0 : leave  0x80483e1 : ret  0x80483e2 : mov %esi,%esi  End of assembler dump.  来看看部分的内存映象  (内存高址)  +--------+  |bffffbc4| argv的地址(即argv[0]的地址)  0xbffffb84 +--------+  |00000001| argc的值  0xbffffb80 +--------+  |400309cb|main的返回地址  0xbffffb7c +--------+ <-- 调用main函数前的esp  |bffffb98| 调用main函数前的ebp  0xbffffb78 +--------+ <-- main函数的ebp  |080483ec| hi()的返回地址  0xbffffb74 +--------+  |bffffb78| 调用hi()前的esp  0xbffffb70  +--------+  |08048450| "hi"的地址  0xbffffb6c +--------+  | ...... |  (内存低址)  leave 指令所做的操作相当于MOV ESP,EBP 然后 POP EBP  ret 指令所做的操作相当于POP EIP  ★ -O 编译选项  With `-O', the compiler tries to reduce code size and execution time.  When you specify `-O', the two options `-fthread-jumps' and  `-fdefer-pop' are turned on  优化,减少代码大小和执行的时间  [alert7@redhat62 alert7]$ gcc -O -o test test.c  [alert7@redhat62 alert7]$ wc -c test  11757 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483d8 : push %ebp  0x80483d9 : mov %esp,%ebp  0x80483db : call 0x80483c8  0x80483e0 : xor %eax,%eax  0x80483e2 : leave  0x80483e3 : ret  0x80483e4 : nop  ...  End of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483c8 : push %ebp  0x80483c9 : mov %esp,%ebp  0x80483cb : push $0x8048440  0x80483d0 : call 0x8048308  0x80483d5 : leave  0x80483d6 : ret  0x80483d7 : nop  End of assembler dump.  在main()中,把一条jmp指令优化掉了,很显然,这条指令是可以不需要的。  在hi()中,把add  $0x4,%esp优化掉了,这会不会使stack不平衡呢?  来看看部分的内存映象  (内存高址)  +--------+  |bffffbc4| argv的地址(即argv[0]的地址)  0xbffffb84 +--------+  |00000001| argc的值  0xbffffb80 +--------+  |400309cb|main的返回地址  0xbffffb7c +--------+ <-- 调用main函数前的esp  |bffffb98| 调用main函数前的ebp  0xbffffb78 +--------+ <-- main函数的ebp  |080483e0| hi()的返回地址  0xbffffb74 +--------+  |bffffb78| 调用hi()前的esp  0xbffffb70 +--------+  |08048440| "hi"的地址  0xbffffb6c +--------+  | ...... |  (内存低址)  leave 指令所做的操作相当于把MOV ESP,EBP 然后 POP EBP  看到leave指令操作了没有,先把ebp-->esp,再pop ebp,这样即使  在过程内堆栈的esp,ebp是不平衡的,但只要返回时候碰到leave指令  就会平衡了,所以把add $0x4,%esp优化掉也是没有问题的。  ★ -O2 编译选项  -O2 Optimize even more. Nearly all supported optimizations that do  not involve a space-speed tradeoff are performed. Loop unrolling  and function inlining are not done, for example. As compared to -O,  this option increases both compilation time and the performance of  the generated code.  [alert7@redhat62 alert7]$ gcc -O2 -o test test.c  [alert7@redhat62 alert7]$ wc -c test  11757 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483d8 : push %ebp  0x80483d9 : mov %esp,%ebp  0x80483db : call 0x80483c8  0x80483e0  : xor %eax,%eax  0x80483e2 : leave  0x80483e3 : ret  ...  0x80483ef : nop  End of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483c8 : push %ebp  0x80483c9 : mov %esp,%ebp  0x80483cb : push $0x8048440  0x80483d0 : call 0x8048308  0x80483d5 : leave  0x80483d6 : ret  0x80483d7 : nop  End of assembler dump.  由于程序比较简单,再优化也没有好优化的了,所以跟-O出来的一样。  ★ -fomit-frame-pointer 编译选项  -fomit-frame-pointer  Don't keep the frame pointer in a register for functions  that don't need one. This avoids the instructions to save,  set up and restore frame pointers; it also makes an extra  register available in many functions. It also makes  debugging impossible on most machines.  忽略帧指针。这样在程序就不需要保存,安装,和恢复ebp了。这样ebp也就是一个  free的register了,在函数中就可以随便使用了。  [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -o test test.c  [alert7@redhat62 alert7]$ wc -c test  11773 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483e0 : call 0x80483d0  0x80483e5 : xor %eax,%eax  0x80483e7 : jmp 0x80483f0  0x80483e9 : lea 0x0(%esi,1),%esi  0x80483f0 : ret  ....  End  of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483d0 : push $0x8048450  0x80483d5 : call 0x8048308  0x80483da : add $0x4,%esp  0x80483dd : ret  0x80483de : mov %esi,%esi  End of assembler dump.  在main()和hi()中都去掉了以下指令  push %ebp  mov %esp,%ebp//这两条指令安装  leave//这条指令恢复  来看看部分的内存映象  (内存高址)  +--------+  |bffffbc4| argv的地址(即argv[0]的地址)  0xbffffb84 +--------+  |00000001| argc的值  0xbffffb80 +--------+  |400309cb|main的返回地址  0xbffffb7c +--------+  |080483e5| hi()的返回地址  0xbffffb78 +--------+  |08048450| "hi"字符串的地址  0xbffffb74 +--------+  | ...... |  (内存低址)  没有保存上层执行环境的ebp.  ★ -fomit-frame-pointer && -O2  -fomit-frame-pointer编译选项去掉了  push %ebp  mov %esp,%ebp//这两条指令安装  leave//这条指令恢复  -O2编译选项去掉了  add $0x4,%esp  两个加起来会不会这四条指令一起去掉,从而使stack不平衡呢?  [alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -O2 -o test test.c  [alert7@redhat62  alert7]$ wc -c test  11741 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483d8 : call 0x80483c8  0x80483dd : xor %eax,%eax  0x80483df : ret  End of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483c8 : push $0x8048430  0x80483cd : call 0x8048308  0x80483d2 : add $0x4,%esp  0x80483d5 : ret  0x80483d6 : mov %esi,%esi  End of assembler dump.  来看看部分的内存映象  (内存高址)  +--------+  |bffffbc4| argv的地址(即argv[0]的地址)  0xbffffb84 +--------+  |00000001| argc的值  0xbffffb80 +--------+  |400309cb|main的返回地址  0xbffffb7c +--------+  |080483dd| hi()的返回地址  0xbffffb78 +--------+  |08048430| "hi"字符串的地址  0xbffffb74 +--------+  | ...... |  (内存低址)  此时就没有把add $0x4,%esp优化掉,如果优化掉的话,整个stack就  会变的不平衡,从而会导致程序出错。  ★ -fPIC 编译选项  -fPIC If supported for the target machine, emit position-independent  code, suitable for dynamic linking,even if branches need large  displacements.  产生位置无关代码(PIC),一般创建共享库时用到。  在x86上,PIC的代码的符号引用都是通过ebx进行操作的。  [alert7@redhat62  alert7]$ gcc -fPIC -o test test.c  [alert7@redhat62 alert7]$ wc -c test  11805 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80483f8 : push %ebp  0x80483f9 : mov %esp,%ebp  0x80483fb : push %ebx  0x80483fc : call 0x8048401  0x8048401 : pop %ebx//取得该指令的地址  0x8048402 : add $0x1093,%ebx//此时ebx里面存放着是GOT表的地址  0x8048408 : call 0x80483d0  0x804840d : xor %eax,%eax  0x804840f : jmp 0x8048411  0x8048411 : mov 0xfffffffc(%ebp),%ebx  0x8048414 : leave  0x8048415 : ret  ...  End of assembler dump.  (gdb) disass hi  Dump of assembler code for function hi:  0x80483d0 : push %ebp  0x80483d1 : mov %esp,%ebp  0x80483d3 : push %ebx  0x80483d4 : call 0x80483d9  0x80483d9 : pop %ebx  0x80483da : add $0x10bb,%ebx  0x80483e0 : lea 0xffffefdc(%ebx),%edx  0x80483e6 : mov %edx,%eax  0x80483e8 : push %eax  0x80483e9 : call 0x8048308  0x80483ee : add $0x4,%esp  0x80483f1 : mov 0xfffffffc(%ebp),%ebx  0x80483f4 : leave  0x80483f5 : ret  0x80483f6 : mov %esi,%esi  End of assembler dump.  来看看部分的内存映象  (内存高址)  +--------+  |bffffbc4| argv的地址(即argv[0]的地址)  0xbffffb84  +--------+  |00000001| argc的值  0xbffffb80 +--------+  |400309cb|main的返回地址  0xbffffb7c +--------+ <-- 调用main函数前的esp  |bffffb98| 调用main函数前的ebp  0xbffffb78 +--------+ <-- main函数的ebp  |401081ec| 保存的ebx  0xbffffb74 +--------+  |0804840d| (存放过call 0x8048401的下一条指令地址)  0xbffffb70 +--------+  |bffffb78| 调用hi()前的esp  0xbffffb6c +--------+  |08049494| GOT表地址  0xbffffb68 +--------+  |08048470|(存放过call 0x80483d9的下一条指令地址)  0xbffffb64 +--------+  | ...... |  (内存低址)  ★ -static 编译选项  -static  On systems that support dynamic linking, this prevents  linking with the shared libraries. On other systems,  this option has no effect.  把一些函数都静态的编译到程序中,而无需动态链接了。  [alert7@redhat62 alert7]$ gcc -o test -static test.c  [alert7@redhat62 alert7]$ wc -c test  962808 test  [alert7@redhat62 alert7]$ gdb -q test  (gdb) disass main  Dump of assembler code for function main:  0x80481b4 : push %ebp  0x80481b5 : mov %esp,%ebp  0x80481b7 : call 0x80481a0  0x80481bc : xor %eax,%eax  0x80481be : jmp 0x80481c0  0x80481c0 : leave  0x80481c1 : ret  ...  End of assembler dump.  (gdb)  disass hi  Dump of assembler code for function hi:  0x80481a0 : push %ebp  0x80481a1 : mov %esp,%ebp  0x80481a3 : push $0x8071528  0x80481a8 : call 0x804865c  0x80481ad : add $0x4,%esp  0x80481b0 : leave  0x80481b1 : ret  0x80481b2 : mov %esi,%esi  End of assembler dump.  [alert7@redhat62 alert7]$ ldd test  not a dynamic executable  -static出来的代码已经没有PLT了,GOT虽然有,已经全部为0了。 ****************gcc使用手册***************** 使用语法:    gcc [ option | filename ]...    g++ [ option | filename ]...    其中 option 为 gcc 使用时的选项(后面会再详述),    而 filename 为欲以 gcc 处理的文件  说明:    这 C 与 C++ 的 compiler 已将产生新程序的相关程序整合起来。产  生一个新的程序需要经过四个阶段:预处理、编译、汇编、连结,而这两  个编译器都能将输入的文件做不同阶段的处理。虽然原始程序的扩展名可  用来分辨编写原始程序码所用的语言,但不同的compiler,其预设的处理  程序却各不相同:    gcc  预设经由预处理过(扩展名为.i)的文件为 C 语言,并於程式        连结阶段以 C 的连结方式处理。    g++  预设经由预处理过(扩展名为.i)的文件为 C++ 语言,并於程  序连结阶段以 C++ 的连结方式处理。    原始程序码的扩展名指出所用编写程序所用的语言,以及相对应的处  理方法:    .c  C 原始程序         ;  预处理、编译、汇编    .C  C++ 原始程序        ;  预处理、编译、汇编    .cc  C++ 原始程序        ;  预处理、编译、汇编    .cxx C++ 原始程序        ;  预处理、编译、汇编    .m  Objective-C  原始程序    ;  预处理、编译、汇编    .i  已经过预处理之 C 原始程序  ;  编译、汇编    .ii  已经过预处理之 C++ 原始程序 ;  编译、汇编    .s  组合语言原始程序      ;  汇编    .S  组合语言原始程序      ;  预处理、汇编    .h  预处理文件(标头文件)    ;  (不常出现在指令行)    其他扩展名的文件是由连结程序来处理,通常有:    .o  Object file    .a  Archive file    除非编译过程出现错误,否则 "连结" 一定是产生一个新程序的最    後阶段。然而你也可以以 -c、-s 或 -E 等选项,将整个过程自四    个阶段中的其中一个停止。在连结阶段,所有与原始码相对应的    .o 文件、程序库、和其他无法自文件名辨明属性的文件(包括不以 .o    为扩展名的 object file 以及扩展名为 .a 的 archive file)都会    交由连结程序来处理(在指令行将那些文件当作连结程序的参数传给    连结程序)。  选项:    不同的选项必须分开来下:例如 `-dr' 这个选项就与 `-d -r' 大    不相同。    绝大部份的 `-f' 及 `-W' 选项都有正反两种形式:-fname 及    -fno-name (或 -Wname 及 -Wno-name)。以下只列出非预设的那个    形式。    以下是所有选项的摘要。以形式来分类。选项的意义将另辟小节说    明。    一般性(概略、常用的)选项         -c -S -E -o file -pipe -v -x language    程序语言选项         -ansi -fall-virtual -fcond-mismatch         -fdollars-in-identifiers -fenum-int-equiv         -fexternal-templates -fno-asm -fno-builtin         -fno-strict-prototype -fsigned-bitfields         -fsigned-char -fthis-is-variable         -funsigned-bitfields -funsigned-char         -fwritable-strings -traditional -traditional-cpp         -trigraphs    编译时的警告选项          -fsyntax-only -pedantic -pedantic-errors -w -W         -Wall -Waggregate-return -Wcast-align -Wcast-qual         -Wchar-subscript -Wcomment -Wconversion         -Wenum-clash -Werror -Wformat -Wid-clash-len         -Wimplicit -Winline -Wmissing-prototypes         -Wmissing-declarations -Wnested-externs -Wno-import         -Wparentheses -Wpointer-arith -Wredundant-decls         -Wreturn-type -Wshadow -Wstrict-prototypes -Wswitch         -Wtemplate-debugging -Wtraditional -Wtrigraphs         -Wuninitialized -Wunused -Wwrite-strings    除错选项         -a -dletters -fpretend-float -g -glevel -gcoff         -gxcoff -gxcoff+ -gdwarf -gdwarf+ -gstabs -gstabs+         -ggdb -p -pg -save-temps -print-file-name=library         -print-libgcc-file-name -print-prog-name=program    最佳化选项         -fcaller-saves -fcse-follow-jumps -fcse-skip-blocks         -fdelayed-branch -felide-constructors         -fexpensive-optimizations -ffast-math -ffloat-store         -fforce-addr -fforce-mem -finline-functions         -fkeep-inline-functions -fmemoize-lookups         -fno-default-inline -fno-defer-pop         -fno-function-cse -fno-inline -fno-peephole         -fomit-frame-pointer -frerun-cse-after-loop         -fschedule-insns -fschedule-insns2         -fstrength-reduce -fthread-jumps -funroll-all-loops         -funroll-loops -O -O2    预处理选项         -Aassertion -C -dD -dM -dN -Dmacro[=defn] -E -H         -idirafter dir -include file -imacros file -iprefix         file -iwithprefix dir -M -MD -MM -MMD -nostdinc -P         -Umacro -undef    汇编程序选项         -Wa,option    连结程序选项         -llibrary -nostartfiles -nostdlib -static -shared         -symbolic -Xlinker option -Wl,option -u symbol    目录选项          -Bprefix -Idir -I- -Ldir    Target Options         -b machine -V version    与机器(平台)相关的选项         M680x0 Options         -m68000 -m68020 -m68020-40 -m68030 -m68040 -m68881         -mbitfield -mc68000 -mc68020 -mfpa -mnobitfield         -mrtd -mshort -msoft-float         VAX Options         -mg -mgnu -munix         SPARC Options         -mepilogue -mfpu -mhard-float -mno-fpu         -mno-epilogue -msoft-float -msparclite -mv8         -msupersparc -mcypress         Convex Options         -margcount -mc1 -mc2 -mnoargcount         AMD29K Options         -m29000 -m29050 -mbw -mdw -mkernel-registers         -mlarge -mnbw -mnodw -msmall -mstack-check         -muser-registers         M88K Options         -m88000 -m88100 -m88110 -mbig-pic         -mcheck-zero-division -mhandle-large-shift         -midentify-revision -mno-check-zero-division         -mno-ocs-debug-info -mno-ocs-frame-position         -mno-optimize-arg-area -mno-serialize-volatile         -mno-underscores -mocs-debug-info         -mocs-frame-position -moptimize-arg-area         -mserialize-volatile -mshort-data-num -msvr3 -msvr4         -mtrap-large-shift -muse-div-instruction         -mversion-03.00 -mwarn-passed-structs         RS6000 Options         -mfp-in-toc -mno-fop-in-toc          RT Options         -mcall-lib-mul -mfp-arg-in-fpregs -mfp-arg-in-gregs         -mfull-fp-blocks -mhc-struct-return -min-line-mul         -mminimum-fp-blocks -mnohc-struct-return         MIPS Options         -mcpu=cpu type -mips2 -mips3 -mint64 -mlong64         -mlonglong128 -mmips-as -mgas -mrnames -mno-rnames         -mgpopt -mno-gpopt -mstats -mno-stats -mmemcpy         -mno-memcpy -mno-mips-tfile -mmips-tfile         -msoft-float -mhard-float -mabicalls -mno-abicalls         -mhalf-pic -mno-half-pic -G num -nocpp         i386 Options         -m486 -mno-486 -msoft-float -mno-fp-ret-in-387         HPPA Options         -mpa-risc-1-0 -mpa-risc-1-1 -mkernel -mshared-libs         -mno-shared-libs -mlong-calls -mdisable-fpregs         -mdisable-indexing -mtrailing-colon         i960 Options         -mcpu-type -mnumerics -msoft-float         -mleaf-procedures -mno-leaf-procedures -mtail-call         -mno-tail-call -mcomplex-addr -mno-complex-addr         -mcode-align -mno-code-align -mic-compat         -mic2.0-compat -mic3.0-compat -masm-compat         -mintel-asm -mstrict-align -mno-strict-align         -mold-align -mno-old-align         DEC Alpha Options         -mfp-regs -mno-fp-regs -mno-soft-float -msoft-float         System V Options         -G -Qy -Qn -YP,paths -Ym,dir    Code Generation Options         -fcall-saved-reg -fcall-used-reg -ffixed-reg         -finhibit-size-directive -fnonnull-objects         -fno-common -fno-ident -fno-gnu-linker         -fpcc-struct-return -fpic -fPIC         -freg-struct-returno -fshared-data -fshort-enums         -fshort-double -fvolatile -fvolatile-global         -fverbose-asm  PRAGMAS     Two `#pragma' directives are supported for GNU C++, to    permit using the same header file for two purposes: as a    definition of interfaces to a given object class, and as    the full definition of the contents of that object class.    #pragma interface         (C++ only.)  Use this directive in header files         that define object classes, to save space in most         of the object files that use those classes. Nor-         mally, local copies of certain information (backup         copies of inline member functions, debugging infor-         mation, and the internal tables that implement vir-         tual functions) must be kept in each object file         that includes class definitions. You can use this         pragma to avoid such duplication. When a header         file containing `#pragma interface' is included in         a compilation, this auxiliary information will not         be generated (unless the main input source file it-         self uses `#pragma implementation'). Instead, the         object files will contain references to be resolved         at link time.    #pragma implementation    #pragma implementation "objects.h"         (C++ only.) Use this pragma in a main input file,         when you want full output from included header         files to be generated (and made globally visible).         The included header file, in turn, should use         `#pragma interface'. Backup copies of inline mem-         ber functions, debugging information, and the in-         ternal tables used to implement virtual functions         are all generated in implementation files.         If you use `#pragma implementation' with no argu-         ment, it applies to an include file with the same         basename as your source file; for example, in         `allclass.cc', `#pragma implementation' by itself         is  equivalent  to  `#pragma  implementation         "allclass.h"'. Use the string argument if you want         a single implementation file to include code from         multiple header files.          There is no way to split up the contents of a sin-         gle header file into multiple implementation files.  文件说明    file.c       C source file    file.h       C header (preprocessor) file    file.i       经预处理过的 C source file    file.C       C++ source file    file.cc      C++ source file    file.cxx    C++ source file    file.m       Objective-C source file    file.s       assembly language file    file.o       object file    a.out       link edited output    TMPDIR/cc*     temporary files    LIBDIR/cpp     preprocessor    LIBDIR/cc1     compiler for C    LIBDIR/cc1plus   compiler for C++    LIBDIR/collect   linker front end needed on some machines    LIBDIR/libgcc.a  GCC subroutine library    /lib/crt[01n].o  start-up routine    LIBDIR/ccrt0  additional start-up routine for C++    /lib/libc.a    standard C library, 参阅 man page intro(3)    /usr/include  standard directory for #include files    LIBDIR/include   standard gcc directory for #include files    LIBDIR/g++-include additional g++ directory for #include    LIBDIR is usually /usr/local/lib/machine/version.    TMPDIR comes from the environment variable TMPDIR (default    /usr/tmp if available, else /tmp). C语言中有一些函数不需要进行编译,有一些函数也可以在多个文凭中使用。一般来说,这些函数都会执行一些标准任务,如数据库输入/输出操作或屏幕控制等。可以事先对这些函数进行编译,然后将它们放置在一些特殊的目标代码文件中,这些目标代码文件就称为库。库文件中的函数可以通过连接程序与应用程序进行连接。这样就不必在每次开发程序时都对这些通用的函数进行编译了。  不同类型的应用程序将会使用不同的函数库。例如:libdbm库中组包含了对数据库文件进行访问的dbm函数,需要对数据库进行操作的程序就会与该库进行连接。数学应用程序将使用数学库libm,X-Windows应用程序将使用Xlib库,libX11。另外,所有的程序都将使用标准的C函数库。libc,该库中包含了诸好内存管理或输入输出操作的基本函数,这些库都存放在/usr/lib这些系统公用的目录中,系统中的任何用户都可以利用这些库。当然用户也可以建立自己专用的库函数,供自己或其它指定的人员使用。  库可以有三种使用的形式:静态、共享和动态。静态库的代码在编译时就已连接到开发人员开发的应用程序中,而共享库只是在程序开始运行时才载入,在编译时,只是简单地指定需要使用的库函数。动态库则是共享库的另一种变化形式。动态库也是在程序运行时载入,但与共享库不同的是,使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入。动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。   已经开发的大多数库都采取共享库的方式。ELF格式的可执行文件使得共享库能够比较容易地实现,当然使用旧的a.out模式也可以实现库的共享。Linux系统中目前可执行文件的标准格式为ELF格式。  GNU库的使用必须遵守Library GNU Public License(LGPL许可协议)。该协议与GNU许可协议略有不同,开发人员可以免费使用GNU库进行软件开发,但必须保证向用户提供所用的库函数的源代码。  系统中可用的库都存放在/usr/lib和/lib目录中。库文件名由前缀lib和库名以及后缀组成。根据库的类型不同,后缀名也不一样。共享库的后缀名由.so和版本号组成,静态库的后缀名为.a。采用旧的a.out格式的共享库的后缀名为.sa。  libname.so.major.minor  libname.a  这里的name可以是任何字符串,用来唯一标识某个库。该字符串可以是一个单字、几个字符、甚至一个字母。数学共享库的库名为libm.so.5,这里的标识字符为m,版本号为5。libm.a则是静态数学库。X-Windows库名为libX11.so.6,这里使用X11作为库的标识,版本号为6。  使用gcc编译器就可以将库与自己开发的程序连接起来,例如:libc.so.5中包含了标准的输入输出函数,当连接程序进行目标代码连接时会自动搜索该程序并将其连接到生成的可执行文件中。标准的输入输出库中包含了许多基本的输入输出函数,如printf函数等。也可以连接其它的一些系统函数库,如数学库等,但与libc.so.5不同,大部分其它的系统库需要在命令行中显式指定所用的库名。  在/usr/lib和/lib目录中可以找到绝大多数的共享库。连接时将首先搜索这两个目录。有一些库也可能存放在特定的目录中,在/etc/ld.conf配置文件中给出了这些目录的列表。连接程序也会对列出的这些目录进行搜索。在默认情况下,Linux将首先搜索指定库的共享版本,如果找不到,才会去搜索静态版本。在对共享库进行更新或安装新库后,必须运行ldconfig命令更新/etc/ld.conf文件中相应的项(如果使用RPM进行安装,一般会自动进行更新,不过也不能保证这一点)。  在gcc编译器中引用可搜索到的目录中的库文件时,需要使用-l选项和库名。在gcc命令行上输入-lm可以在程序中连接标准算术库,-l将首先使用libname.so进行搜索,这里是libm.so。下面的例子将使用算术库创建bookrecs程序,请注意这里的-lm选项。  $ gcc main.c io.c -o bookrecs -lm  系统中还有一些其它可用的库,常用的是libncurses.a库,包含了一些简单的鼠标移动例程。在命令行中使用-lncurses选项引用libncurses.so库。下面的例子同时调用了数学和光标库。   $ gcc mian.c io.c -o bookrecs -lm -lncurses  在引用其它目录中的库时,需要使用-ldir选项指定该目录。该选项指定了搜索库函数时其它路径。在下面的例子中,用户在连接时使用了mydir目录中的myio.so库文件。  $ gcc main.c -o bookrecs -lmydir -lmyio

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ]
1 人已下载

下载文档

相关文档