《Linux操作系统-系统移植》Linux系统移植-前言

2019-07-13 04:53发布

1.1 系统移植简介

在正式开始一直前,首先要明白什么系统移植,知其然还要知其所以然,对于嵌入式的学习方法,我认为都是大同小异的:从宏观上把握(解决为什么的问题),微观上研究(解决正在做什么的问题)。 嵌入式Linux系统移植主要由四大部分组成:
 搭建交叉开发环境;
 bootloader的选择和移植;
 kernel的配置、编译、和移植;
 根文件系统的制作;
关于具体的移植详情请看后文。

1.2 Binutils工具集 解析

对于嵌入式系统开发,掌握相应的工具至关重要,它能使我们解决问题的效率大大提高。目前,可以说嵌入式系统的开发工具是GNU的天下,因为来自GNU的GCC编译器支持大量的目标处理器。除了GCC,还有一个非常重要的、同样来自于GNU的工具集(toolchain) —— binutils toolchain。这一工具集中存在的一些工具,可以说是我们开发和调试不可缺少的利器。 Binutils中的工具不少和GCC相类似,也是针对特定的处理器的。 在binutils中以下的工具是我们在做嵌入式系统开发时需要掌握的:
表 1
在这里插入图片描述
在这里插入图片描述
下面是各命令的详细使用方法:
 addr2line
addr2line是用来将程序地址转换成其所对应的程序源文件及所对应的代码行,当然,也可以得到所对应的函数。为了说明addr2line是如何使用的,我们需要有一个练习用的程序。先采用编辑工具编辑一个test.c源文件,其内容如图所示。 在这里插入图片描述 运行如下的命令将test.c编译成可执行文件,
在这里插入图片描述 注意:必须加 -g 这个选项 并运行之。在运行test程序后,我们可以在其终端上看到它打印出的fun()函数的地址 —— 0x80483e4。
现在,我们可以用这一地址来看一看addr2line是如何使用的。在终端中运行如下的命令,从命令的运行结果来看,addr2line工具正确的指出了地址0x80483e4 所对于应的程序的具体位置是在哪以及所对应的函数名是什么。 在这里插入图片描述
可能有人会问了:这个0x80483e4地址是我们打印出来,即然有打印,我们一般情况下也会打印出其具体的函数位置,而不是只打印地址,我为何要这么绕一下通过addr2line去找到地址所对应的函数呢?其实,这里打印出地址只是为了得到一个地址以便用于练习。在现实中,地址往往是在调试过程中或是当程序崩溃时通过某种方式获得的。此外,采用nm工具(后面会讲到)可以得到如下的函数地址信息。 farsight@ubuntu:~/kernel$ nm -n test w _Jv_RegisterClasses w __gmon_start__ U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0 080482b4 T _init 08048330 T _start 08048360 t __do_global_dtors_aux 080483c0 t frame_dummy 080483e4 T fun 08048401 T main 08048420 T __libc_csu_init 08048490 T __libc_csu_fini 08048492 T __i686.get_pc_thunk.bx 080484a0 t __do_global_ctors_aux 080484cc T _fini 080484e8 R _fp_hw 080484ec R _IO_stdin_used 0804862c r __FRAME_END__ 08049f14 d __CTOR_LIST__ 08049f14 d __init_array_end 08049f14 d __init_array_start 08049f18 d __CTOR_END__ 08049f1c d __DTOR_LIST__ 08049f20 D __DTOR_END__ 08049f24 d __JCR_END__ 08049f24 d __JCR_LIST__ 08049f28 d _DYNAMIC 08049ff4 d _GLOBAL_OFFSET_TABLE_ 0804a00c D __data_start 0804a00c W data_start 0804a010 D __dso_handle 0804a014 A __bss_start 0804a014 A _edata 0804a014 b completed.6159 0804a018 b dtor_idx.6161 0804a01c A _end  as
as汇编器,将汇编代码汇编成目标文件
** size **
size工具,就是列程序文件中各段的大小。在后面的章节中,我们会使用objdump查看段信息,除了这三个段还有.rdata和.idata两个段,其中.rdata段被归类到.text段中,而.idata段被归类到.data段中。下面是采用size工具所显示出的test中的段大小信息。 在这里插入图片描述
 nm
nm用于列出程序文件中的符号,符号是指函数或是变量名什么的。 farsight@ubuntu:~/kernel$ nm -n test w _Jv_RegisterClasses w __gmon_start__ U __libc_start_main@@GLIBC_2.0 U printf@@GLIBC_2.0 080482b4 T _init 08048330 T _start 08048360 t __do_global_dtors_aux 080483c0 t frame_dummy 080483e4 T fun 08048401 T main 08048420 T __libc_csu_init 08048490 T __libc_csu_fini 08048492 T __i686.get_pc_thunk.bx 080484a0 t __do_global_ctors_aux 080484cc T _fini 080484e8 R _fp_hw 080484ec R _IO_stdin_used 0804862c r __FRAME_END__ 08049f14 d __CTOR_LIST__ 08049f14 d __init_array_end 08049f14 d __init_array_start 08049f18 d __CTOR_END__ 08049f1c d __DTOR_LIST__ 08049f20 D __DTOR_END__ 08049f24 d __JCR_END__ 08049f24 d __JCR_LIST__ 08049f28 d _DYNAMIC 08049ff4 d _GLOBAL_OFFSET_TABLE_ 0804a00c D __data_start 0804a00c W data_start 0804a010 D __dso_handle 0804a014 A __bss_start 0804a014 A _edata 0804a014 b completed.6159 0804a018 b dtor_idx.6161 0804a01c A _end nm所列出的每一行有三部分组成:
第一列是指程序运行时的符号所对应的地址,对于函数则地址表示的是函数的开始地址,对于变量则表示的是变量的存储地址;
第二列是指相应符号是放在哪一个段的;
第三列则是指符号的名称。
在前面我们讲解addr2line时,我们提到addr2line是将程序地址转换成这一地址所对应的具体函数是什么,而nm则是全面的列出这些信息。但是,nm不具备列出符号所在的源文件及其行号这一功能,因此,我们说每一个工具有其特定的功能,在嵌入式系统的开发过程中我们需要灵活的运用它们。
对于nm列出的第二列信息,非常的有用,其意义在于可以了解我们在程序中所定义的一个符号(比如变量等等)是被放在程序的哪一个段的,下表列出了第二列将会出现的部分字母的含义,要参看所有字母的意思,请在你的开发环境中运行“man nm”。
表2
在这里插入图片描述
 strip
strip的功能也相对的简单,主要用于去除程序文件中的调试信息以便减小文件的大小。对于strip的功能,其与objcopy带–strip-debug参数时的功能是一样的,这我们前面也有提及。strip所具有的功能,objcopy也都有。 在这里插入图片描述
可以看到test小了几KB,strip在大文件中有更好的体现。
 objdump
objdump可以用来查看目标程序中的段信息和调试信息,也可以用来对目标程序进行反汇编。我们知道程序是由多个段组成的,比如.text是用来放代码的、.data是用来放初始化好的数据的、.bss是用来放未初始化好的数据的,等等。在嵌入式系统的开发过程中,我们有时需要知道所生成的程序中的段信息来分析问题。比如,我们需要知道其中的某个段在程序运行时,共起始地址是什么,或者,我们需要知道正在运行的程序中是否存在调试信息等等。
【1】下面是使用objdump的–h选项来查看程序中的段信息,练习用的程序如前面的图,这里假设你已将其编译成了可执行文件test。
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
【2】反汇编 在这里插入图片描述
 objcopy
objcopy的功能非常的强大,它可以对最后生成的程序文件进行一定的编辑。
作用:格式转换
例:objcopy -O binary xx xx.bin
 readelf
readelf可以显示elf格式可执行文件的信息。ELF格式是UNIX系统实验室作为应用程序二进制接口开发的。ELF格式是Unix/Linux平台上应用最广泛的二进制工业标准之一。 在这里插入图片描述

1.3关于gcc、glibc和binutils模块之间的关系

1.3.1 gcc、glibc和binutils模块之间的关系

1、gcc(gnu collect compiler)是一组编译工具的总称。它主要完成的工作任务是“预处理”和“编译”,以及提供了与编译器紧密相关的运行库的支持,如libgcc_s.so、libstdc++.so等。
2、binutils提供了一系列用来创建、管理和维护二进制目标文件的工具程序,如汇编(as)、连接(ld)、静态库归档(ar)、反汇编(objdump)、elf结构分析工具(readelf)、无效调试信息和符号的工具(strip)等。通常,binutils与gcc是紧密相集成的,没有binutils的话,gcc是不能正常工作的。
3、glibc是gnu发布的libc库,也即c运行库。glibc是linux系统中最底层的api(应用程序开发接口),几乎其它任何的运行库都会倚赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现,主要的如下:
 string,字符串处理
 signal,信号处理
 dlfcn,管理共享库的动态加载
 direct,文件目录操作
 elf,共享库的动态加载器,也即interpreter
 iconv,不同字符集的编码转换
 inet,socket接口的实现
 intl,国际化,也即gettext的实现
 io
 linuxthreads
 locale,本地化
 login,虚拟终端设备的管理,及系统的安全访问
 malloc,动态内存的分配与管理
 nis
 stdlib,其它基本功能

1.3.2在现有系统上如何升级(redhat9上实践的)

1、升级这些库时,最好不要覆盖系统中缺省的;因为这些库,尤其是glibc库,是系统中最核心的共享库和工具,如果盲目覆盖,很可能导致整个系统瘫痪,因为一般更新glibc库时,其它所有以来libc库的共享库都需要重新被编译一遍。因此,为了调试某个程序进入glibc时,最好把glibc安装到/usr/local/lib下。 2、首先编译glibc库。注意最好令建立一个glibc-build的目录,configure时加上–enable-add-ons=linuxthreads选项。make install安装到/usr/local下。 3、修改gcc的spec文件(/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs),更改ld-linux.so.2为/usr/local/lib下的新的共享库装载器。 4、编译binutils库,此时被编译出的程序会连接到/usr/local/lib下的新的libc库。注意,在configure前,需要设置ld缺省连接的路径(LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib),否则binutils会configure出错,找不到libc中的一些符号。具体步骤如下:
(1)export LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib
(2)mkdir binutils-build && cd binutils-build
(3)…/binutils-2.13.90.0.18/configure
(4)make
(5)make -C ld clean
(6)make -C ld LIB_PATH=/usr/lib:/lib:/usr/local/bin(设置编译后的ld的缺省库搜索路径,后面的比前面的优先级高)
(7)make install

1.3.3总结

【1】运行时,动态库的装载依赖于ld-linux.so.6的实现,它查找共享库的顺序如下:
(1)ld-linux.so.6在可执行的目标文件中被指定,可用readelf命令查看;
(2)ld-linux.so.6缺省在/usr/lib和lib中搜索;当glibc安装到/usr/local下时,它查找/usr/local/lib;
(3)LD_LIBRARY_PATH环境变量中所设定的路径;
(4)/etc/ld.so.conf(或/usr/local/etc/ld.so.conf)中所指定的路径,由ldconfig生成二进制的ld.so.cache中。 【2】编译时,搜索库的路径顺序如下:
(1)ld-linux.so.6由gcc的spec文件中所设定;
(2)gcc --print-search-dirs所打印出的路径,主要是libgcc_s.so等库。可以通过GCC_EXEC_PREFIX来设定;
(3)LIBRARY_PATH环境变量中所设定的路径,或编译的命令行中指定的-L/usr/local/lib ;
(2)binutils中的ld所设定的缺省搜索路径顺序,编译binutils时指定。(可以通过“ld --verbose | grep SEARCH”来查看)。 【3】二进制程序的搜索路径顺序为PATH环境变量中所设定。一般/usr/local/bin高于/usr/bin。 【4】编译时的头文件的搜索路径顺序,与library的查找顺序类似。一般/usr/local/include高于/usr/include。