linux内核vmlinux的编译过程之 --- $(kallsyms.o)详解(九)

在编译完依赖 vmlinux.o 后,链接 vmlinux 之前,构建系统还要编译依赖目标 $(kallsyms.o)。接下来就对 kallsyms 进行一个简单的解释。

一. 引言
1.符号的概念
Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号

2.kernel_module
内核也有一个module结构,叫做kernel_module。另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:
struct module *module_list = &kernel_module;

3.kallsyms的简介
在2.6.38版的内核中,为了更方便的调试内核代码,开发者考虑将内核代码中所有函数以及所有非栈变量的地址抽取出来,形成是一个简单的数据块(data blob),并将此链接进 vmlinux 中去。如此,在需要的时候,内核就可以将符号地址信息以及符号名称都显示出来,方便开发者对内核代码的调试。完成这一地址抽取+数据快组织封装功能的相关子系统就称之为 kallsyms。反之,如果没有 kallsyms 的帮助,内核只能将十六进制的符号地址呈现给外界,因为机器能理解的只有符号地址,而并不包括人类可读的符号名称。
一般来说,内核只会导出由EXPORT_PARM宏(如EXPORT_SYMBOL_GPL、EXPORT_SYMBOL等)指定的符号给模块使用。为了使debugger提供更好的调试功能,需要使用kallsyms工具为内核生成__kallsyms段数据,该段描述所有不处在堆栈上的内核符号。这样debugger就能更好地解析内核符号,而不仅仅是内核指定导出的符号

二. kallsyms有什么用
kallsyms把内核用到的所有函数地址和名称连接进内核文件,当内核启动后,同时加载到内存中。当发生oops,例如在内核中访问空地址时,就会打印如下的Oops 。上面出现的Oops消息中,显示了出错时的CPU各寄存器的值,以及以 “Backtrace:” 一行开始的C函数调用栈。注意其中除了显示在中括号内的地址,还显示了函数的名称,这就是受助于 kallsyms 的结果。否则它只能显示地址信息(例子待更新):

Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c0004000
[00000000] *pgd=00000000
Internal error: Oops: 805 [#1]
Modules linked in:
CPU: 0
Not tainted (2.6.22.6 #36)
PC is at s3c2410fb_probe+0x18/0x560
LR is at platform_drv_probe+0x20/0x24
pc : [<c001a70c>]
lr : [<c01bf4e8>]
psr: a0000013
sp : c0481e64 ip : c0481ea0 fp : c0481e9c
r10: 00000000 r9 : c0024864 r8 : c03c420c
r7 : 00000000 r6 : c0389a3c r5 : 00000000 r4 : c036256c
r3 : 00001234 r2 : 00000001 r1 : c04c0fc4 r0 : c0362564
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment kernel
Control: c000717f Table: 30004000 DAC: 00000017
Process swapper (pid: 1, stack limit = 0xc0480258)
Stack: (0xc0481e64 to 0xc0482000)
1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c
1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14
1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c
1f00: c0389a44 c038d9dc c0481f24 c0481f18 c01bd808 c01bc568 c0481f4c c0481f28
1f20: c01bcd78 c01bd7f8 c0389a3c 00000000 00000000 c0480000 c0023ac8 00000000
1f40: c0481f60 c0481f50 c01bdc84 c01bcd0c 00000000 c0481f70 c0481f64 c01bf5fc
1f60: c01bdc14 c0481f80 c0481f74 c019479c c01bf5a0 c0481ff4 c0481f84 c0008c14
1f80: c0194798 e3c338ff e0222423 00000000 00000001 e2844004 00000000 00000000
1fa0: 00000000 c0481fb0 c002bf24 c0041328 00000000 00000000 c0008b40 c00476ec
1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
1fe0: 00000000 00000000 00000000 c0481ff8 c00476ec c0008b50 c03cdf50 c0344178
Backtrace:
[<c001a6f4>] (s3c2410fb_probe+0x0/0x560) from [<c01bf4e8>] (platform_drv_
probe+0x20/0x24)
[<c01bf4c8>] (platform_drv_probe+0x0/0x24) from [<c01bd5a8>] (driver_probe_
device+0xe8/0x18c)
[<c01bd4c0>] (driver_probe_device+0x0/0x18c) from [<c01bd788>] (__driver_
attach+0x80/0xe0)
r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644
[<c01bd708>] (_ _driver_attach+0x0/0xe0) from [<c01bc5a8>] (bus_for_each_
dev+0x50/0x84)
r5:c0481eec r4:00000000
[<c01bc558>] (bus_for_each_dev+0x0/0x84) from [<c01bd808>] (driver_attach+
0x20/0x28)
r7:c038d9dc r6:c0389a44 r5:c0389a3c r4:00000000
[<c01bd7e8>] (driver_attach+0x0/0x28) from [<c01bcd78>] (bus_add_driver+
0x7c/0x1b4)
[<c01bccfc>] (bus_add_driver+0x0/0x1b4) from [<c01bdc84>] (driver_register+
0x80/0x88)
[<c01bdc04>] (driver_register+0x0/0x88) from [<c01bf5fc>] (platform_driver_
register+0x6c/0x88)
r4:00000000
[<c01bf590>] (platform_driver_register+0x0/0x88) from [<c019479c>] (s3c2410fb_
init+0x14/0x1c)
[<c0194788>] (s3c2410fb_init+0x0/0x1c) from [<c0008c14>] (kernel_init+0xd4/
0x28c)
[<c0008b40>] (kernel_init+0x0/0x28c) from [<c00476ec>] (do_exit+0x0/0x760)
Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)
Kernel panic - not syncing: Attempted to kill init!

当然它的功能不仅仅于此,还可以查找某个函数例如的sys_fork的地址,然后hook它,kprobe就是这么干的。在v2.6.38中,还可以包含所有符号的地址,应此功能更强大,就相当于内核中有了System.map了,此时查找sys_call_table的地址易如反掌。

三. kallsyms怎么用

内核配置
在2.6.38 内核中,为了更好地调试内核,引入了kallsyms。kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成一个数据块,作为只读数据链接进内核映像,相当于内核中存了一个System.map。要在一个内核中启用 kallsyms 功能,你必须设置 CONFIG_KALLSYMS 选项为y;如果你要在 kallsyms 中包含全部符号信息,必须设置 CONFIG_KALLSYMS_ALL 为y。例如:
linux内核vmlinux的编译过程之 --- $(kallsyms.o)详解(九)_第1张图片

四. kallsyms如何生成符号表
既然明白了 kallsyms 的大致作用,那么内核是如何抽取函数和非堆栈变量的地址,以及如何将它们连同对应的名称保存到一个 data blog中去以便最后链接到 vmlinux 中去的?
(1)首先抽取函数和非堆栈变量的地址。这对内核构建系统来说比较简单,只需要使用 nm 工具即可,我们后面会看到。
(2)然后内核使用这个工具的输出作为输入,来调用主机工具程序 scripts/kallsyms,从而生成一个汇编程序文件。在这个汇编程序文件的数据段中,定义有若干个标号(你可以将其理解成用来存储数据的C数组或数据结构)。在这些标号下面就存储有前面取到的函数/变量地址和对应的名称。
所以,前面所谓的 数据块(data blob) 其实就是这个汇编文件编译后的对象文件。构建系统最后将其链接到 原版内核elf文件vmlinux中。

1. tmp_kallsyms1.S/.tmp_kallsyms1.S的格式
这两个文件最终都被保存在最顶层目录下,注意这是个隐藏文件,在linux下必须使用 “ls -a” 命令才能看到(在windows下显示隐藏文件即可),如下:
在这里插入图片描述
两个文件格式完全一样,不同的是其中包含的函数/变量地址和名称等等。我们先来看看这个汇编文件的格式,大致是这样的(我这里重在列出那些标号,具体数据都省略掉):

#include 
#if BITS_PER_LONG == 64
#define PTR .quad
#define ALGN .align 8
#else
#define PTR .long
#define ALGN .align 4
#endif
	.section .rodata, "a"
.globl kallsyms_addresses
	ALGN
kallsyms_addresses:
	PTR	_text - 0x160000
	...
	PTR	_text + 0x5aadcc

.globl kallsyms_num_syms
	ALGN
kallsyms_num_syms:
	PTR	37229

.globl kallsyms_names
	ALGN
kallsyms_names:
	.byte 0x04, 0xcd, 0xbc, 0x78, 0x74
	...
	.byte 0x06, 0x28, 0xac, 0x31, 0x30, 0x36, 0x33

.globl kallsyms_markers
	ALGN
kallsyms_markers:
	PTR	0
	...
	PTR	405601

.globl kallsyms_token_table
	ALGN
kallsyms_token_table:
	.asciz	"param"
	...
	.asciz	"__"

.globl kallsyms_token_index
	ALGN
kallsyms_token_index:
	.short	0
	...
	.short	909

从上面的汇编代码中可以看出,有六个汇编标号(也是全局变量)的定义,分别是 kallsyms_addresses,kallsyms_num_syms,kallsyms_names,kallsyms_markers,kallsyms_token_table 和 kallsyms_token_index。这些变量的具体用法,我们这里先不予关(若有兴趣可以移步到:(待更新))。目前只需要知道Linux内核通过它们来保存函数/变量地址和对应名称之间的mapping即可。因为在这里定义的时候,它们都是全局的。所以在C代码里面,只需要做一下 extern 声明就可以直接引用它们,这些 extern 声明放在文件kernel/kallsyms.c 中,具体代码如下:

# kernel/kallsyms.c
/*
 * These will be re-linked against their real values
 * during the second link stage.
 */
extern const unsigned long kallsyms_addresses[] __attribute__((weak));
extern const u8 kallsyms_names[] __attribute__((weak));

/*
 * Tell the compiler that the count isn't in the small data section if the arch
 * has one (eg: FRV).
 */
extern const unsigned long kallsyms_num_syms
__attribute__((weak, section(".rodata")));

extern const u8 kallsyms_token_table[] __attribute__((weak));
extern const u16 kallsyms_token_index[] __attribute__((weak));

extern const unsigned long kallsyms_markers[] __attribute__((weak));

这里需要引起注意的是,上面声明中都使用了 attribute((weak))。这个作何解释,可以参考linux gcc attribute_((weak)) 简介及作用

2. kallsyms.o的生成过程
讲了那么多,我们还是来看看构建系统是如何处理kallsyms 的,我们先看看变量 kallsyms.o 的定义:

# 顶层Makefile
ifdef CONFIG_KALLSYMS_EXTRA_PASS
last_kallsyms := 3
else
last_kallsyms := 2
endif

kallsyms.o := .tmp_kallsyms$(last_kallsyms).o

默认情况下,CONFIG_KALLSYMS_EXTRA_PASS 是不会被配置的(我用的开发板也未配置),因此 last_kallsyms 被默认设为 2,给它赋值为3只是为了更方便调试kallsyms系统代码。因此在这里我们简单的认为kallsyms.o变量指代的就是.tmp_kallsyms2.o。
例如mini6410开发板的配置文件定义如下:

CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
# CONFIG_KALLSYMS_EXTRA_PASS is not set

好了,知道这些后,我们再列出顶层 Makefile 中的代码来就比较好懂了:

# 顶层Makefile

define verify_kallsyms
	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
	  echo '  $($(quiet)cmd_sysmap)  .tmp_System.map' &&)                \
	  $(cmd_sysmap) .tmp_vmlinux$(last_kallsyms) .tmp_System.map
	$(Q)cmp -s System.map .tmp_System.map ||                             \
		(echo Inconsistent kallsyms data;                            \
		 echo Try setting CONFIG_KALLSYMS_EXTRA_PASS;                \
		 rm .tmp_kallsyms* ; /bin/false )
endef

# Update vmlinux version before link
# Use + in front of this rule to silent warning about make -j1
# First command is ':' to allow us to use + in front of this rule
cmd_ksym_ld = $(cmd_vmlinux__)
define rule_ksym_ld
	: 
	+$(call cmd,vmlinux_version)
	$(call cmd,vmlinux__)
	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
endef

# Generate .S file with all kernel symbols
quiet_cmd_kallsyms = KSYM    $@
      cmd_kallsyms = $(NM) -n $< | $(KALLSYMS) \
                     $(if $(CONFIG_KALLSYMS_ALL),--all-symbols) > $@

.tmp_kallsyms1.o .tmp_kallsyms2.o .tmp_kallsyms3.o: %.o: %.S scripts FORCE
	$(call if_changed_dep,as_o_S)

.tmp_kallsyms%.S: .tmp_vmlinux% $(KALLSYMS)
	$(call cmd,kallsyms)

# .tmp_vmlinux1 must be complete except kallsyms, so update vmlinux version
.tmp_vmlinux1: $(vmlinux-lds) $(vmlinux-all) FORCE
	$(call if_changed_rule,ksym_ld)

.tmp_vmlinux2: $(vmlinux-lds) $(vmlinux-all) .tmp_kallsyms1.o FORCE
	$(call if_changed,vmlinux__)

.tmp_vmlinux3: $(vmlinux-lds) $(vmlinux-all) .tmp_kallsyms2.o FORCE
	$(call if_changed,vmlinux__)

下面给出各种依赖关系:

kallsyms.o = .tmp_kallsyms2.o

|--- .tmp_kallsyms2.o
|      |--- .tmp_kallsyms2.S
|      |       |--- .tmp_vmlinux2
|      |       |      |--- $(vmlinux-lds)
|      |       |      |         |--- arch/arm/kernel/vmlinux.lds
|      |       |      |         
|      |       |      |--- $(vmlinux-all)
|      |       |      |         |--- $(vmlinux-init) $(vmlinux-main)
|      |       |      |         
|      |       |      |--- .tmp_kallsyms1.o
|      |       |      |         |--- .tmp_kallsyms1.S
|      |       |      |         |         |--- .tmp_vmlinux1
|      |       |      |         |         |        |--- $(vmlinux-lds)
|      |       |      |         |         |        |        |--- arch/arm/kernel/vmlinux.lds
|      |       |      |         |         |        | 
|      |       |      |         |         |        |--- $(vmlinux-all)
|      |       |      |         |         |        |        |--- $(vmlinux-init) $(vmlinux-main)
|      |       |      |         |         |        | 
|      |       |      |         |         |        |--- FORCE
|      |       |      |         |         | 
|      |       |      |         |         |--- scripts/kallsyms
|      |       |      |         |
|      |       |      |         |--- scripts
|      |       |      |         |
|      |       |      |         |--- FORCE
|      |       |      |
|      |       |      |--- FORCE
|      |       |
|      |       |--- scripts/kallsyms
|      |       
|      |--- scripts
|      |
|      |--- FORCE

从依赖关系可以看到,要想编译出目标文件,必须先编译.tmp_vmlinux1,

-------------------第一阶段 .tmp_vmlinux1 -> .tmp_kallsyms1.S -> .tmp_kallsyms1.o -> ----------
2.1 .tmp_vmlinux1
由顶层Makefile可以看出处理目标 .tmp_vmlinux1实际上是调用了rule_ksym_ld,定义如下:

# 顶层Makefile
# Update vmlinux version before link
# Use + in front of this rule to silent warning about make -j1
# First command is ':' to allow us to use + in front of this rule
cmd_ksym_ld = $(cmd_vmlinux__)
define rule_ksym_ld
	: 
	+$(call cmd,vmlinux_version)
	$(call cmd,vmlinux__)
	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
endef

(1) +$(call cmd,vmlinux_version)
调用 cmd_vmlinux_version 去更新基本内核的链接次数,这里更新的是 .version中的数值(最后被同步到 init/version.o 中的版本号,开发板上电时会打印,这里不再讲解),具体代码为:

# 顶层Makefile
# Generate new vmlinux version
quiet_cmd_vmlinux_version = GEN     .version
      cmd_vmlinux_version = set -e;                     \
	if [ ! -r .version ]; then			\
	  rm -f .version;				\
	  echo 1 >.version;				\
	else						\
	  mv .version .old_version;			\
	  expr 0$$(cat .old_version) + 1 >.version;	\
	fi;						\
	$(MAKE) $(build)=init

最后一句执行的$(MAKE) $(build)=init命令就是生成init/built-in.o文件。在linux内核Makefile中的变量build— 过渡篇(五)中不指定编译目标的例子已经讲过。为什么编译它?因为 .version中的数值最后会被同步到 init/version.o 中的版本号。

实际执行命令如下:

  set -e; if [ ! -r .version ]; then rm -f .version; echo 1 >.version; else mv .version .old_version;
   expr 0$(cat .old_version) + 1 >.version; fi; make -f scripts/Makefile.build obj=init

(2) $(call cmd,vmlinux__)
调用 cmd_vmlinux__链接生成 .tmp_vmlinux1,具体代码为:

# 顶层Makefile
# Rule to link vmlinux - also used during CONFIG_KALLSYMS
# May be overridden by arch/$(ARCH)/Makefile
quiet_cmd_vmlinux__ ?= LD      $@
      cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
      -T $(vmlinux-lds) $(vmlinux-init)                          \
      --start-group $(vmlinux-main) --end-group                  \
      $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)

实际执行命令如下:

 arm-linux-ld -EL  -p --no-undefined -X --build-id -o .tmp_vmlinux1 -T arch/arm/kernel/vmlinux.lds
  arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o
    arch/arm/nwfpe/built-in.o  arch/arm/vfp/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o
      arch/arm/common/built-in.o  arch/arm/mach-s3c64xx/built-in.o  arch/arm/plat-samsung/built-in.o  
      kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  
      crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  
      lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  net/built-in.o --end-group 

(3) $ (Q)echo ‘cmd_$ @ := $ (cmd_vmlinux__)’ > $ (@D)/.$(@F).cmd
将编译 .tmp_vmlinux1 的命令写入到 .tmp_vmlinux1.cmd 文件中保存起来,以便下次再编译内核时可以进行新旧命令的比较。
实际执行命令如下:

 echo 'cmd_.tmp_vmlinux1 := /home/hh/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-ld -EL  -p --no-undefined -X --
 build-id -o .tmp_vmlinux1 -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o 
  init/built-in.o --start-group  usr/built-in.o  arch/arm/nwfpe/built-in.o  arch/arm/vfp/built-in.o 
   arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/mach-s3c64xx/built-in.o
     arch/arm/plat-samsung/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o 
      security/built-in.o  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  
      arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o
        net/built-in.o --end-group ' > ./..tmp_vmlinux1.cmd

生成的内核基本映像 .tmp_vmlinux1 中,实际上已经有对上面提到的六个kallsyms_*变量的的引用,只不过那是weak链接,意味着在链接时这些变量即使没有没有定义也没有关系

2.2 .tmp_kallsyms1.S
.tmp_vmlinux1 生成后,就可以生成 .tmp_kallsyms1.S 了。由顶层Makefile可以看出处理目标 .tmp_kallsyms1.S实际上是调用了cmd_kallsyms(定义上面已列出)。
实际执行命令如下:

  arm-linux-nm -n .tmp_vmlinux1 | scripts/kallsyms --all-symbols > .tmp_kallsyms1.S

生成 .tmp_kallsyms1.S 后,内核中所有函数和非堆栈变量的地址及名称也都已经保存在汇编程序中了(也就是上面汇编程序中省略掉的部分)

2.3 .tmp_kallsyms1.o
.tmp_kallsyms1.S 生成后,就可以生成 .tmp_kallsyms1.o 了。由顶层Makefile可以看出处理目标 .tmp_kallsyms1.o实际上是调用了cmd_as_o_S ,定义如下:

# 顶层Makefile
quiet_cmd_as_o_S = AS      $@
cmd_as_o_S       = $(CC) $(a_flags) -c -o $@ $<

实际执行命令如下:

arm-linux-gcc -Wp,-MD,./..tmp_kallsyms1.o.d -D__ASSEMBLY__ -mabi=aapcs-linux -mno-thumb-interwork -funwind-tables
 -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -include asm/unified.h -msoft-float -gdwarf-2    
