Linux内核源码阅读系列(5)-链接2

可重定位目标文件实例解析

上回书说到ELF的文件格式,这里看一个真实的例子:用readelf工具窥看一下上篇提到的main.c编译而成的main.o文件。

$ gcc -O2 -g -c main.c -o main.o
$ file swap.o
swap.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

file命令的结果显示,main.o文件是一个386结构上的“可重定位目标文件”,并且包含调试信息。这个我使用的编译命令是相应的。用readelf工具读出main.o的细节看一下,将是下面这个样子。重要的部分直接插入了注解。

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
                                                      ^^^---小端
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
                                     ^^^---ObjectFile的类型,上篇提到的3种之一
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          900 (bytes into file)
                                     ^^^---这里指明了“节头表”的位置
  Flags:                             0x0
  Size of this header:               52 (bytes)
                                     ^^^---ELF头的大小
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
                                     ^^^---这里指明了“节头表”的大小
  Number of section headers:         23
  Section header string table index: 20

上面这个就是ELF的头部信息,头部信息主要提供了ELF文件适用的体系结构以及文件各个部分的定位信息,比如“节头表”的位置/大小等。当然还包括“节头表”中记录的数量。

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000040 000021 00  AX  0   0 16
  [ 2] .rel.text         REL             00000000 000854 000008 08     21   1  4
  [ 3] .data             PROGBITS        00000000 000064 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00006c 000000 00  WA  0   0  4
       ^^^---未初始化的全局变量将存放在此,但仔细看他的"Size"就知道,这个节是空的;
             事实上它通常就只是一个占位符。.bss这个名字来自于IBM 704汇编语言中的
             Block Storage Start指令的首字母缩写,鉴于它只是占位符号,可以记作
             Best Save Space。(来自于《深入理解计算机系统》)恩,随你怎么叫它。

  [ 5] .debug_abbrev     PROGBITS        00000000 00006c 000060 00      0   0  1
  [ 6] .debug_info       PROGBITS        00000000 0000cc 00006a 00      0   0  1
  [ 7] .rel.debug_info   REL             00000000 00085c 000060 08     21   6  4
  [ 8] .debug_line       PROGBITS        00000000 000136 000037 00      0   0  1
  [ 9] .rel.debug_line   REL             00000000 0008bc 000008 08     21   8  4
  [10] .debug_frame      PROGBITS        00000000 000170 000050 00      0   0  4
  [11] .rel.debug_frame  REL             00000000 0008c4 000010 08     21  10  4
  [12] .debug_loc        PROGBITS        00000000 0001c0 000043 00      0   0  1
  [13] .debug_pubnames   PROGBITS        00000000 000203 000023 00      0   0  1
  [14] .rel.debug_pubnam REL             00000000 0008d4 000008 08     21  13  4
  [15] .debug_aranges    PROGBITS        00000000 000226 000020 00      0   0  1
  [16] .rel.debug_arange REL             00000000 0008dc 000010 08     21  15  4
  [17] .debug_str        PROGBITS        00000000 000246 00004c 01  MS  0   0  1
  [18] .comment          PROGBITS        00000000 000292 00002a 00      0   0  1
  [19] .note.GNU-stack   PROGBITS        00000000 0002bc 000000 00      0   0  1
       ^^^---上面是一堆调试用的二进制内容。忽略之没有什么大碍。

  [20] .shstrtab         STRTAB          00000000 0002bc 0000c5 00      0   0  1
  [21] .symtab           SYMTAB          00000000 00071c 000120 10     22  15  4
       ^^^---符号表。这位神仙是链接过程处理的重点。

  [22] .strtab           STRTAB          00000000 00083c 000016 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

There are no program headers in this file.
^^^----“可重定位目标文件”并不包含program header table,这个表用于将目标文件
       映射到虚拟存储器,后文详述。
Relocation section '.rel.text' at offset 0x854 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000012  00001002 R_386_PC32        00000000   swap
                   ^^^---符号的重定位类型,一共有11种,现在遇到的这个是最重要的两种之一的
                         “与PC相关的地址引用”

