📄 gcc-howto.txt
字号:
而不是只有, execl("/bin/ls", NULL); 执行程式而不带任何引数,可解释成是一种邀请函,目的是把此程式的动态程式 库独立的特性印出来。至少,a.out是这样的。就ELF而言。事情就不是这样了. (如果你想得知此程式库的资讯,有一些更简单的介面可用;参考动态载入那一 章节,或是ldd的manual page。) 11/16/97译 6/2/98修正 5. 除错与监管5.1 预防重於治疗(lint) lint对Linux而言并没有很广泛的用途,主要是因为大部份的人都能满足於gcc所 提供的警告讯息。可能最有用的就是-Wall参数了---这个参数的用途是要求gcc将 所有的警告讯息显现出来;but probably has more mnemonic value if thought of as the thing you bang your head against. 网路上有一个实用的public domain lint,位於 [15]ftp://larch.lcs.mit.edu/pub/Larch/lclint。我并不知道这个站到底有多 好就是了。 5.2 除错 我要怎样做才能将除错资讯放到一支程式里头? 你需要添加-g的参数来编译与连结程式,而且不可以用-fomit-frame-pointer参 数。事实上,你不需要重新编译所有的程式,只需重新编译目前你正在除错的部 份即可。 就a.out的组态而言,共享程式库是以-fomit-frame-pointer编译而成,这个时候 ,gdb就变得英雄无用武之地了。连结时给定-g的选项,应该就隐含著静态连结的 意义了;这就是为什麽要加-g的原因了。 如果连结器连结失败,告诉你找不到libg.a,那就是在/usr/lib/的目录底下,少 了libg.a。libg.a是一个C语言很特别的侦错程式库。一般在libc的套件内就会提 供libg.a;不然的话(新版是这样的),你可能需要拿libc的原始码自己设置了 ,不过,实际上你应该不需要才对。不管是什麽目的,大部份的情况下,只需 将libg.a连结到/usr/lib/libc.a,你就能得到足够的资讯了。 那,能不能把除错资讯给拿掉? 很多的GNU软体在编译连结时,都会设定-g的选项;这样做会造成执行档过大的问 题(通常是静态的连结)。实际上,这并不是一个很热门的想法。 如果程式本身有autoconf,产生了configure命令稿,通常你就可以 用./configure CFLAGS=或是./configure CFLAGS=-O2来关掉除错资讯。不然的话 ,你得检查检查Makefile了。当然啦,假如你用的是ELF,程式便会以动态的方式 来连结,不论是否有-g的设定;因此你可以平常心把-g拿掉。 实用的软体 据了解,大部份的人都是用gdb来除错。你可以从 [16]GNU archive sites拿到原 始程式;或者是到 [17]tsx-11拿可执行档。xxgdb是一个X介面的除错程式,植基 於gdb(也就是说你得先安装好gdb,才能再装xxgdb)。xxgdb的原始码可以在 [18]ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz找到。 另外,UPS除错程式已由Rick Sladkey移植成功。UPS可以在X底下活得很好,不 像xxgdb那样---仅仅是gdb的X前端介面(X front end)。这支除错程式有一大堆 优良的特点,而且如果你得花时间去除一支破烂的程式,建议你考虑考虑xxgdb。 事先编译好的Linux版与修正版的原始码可以在 [19]ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/找到。而最初的原始 程式则放在 [20]ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z。 你可能会发现另一个用来除错的工具strace,也是相当的有用。它可以显示出由 程序所产生的系统呼叫,而且还拥有其它众多繁复的功能,像是如果你手边没有 原始码的话,strace可以帮你找出有那些路径名称(path-names)已编译进执行 档内; exacerbating race conditions in programs that you suspect contain them;还有,strace可拿来学习程式是怎麽在电脑中执行的。最新的版本 (目前是3.0.8)可在找到 [21]ftp://ftp.std.com/pub/jrs/。 背景程式(常驻程式) 早期典型的常驻程式(daemon programs)是执行fork(),然後终止父程序。这样的 做法使得除错的时间减短了。 了解这点的最简单的方法就是替fork()设一个中断点(breakpoint)。当程式停 止时,强迫fork()传回0。 (gdb) list1 #include <stdio.h>23 main()4 {5 if(fork()==0) printf("child\n");6 else printf("parent\n");7 }(gdb) break forkBreakpoint 1 at 0x80003b8(gdb) runStarting program: /home/dan/src/hello/./forkBreakpoint 1 at 0x400177c4Breakpoint 1, 0x400177c4 in fork ()(gdb) return 0Make selected stack frame return now? (y or n) y#0 0x80004a8 in main () at fork.c:55 if(fork()==0) printf("child\n");(gdb) nextSingle stepping until exit from function fork,which has no line number information.child7 } 核心档案 当Linux开机时,通常组态会设定成不要产生核心档案。要是你那麽喜欢它们的话 ,可以用shell的builtin命令使其重新生效:就C-shell相容的shell(如tcsh) 而言,会是下面这样: % limit core unlimited 而类似Bourne shell的shell(sh, bash, zsh, pdksh)则使用下面的语法: $ ulimit -c unlimited 如果你想要有个多才多艺的核心档命名(core file naming)(for example, if you're trying to conduct a post-mortem using a debugger that's buggy itself),那麽你可以对你的核心程式做一点小小的更动。找一 找fs/binfmt_aout.c与fs/binfmt_elf.c档中与下列相符的程式片段(in newer kernels, you'll have to grep around a little in older ones): memcpy(corefile,"core.",5);#if 0 memcpy(corefile+5,current->comm,sizeof(current->comm));#else corefile[4] = '\0';#endif 将0换成1. 5.3 监管 监管(Profiling)是用来检核一支程式中那些部份是最常呼叫或是执行的时间最 久的方法。这对程式的最佳化与找出何时时间是浪费掉的而言,是相当好的方式 。你必须就你所要的时程资讯(timing information)的目的档加上-p来编译, 而且如果要让输出的档案有意义,你也会需要gprof(来自binutils套件的命令) 。参阅gprof的manual page,可得知其细节。 11/18/97译 6. 连结 由於静态与共享程式库两者间不相容的格式的差异性与动词*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 tcget00010fe8 T __tcgetattr00010fe8 W tcgetattr00068718 T tcgetpgrp *W*说明了*弱态(weak)*,意指符号虽已定义,但可由不同程式库中的另一定义所 替代。最简单的*正常*定义(像是tcgetpgrp)是由*T*所标示: 标题所谈的问题,最简明的答案便是libm.(so|a)了。所有定义在<math.h>的函数 都保留在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的主
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -