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。例如:
四. 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 <asm/types.h>
#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
下图列出部分不同之处:
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内核vmlinux的编译过程之 --- $(kallsyms.o)详解(九)的更多相关文章
- Linux内核驱动学习(十)Input子系统详解
文章目录 前言 框架 如何实现`input device` 设备驱动? 头文件 注册input_dev设备 上报按键值 dev->open()和dev->close() 其他事件类型,处理 ...
- Linux 内核配置和编译
Linux 内核配置和编译 一.配置内核 (1). 为什么要配置内核 1. 硬件需求 2. 软件需求 选出需要的,去掉不要的 (2). 如何配置内核 1. make config 基于文本模式的交互 ...
- Linux下nginx编译安装教程和编译参数详解
这篇文章主要介绍了Linux下nginx编译安装教程和编译参数详解,需要的朋友可以参考下 一.必要软件准备1.安装pcre 为了支持rewrite功能,我们需要安装pcre 复制代码代码如下: # y ...
- I/O模型之二:Linux IO模式及 select、poll、epoll详解
目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...
- (转)Linux下select, poll和epoll IO模型的详解
Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...
- 技巧:Linux 动态库与静态库制作及使用详解
技巧:Linux 动态库与静态库制作及使用详解 标准库的三种连接方式及静态库制作与使用方法 Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 L ...
- Linux NFS服务器的安装与配置方法(图文详解)
这篇文章主要介绍了Linux NFS服务器的安装与配置方法(图文详解),需要的朋友可以参考下(http://xb.xcjl0834.com) 一.NFS服务简介 NFS 是Network File S ...
- Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置
一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...
- Linux Shell脚本入门--wget 命令用法详解
Linux Shell脚本入门--wget 命令用法详解 wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上.它有以下功能 ...
- (总结)Linux下的暴力密码在线破解工具Hydra详解
(总结)Linux下的暴力密码在线破解工具Hydra详解 学习了:https://blog.csdn.net/yafeichang/article/details/53502869
随机推荐
- Go中的有限状态机FSM的详细介绍
1.FSM简介 1.1 有限状态机的定义 有限状态机(Finite State Machine,FSM)是一种数学模型,用于描述系统在不同状态下的行为和转移条件. 状态机有三个组成部分:状态(Stat ...
- [C++核心编程] 3、函数提高
文章目录 3 函数提高 3.1 函数默认参数 3.2 函数占位参数 3.3 函数重载 3.3.1 函数重载概述 3.3.2 函数重载注意事项 3 函数提高 3.1 函数默认参数 在C++中,函数的形参 ...
- MySQL-分组函数ROLLUP的基本用法
一.ROLLUP简介 ROLLUP是GROUP BY子句的扩展. ROLLUP选项允许包含表示小计的额外行,通常称为超级聚合行,以及总计行. 通过使用ROLLUP选项,可以使用单个查询生成多个分组集. ...
- 2021-01-11:linux中,如何看内存的使用情况呢?
福哥答案2021-01-11: 1.free:查看内存占用情况,会直接返回,常用参数 -M.-G 是以MB或GB为单位返回结果.2.sar:定时检测系统资源占用情况,-r 参数是内存资源,一般用法 s ...
- 2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作。 比如你选定1下标,
2022-01-03:比如arr = {3,1,2,4}, 下标对应是:0 1 2 3, 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作. 比如你选定1下标, ...
- 【Azure Redis 缓存】使用开源工具redis-copy时遇见6379端口无法连接到Redis服务器的问题
问题描述 当使用Azure Redis服务时,需要把一个Redis服务的数据导入到另一个Redis上,因为Redis服务没有使用高级版,所以不支持直接导入/导出RDB文件. 以编程方式来读取数据并写入 ...
- 关于JavaBean和vo的解释
前景提要 最近在学JavaWeb,接触到了很多java后端的概念,其中JavaBean和vo的概念一直让我模糊不清,查询众多资料后写个博客记录一下. 首先先贴一下两者的概念: JavaBean Jav ...
- 最通俗易懂的flex讲解
30分钟彻底弄懂flex布局 欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由elson发表于云+社区专栏 目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用fle ...
- Django自定义视图类及实现请求参数和返回参数加解密
django rest_framework中GenericAPIView配合拓展类mixin或者视图集viewset可以复用其代码,减少自己编写的代码量.下面我要实现自己的视图类,以减少代码量新建一个 ...
- 【爬虫+数据清洗+可视化】用Python分析“淄博烧烤“的评论数据
目录 一.背景介绍 二.爬虫代码 2.1 展示爬取结果 2.2 爬虫代码讲解 三.可视化代码 3.1 读取数据 3.2 数据清洗 3.3 可视化 3.3.1 IP属地分析-柱形图 3.3.2 评论时间 ...