Relocation section '.rel.debug_info' at offset 0x85c contains 12 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000501 R_386_32          00000000   .debug_abbrev
                   ^^^---符号的重定位类型,最重要之二,“32位地址引用”

0000000c  00000c01 R_386_32          00000000   .debug_str
00000011  00000c01 R_386_32          00000000   .debug_str
00000015  00000c01 R_386_32          00000000   .debug_str
00000019  00000201 R_386_32          00000000   .text
0000001d  00000201 R_386_32          00000000   .text
00000021  00000701 R_386_32          00000000   .debug_line
00000027  00000c01 R_386_32          00000000   .debug_str
00000031  00000201 R_386_32          00000000   .text
00000035  00000201 R_386_32          00000000   .text
00000039  00000901 R_386_32          00000000   .debug_loc
00000065  00001101 R_386_32          00000000   buf

Relocation section '.rel.debug_line' at offset 0x8bc contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0000002a  00000201 R_386_32          00000000   .text

Relocation section '.rel.debug_frame' at offset 0x8c4 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000018  00000801 R_386_32          00000000   .debug_frame
0000001c  00000201 R_386_32          00000000   .text

Relocation section '.rel.debug_pubnames' at offset 0x8d4 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000601 R_386_32          00000000   .debug_info

Relocation section '.rel.debug_aranges' at offset 0x8dc contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000601 R_386_32          00000000   .debug_info
00000010  00000201 R_386_32          00000000   .text

There are no unwind sections in this file.

这一段描述了需要重定位的符号或者代码总览。其中每一个记录都描述了需要重定位的对象存在于哪一节,以及它相对于节开始位置的偏移量。

Symbol table '.symtab' contains 18 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000000     0 SECTION LOCAL  DEFAULT    6
     7: 00000000     0 SECTION LOCAL  DEFAULT    8
     8: 00000000     0 SECTION LOCAL  DEFAULT   10
     9: 00000000     0 SECTION LOCAL  DEFAULT   12
    10: 00000000     0 SECTION LOCAL  DEFAULT   13
    11: 00000000     0 SECTION LOCAL  DEFAULT   15
    12: 00000000     0 SECTION LOCAL  DEFAULT   17
    13: 00000000     0 SECTION LOCAL  DEFAULT   19
    14: 00000000     0 SECTION LOCAL  DEFAULT   18
    ^^^---符号表的前14项无须特别关心,因为他们都是编译器自己加的默认值或者调试信息。

    15: 00000000    33 FUNC    GLOBAL DEFAULT    1 main
    16: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
    17: 00000000     8 OBJECT  GLOBAL DEFAULT    3 buf
    ^^^---在main.o文件中真正存在的符号。其中,第一列描述了符号的地址相对于“节”开始的位置的偏移量。
          第二列说明的是这个对象的大小,第三列是对象类型,Ndx指明了对象存在在哪个节中,其中"1"是
          .text节,"3"是.data节。如果是"UND",它指的是没有在这个ObjectFile中定义的符号,是需
          要重定位的对象。

No version information found in this file.

谢谢各位看官的耐心,这玩意看一遍赶紧忘了吧,如果记住就是把大脑当硬盘用了。之所以细节到这个地步,只为了说明一个问题“万事万物都是有原因的”,只要仔细一点都能弄清楚事情到底怎么了,而这种仔细和耐心正是现在国内很多程序员缺少的。我参加过的与中国工程师打交道的软件项目大都让我感慨万千,中国人做事的态度和日本人做事的态度简直没有办法相提并论,不知道什么原因让很多程序员都浮躁的烧到不行……最近参加了一些中文的邮件列表,发现这个风气尤为盛行,难道真的是民族性格问题?话又说回来,同样的工作,日本工程师拿着不止2倍于中国工程师的工资,好歹做事情的态度也应该对得起这份钱。

符号和符号表

上面提到的“符号”,就是“链接”阶段需要解决的重点问题的作业对象。链接需要解决被链接在一起的各个模块之间的符号和代码之间的联系,并重定位这些信息,以便生成的机器代码能够正确的跳转,或者正确的引用到某个变量的值。从链接器的角度看,符号可以分为三类 1.在本模块中定义,被其他模块引用的符号,这可能包括非静态(static)函数和非静态变量; 2.在其他模块中定义,本模块引用的符号,这种符号被称为外部符号(external symbol); 3.在本模块中定义并只在本模块中引用的符号,这种符号成为本地符号(local symbol),可能主要包含static函数和static全局变量。值得一提的是,本地符号并不等于程序的本地变量,因为众所周知,本地程序变量是在运行时栈中管理的,他们的生存周期很短,通过pop和push就能瞬间产生和消灭,无需符号表管理。

链接器的符号解析规则

本地符号的定义在链接过程中不会有大问题,每个本地符号都有唯一的定义;包括Java或者C++中的重载函数等,编译器会运用规则位有同名不同参数的函数各自产生一个唯一的“内藏”函数名。

全局符号比较麻烦,首先是要按照强弱分类,1.函数,已经初始化的全局变量是“强符号”;2.未初始化的全局变量是“弱符号”。然后是取舍规则,1.同名两强必出错,链接器报错; 2.同名强弱,肯定是选择强者; 3.同名两弱就随便取一个。注意啦,如果这个时候你还没有意识到明明规则存在的重要性的话,真是后知后觉了。此外,还有一个问题,就是为什么链接大批动态链接库时会有莫名其妙的错误?原因就是这些“潜规则”导致的错误了。比如,下面的例子:

文中有注释,c&p注意。

/* foo2.c */

void bar(void);

int x = 12345;
        ^^^---已经初始化的全局变量,“强”符号

int main()
{
	bar();
	printf("x = %dn", x);
	return 0;
}
/* bar2.c */
int x;
    ^^^---未初始化的全局变量,“弱”符号

void bar()
{
	x = 54321;
}

这个程序被链接的并运行的话,其结果让程序员大跌眼睛的,x的输出居然还是12345。而实际现实中的问题比这个不知道要复杂多少倍,往往非常难于发现和排除,所以好的命名习惯真的是在体现一个程序员和一个软件开发团队的素养,而不是为了符合CodeStyle做的面子工程。如果你使用gcc作编译器的话,开启-warn-common选项,将帮助你查找重定义的错误。微软的编译器肯定也有这个选项,但是我有n多年都没给Windows写过程序了,实在是不知道。

未完待续。
—-
文中例子来自《深入理解计算机系统》

Linux内核源码阅读系列(4)-链接1

上周工作实在太忙,写blog的事情是一拖再拖……本来准备好继续写Linux内核构成的,但是因为看书看的比较快,刚好看到一个比较关键的地方——程序的链接和载入,而这个部分有牵扯代码生成,机器语言编程,程序实例化和虚拟存储器很多内容,可以说是从程序员的角度理解Linux内核如何执行用户应用程序行为的核心。况且,这个部分牵扯很多不容忽视的“细节”,我害怕如果日子久了,就再也不记得这些内容,于是算是插入一篇,给自己记录下来。

什么是链接

大概教科书上都念过这样的经——源代码变成可执行文件一般要经过 1.编译预处理;2.编译;3.汇编;4.链接 这些过程。从使用TurboC那天起,我就被告知程序是这样产生的,但是直到我大学毕业以后的几年里,才真正知道这些事情都是怎么做的。而且,还是从一本卡耐基梅隆大学的本科生前导课程中学到的,由此可见中国所谓的大学误人子弟的老师的确不在少数。除了继续被愚弄几年以外,上那个大学又有什么意思?先忘掉将中国国内的计算机教育搞到本末倒置的Windows吧,看看Linux中的典型编译过程是怎样完成的。每个操作系统都提供一种编译驱动程序(compile driver),最典型的例子就是gcc。gcc事实上不是一个单独的程序,而是一组程序的组合。Unix世界的逻辑就是将事情分解和简化然后分发给各个可以相互协作的部分完成,在这个设计思想下,Unix世界产成了很多专注做好一件事情的小程序,比如最简单的yes。然而,又为了解决各个小程序之间的协同工作,Unix工具集中又添加进很多tools driver,比如gcc,这种tools driver的设计思想有点像设计模式中提到的facade(这个词似乎是法语,注意发音),他把一些难以把握细节的小工具进行整合从而为用户提供一个简便的接口。gcc在编译程序的过程中实际上需要调用cpp,cc1,as和ld这些工具来帮助它完成工作,于是编译过程就是个RPG游戏,当然这里需要重点看看角色变换和他们的输入与产出。下面游戏开始:

故事背景

一个简单的Swap程序:

/* main.c */
void swap();

int buf[2] = {1,2};

int main()
{
	swap();
	return 0;
}
/* swap.c */
extern int buf[];

int *bufp0 = &buf[0];
int *bufp1;

void swap()
{
	int temp;

	bufp1 = &buf[1];
	temp = *bufp0;
	*bufp0 = *bufp1;
	*bufp1 = temp;
}

出场人物

  1. cpp,“预处理器”
  2. cc1,“c语言编译器”
  3. as,“汇编器”
  4. ld,本集主角,江湖人称“链接器”

这些程序有的是不能被直接调用的,但有的是可以的。为了将问题简化,还是给gcc添加不同的选项作为驱动来观察比他们之间都发生了什么吧。具体什么选项呢,man gcc看看吧。


gcc [-c|-S|-E] ... infile ...

gcc手册的第一行就告诉我们“他不是一个人”。这三个选项从后往前分别指明的就是 -E 预处理; -S 编译; -c 汇编;如果不加参数就直接将输入的源文件做到链接,默认情况是一条龙服务。下面的表格简单的列出了各个步骤的命令,输入和输出。

命令 角色 输出 注释
gcc -E x cpp main.i 这里产生一个经过预处理的中间文件
gcc -S x cc1 main.S 产生汇编语言文件
gcc -c x as main.o 产生可重定位的ObjectFile

此外,swap.c的代码也可以按照上面的步骤按部就班的生成一个swap.o。接下来的工作就是用ld对已经生成的.o文件进行“链接”产生可执行文件。由此可见,

链接就是将不同部分的代码和数据收集和组合成一个单一文件的过程。这个文件可以被加载(或者被拷贝)到存储器中执行。(来自《深入理解计算机系统》)

但如果事情做到这个步,算是可以告一个段落,因为最重要的内容之一“可重定位的目标文件”(relocatable object file)已经生成了,也就是这里出现的.o文件。从技术上说.o与普通的二进制文件相比并没有什么特别之处,它就是一个在磁盘文件中的“字节序列”。但是,这个文件的重要之处在于他是类UNIX操作系统的ABI(application binary interface)的核心。

ELF

ELF的名字不错,elf似乎是德国神话传说中的一种精灵,恰恰也说明了ELF文件在系统中的执行就像是变魔法。跑题了。其实他是Executable and Linkable Format的缩写形式,wikipedia上关于elf的解释包括英文在内都不甚详细,但是还是值得一读的。这种二进制文件格式广泛使用于各种计算机平台。最早的ObjectFile的格式是诞生于贝尔实验室的a.out,知道现在仍然有很多应用程序采用a.out形式运行。

这里插播一下历史消息。贝尔实验室的UNIX系统中使用a.out作为可执行文件的形式,而后在很多UNIX版本中这个二进制文件格式被大量的采用。UNIX系统发展的重要里程碑System V在诞生的时候采用COFF(common object file format)——微软这个偷学狂人在其Windows系统发展的过程中采用了COFF的一个变体作为自己的可执行文件的形式至今,称为PE(portable executable)——现代UNIX版本中大多采用了ELF代替此前比较原始的二进制形式。

伟大的系统在诞生和发展过程中总能产生一些伟大的部件,甚至有些系统本身已经不存在了,但它的思想或者某些精妙的实现却依然在其他系统中以某种形式存在。这个例子数不胜数,比如上面说到的ELF,再比如研发Plan 9操作系统(现在依然存在)的过程中诞生的unicode和procfs。

ObjectFile有三种形式,1.可重定位目标文件(relocatable object file); 2.可执行目标文件(executable object file); 3.共享目标文件(shared object file)。
其中可重定位目标文件就是指.o文件。下面这个图展示的是一个典型的.o的文件组成。

 +----------------------+
 |    ELF header        | <--帮助链接器解析ObjectFile的信息
 +----------------------+
 |       .text          | <--已编译程序的机器代码
 +----------------------+
 |       .rodata        | <--只读数据,比如pirntf的格式化字串等
 +----------------------+
 |       .data          | <--已经初始化全局变量
 +----------------------+
 |        .bss          | <--未初始化全局变量
 +----------------------+
 |        .symtab       | <--符号表,这个表是提供给链接器使用的,每个OjectFile
 +----------------------+
 |      .rel.text       | <--可重定位的代码
 +----------------------+
 |      .rel.data       | <--可重定位的数据
 +----------------------+
 |       .debug         | <--调试符号表
 +----------------------+
 |       .line          | <--.text节中机器指令于源程序行号之间的映射表
 +----------------------+
 |       .strtab        | <--字符串
 +----------------------+
 | section header table | <--节头表(section header table)
 +----------------------+

更加详细的图表可以在"Linkers and Loaders"一书中看到,点这里

这些分段被称为“节”(section),并且,在.o文件中为这些保留了一张表,称作“节头表”(section header table)。节头表描述了不同节的位置和大小,其作用有点像各个节的检索索引。这些节之中,.debug.line节包含的是调试信息,只有gcc在使用"-g"选项时才能得到。而.symtab这个符号表节是每一个ObjectFile都会包含的,一些程序员错误的认为只有在使用"-g"选项时才能在ObjectFile中得到符号表。而这个.symtab节正是链接操作的核心。

预知后事如何,且听下回分解吧。下午约了师兄去游泳,4点从图书馆出来背着两块砖头一样的书就去了,被水一泡想说的东西全忘了。

Linux内核源码阅读系列(3)-代码阅读的陷阱

在前几天些的博文中大概说了一下应该怎样索引代码来帮助阅读过程。但是那篇文章实在是粗略的很,粗略到几乎不能用的地步。于是想偷偷补充一下……

Cscope不能帮你的事情

Cscope本来是为了检索c语言的代码而设计的。这句话的意义就是,Cscope可以索引和帮助检索大部分内核源代码,但是却是有很多遗漏。被遗漏掉的索引理所当然的不能被检索到。所以,这是阅读源代码是必须注意的问题。Linux内核代码无法分析的部分形成原因在于:

  1. 汇编源码包括内迁汇编
  2. 汇编代码和C代码之间的调用关系
  3. 利用函数指针的函数调用
  4. 宏定义的“假函数”
  5. 利用宏在编译时动态生成的函数体

比如,第2点所说的情况,利用汇编语言调用c语言定义的函数,在i386结构的“调度器”dispatcher代码面就存在:

 18 #define switch_to(prev,next,last) do {                                  
 19         unsigned long esi,edi;                                          
 20         asm volatile("pushflnt"               /* Save flags */        
 21                      "pushl %%ebpnt"                                  
 22                      "movl %%esp,%0nt"        /* save ESP */          
 23                      "movl %5,%%espnt"        /* restore ESP */       
 24                      "movl $1f,%1nt"          /* save EIP */          
 25                      "pushl %6nt"             /* restore EIP */       
 26                      "jmp __switch_ton"                                
                          ^^^----------这里利用汇编代码跳转到__switch_to函数
 27                      "1:t"                                             
 28                      "popl %%ebpnt"                                   
 29                      "popfl"                                            
 30                      :"=m" (prev->thread.esp),"=m" (prev->thread.eip),  
 31                       "=a" (last),"=S" (esi),"=D" (edi)                 
 32                      :"m" (next->thread.esp),"m" (next->thread.eip),    
 33                       "2" (prev), "d" (next));                          
 34 } while (0)

第4点中提到的宏定义函数这种情况也比较常见,比如:

#define page_buffers(page)					
	({							
		BUG_ON(!PagePrivate(page));			
		((struct buffer_head *)page_private(page));	
	})

在这里利用宏定义事实上定义了一个函数,而代码索引工具是无法发现这个符号的。
第5点,利用宏定义动态生成的函数体,也是无法被代码索引工具识别的。比如:

 82 #define BUFFER_FNS(bit, name)                                           
...
 91 static inline int buffer_##name(const struct buffer_head *bh)           
 92 {                                                                       
 93         return test_bit(BH_##bit, &(bh)->b_state);                      
 94 }
...
130 BUFFER_FNS(Unwritten, unwritten)

在编译预处理阶段,##name将被替换为正确的缓冲名字,但是,源代码解析工具却不能看到这种变化。这类函数通常和是一些小的内嵌函数,即使是用kgdb调试的时候也是不容易被发现的,要精确发现它们只能靠你的大脑和字符搜索了。所以,在源代码阅读的之前,你必须很清楚的知道,自己究竟可能漏掉什么东西。

内核源码中的其他容易被遗漏的重要内容

除了上面提到的可能被源码索引工具漏掉的内容之外,还必须对下面这些内容保持警惕。

% export LNX=${WHERE YOUR KERNEL SRC LOCATED}

内核的链接器脚本(linker scripts)


find $LNX -name "*lds"

你可以在这里找到链接器脚本的说明。

Every link is controlled by a linker script. This script is written in the linker command language.

The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file. Most linker scripts do nothing more than this.

内核中重要的宏定义文件


$LNX/include/linux/moduleparam.h
$LNX/include/linux/init.h

这两个头文件中定义的宏在很多情况吓都可能被用到(特别是驱动程序开发),所以,尽可能把大脑当硬盘用一下。

内核中的汇编文件


find $LNX -name "*.S"

内核中的Makefile


find $LNX -name "Makefile"

内核中的配置文件


find $LNX -name "*config*"

可能妨碍你理解内核行为的另外一个因素是内核设计的思想以及可能被它影响到的代码运行形式——也就是说虽然内核是使用c语言代码写的,但是并不证明她就是简单的结构化的顺序构成。这主要包含两个方面,1. 面向对象的部分实现,比如文件系统和设备模型——设备模型是内核2.5开发时的一个重要目标,目的在于统一驱动程序的动作以及分离procfs和sysfs;2. “发布——订阅”模型,比如 notification chain,主要思想类似于Design Pattern中提到的Observer Pattern,用于事件消息的传递过程。

OK.源码阅读是件不容易的事情,特别是像Linux内核这样规模庞大的源代码。所以,“长期奋战在”Linxu内核开发“第一线的广大”内核开发者,很难有人说清楚究竟应该怎样去读。但是,注意上面提到的陷阱尽管去读也就是了。 🙂

附送用来生成cscope使用db的脚本。Happy hacking!

#!/bin/sh
if [ "$1" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

if [ "$2" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

LNX=$1
ARCH=$2
mkdir -p ${HOME}/.cscope/
cd /
find $LNX 								
	-path "$LNX/arch/*" ! -path "$LNX/arch/${ARCH}*" -prune -o 	
	-path "$LNX/include/asm-*" ! -path "$LNX/include/asm-${ARCH}*" -prune -o 
	-path "$LNX/tmp*" -prune -o 					
	-path "$LNX/Documentation*" -prune -o 				
	-path "$LNX/scripts*" -prune -o 				
	-path "$LNX/drivers*" -prune -o 				
	-name "*.[chxsS]" -print > ${HOME}/.cscope/cscope.files

cd ${HOME}/.cscope/ #the directory with 'cscope.files'
cscope -b -q -k

—-
参考文献:
x86虚拟调试环境的建立