-nostdinc -isystem /home/hh/opt/FriendlyARM/toolschain/4.5.1/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/include -I/home/hh/linux-2.6.38/arch/arm/include -Iinclude  -include include/generated/autoconf.h -D__KERNEL__ 
-mlittle-endian -Iarch/arm/mach-s3c64xx/include -Iarch/arm/plat-samsung/include    
-c -o .tmp_kallsyms1.o .tmp_kallsyms1.S

--------------------第二阶段 .tmp_vmlinux2-> .tmp_kallsyms2.S -> .tmp_kallsyms2.o -> ----------------

2.4 .tmp_vmlinux2
将 .tmp_kallsyms1.S这个汇编文件编译成对象文件 .tmp_kallsyms1.o后,勾建系统就着手进行第二个阶段,开始链接第二个基本内核映像 .tmp_vmlinux2 了。注意和 .tmp_vmlinux1 不同的是,.tmp_vmlinux2 将 .tmp_kallsyms1.o 也链接进去了 。编译方法同.tmp_vmlinux1。这里只列出实际执行命令:

arm-linux-ld -EL  -p --no-undefined -X --build-id -o .tmp_vmlinux2 -T arch/arm/kernel/vmlinux.lds
arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o
arch/arm/nwfpe/built-in.o  arch/arm/vfp/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o
arch/arm/common/built-in.o  arch/arm/mach-s3c64xx/built-in.o  arch/arm/plat-samsung/built-in.o  
kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  crypto/built-in.o  
block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o
sound/built-in.o  firmware/built-in.o  net/built-in.o --end-group .tmp_kallsyms1.o

注意,链接成功 .tmp_vmlinux2 后,其中包含的部分函数和部分非堆栈变量的地址就发生了变化。为什么?很简单,在一个排好的队伍中间插进去几个人,那后面原有那些人的序号就会因增加不同数目而发生改变。这个时候,这些新地址与记录在 .tm_kallsyms1.o 中的对应地址就不一样。那么以哪个为准?自然是这些新地址,别忘了,它们是因为链接进 kallsyms 而发生改变的,我们就是要链接在一起的效果。

2.5 .tmp_vmlinux2.S
既然发生了地址改变,我们就必须想办法重新生成一次汇编程序 .tmp_kallsyms2.S。这个汇编程序和前面的那个 .tmp_kallsyms1.S 相比。在文件尺寸上没有差别,所不同的只是部分地址罢了。编译方法同.tmp_vmlinux1.S。这里只列出实际执行命令:

   arm-linux-nm -n .tmp_vmlinux2 | scripts/kallsyms --all-symbols > .tmp_kallsyms2.S

下图列出部分不同之处:
linux内核vmlinux的编译过程之 --- $(kallsyms.o)详解(九)_第2张图片

2.6 .tmp_vmlinux2.o
用新生成的 .tmp_kallsyms2.S 编译后的对象文件 .tmp_kallsyms2.o去编译一个新的基本内核,那这个基本内核中所具有的函数/变量地址就将不会再发生变化。所以结论就是,这个对象文件 .tmp_kallsyms2.o 就是我们最后要得到的 data blog,即 $(kallsyms.o) 目标。编译方法同.tmp_vmlinux1.o。这里只列出实际执行命令:

 arm-linux-gcc -Wp,-MD,./..tmp_kallsyms2.o.d -D__ASSEMBLY__ -mabi=aapcs-linux -mno-thumb-interwork -funwind-tables
 -D__LINUX_ARM_ARCH__=6 -march=armv6k -mtune=arm1136j-s -include asm/unified.h -msoft-float -gdwarf-2    
 -nostdinc -isystem /home/hh/opt/FriendlyARM/toolschain/4.5.1/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/include 
 -I/home/hh/linux-2.6.38/arch/arm/include -Iinclude  -include include/generated/autoconf.h -D__KERNEL__ 
 -mlittle-endian -Iarch/arm/mach-s3c64xx/include -Iarch/arm/plat-samsung/include    
 -c -o .tmp_kallsyms2.o .tmp_kallsyms2.S

参考博客链接地址:
https://blog.csdn.net/kehyuanyu/article/details/46346321
http://freearth.blog.chinaunix.net/uid-69913402-id-5818963.html

你可能感兴趣的:(内核,linux,嵌入式)