参考的几篇博客:
0-linux 编程学习笔记导航
linux下configure配置参数
交叉编译与静态链接问题:
https://blog.csdn.net/peach2orange/article/details/78979258
交叉编译openssl :
https://blog.csdn.net/andylauren/article/details/53456340
https://blog.csdn.net/jongden/article/details/22384773
项目小伙伴的ptlib、H323plus交叉编译总结:
https://blog.csdn.net/weixin_40728015/article/details/81208914
作为bug本人,从虚拟机的Ubuntu系统到Hi3531开发板板子的移植遇到各种各样的问题,现在也就把各种问题的解决方法和解决问题的时候知晓的东西也给顺便总结一下。
1.ELF文件
有关 linux 可执行文件的格式,这种文件是 ELF 格式的,而在 Windows下,可执行文件通常是 PE 格式的。下面说的是linux环境下ELF文件:
1.可执行文件(应用程序)
可执行文件包含了代码和数据,是可以直接运行的程序。
2.目标文件(*.o)
可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。
*.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些*.o文件的活动可以反映出不同的需要。Linux下,我们可以用gcc -c编译源文件时可将其编译成*.o格式。
这里就不得不提 * .a文件了, 我们把 * .a文件就当做好几个 *.o 文件用 [AR] 命令给 打包 在一起就行了。
3.共享文件(*.so)
也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。
2.Linux系统中的“动态库”和“静态库”
(动态库的后缀为*.so 静态库的后缀为 libxxx.a )
静态库:这类库的名字一般是libxxx.a,xxx为库的名字。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
特点:代码的装载速度快,执行速度也比较快,因为编译时它只会把你需要的那部分链接进去,应用程序相对比较大。但是如果多个应用程序使用的话,会被装载多次,浪费内存。
动态库:这类库的名字一般是libxxx.M.N.so,同样的xxx为库的名字,M是库的主版本号,N是库的副版本号。当然也可以不要版本号,但名字必须有。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。
特点:1.共享:多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可;
2.开发模块好:要求设计者对功能划分的比较好。
注:
当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
然而,
对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明,当程序执行时,首先必须载入这个库。由于动态库
节省空间,linux下进行连接的缺省操作是
首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。(有时候在一些特定的场景需要只跟静态库编译连接的时候,可以把动态库删掉!)
Linux系统中动态链接库的配置文件一般在
/etc/ld.so.conf文件内,它里面存放的内容是可以被Linux共享的动态联库所在的目录的名字。
我们自己开发用的共享库就可以将其拷贝到/lib、/etc/lib、/usr/lib目录里,又或者修改/etc/ld.so.conf文件将我们自己的库路径添加到该文件中,再执行ldconfig命令。(建议使用后者,开发的时候考虑移植性的话最好就是降低与系统环境变量的耦合性)
最后还有一种添加临时共享库(跟所在终端的使用周期一样):export
LD_LIBRARY_PATH=你的库的路径:$LD_LIBRARY_PATH,用于测试
3.configure时静态交叉编译配置
在各种软件的源代码中configure,默认都是动态编译,也就是可执行文件会是动态链接,如果在你嵌入式设备没有这些对应的依赖库的话,会报“-sh: ./YOURPRJ: not found”,你可以使用静态编译,就不会报这个错误了,但是文件大小会变的很大(YOURPRJ指你的可执行文件。)
添加的参数需要根据configure的--help决定,要学会看懂英文。例子如下:
$ ./configure --host=arm-XXX-linux LDFLAGS=-static
$ ./configure --host=arm-XXX-linux --enable-static
$ ./config no-asm no-shared
...
(有些还需要手动修改Makefile文件,例如交叉编译 openssl)
加上参数-static的作用的也就是静态编译的作用,也就是把可执行文件的core(核心)代码需要实现一些特定的功能所依赖的其他代码、数据(泛指那些相关的依赖库)给静态编译进来,这样生成得到的可执行文件就会明显地比动态编译的可执行文件大得多,不过程序的移植性比较好。
拿ptlib来说,在虚拟机的Ubuntu系统中安装了开发板对应的交叉编译工具, 把一些依赖库给编译进可执行文件里面,然后直接在开发板上面就可以直接运行了,与linux系统中的make bothnoshared有着异曲同工之妙。
4.交叉编译生成可执行文件,无法在目标板上运行
问题抛出:
写了一个简单的 hello.c 虚拟机上面 交叉编译 出可行文件
$ arm-XXX-linux-gcc -o hello
编译程序正常,但可执行文件无法在开发板上运行时, 提示找不到该文件。
问题排除:
1. 用gcc编译,可在虚拟机里正常运行,因此不是c文件的问题。
2. 试过绝对路径后,确定不是文件存在的问题,而是
这个文件并不能被执行.
通过排除法将问题定位到
动态链接库上.
在主机上用arm-XXX-linux-gcc -static -o 来进行静态编译.然后将新产生的文件传到目标板上.可以发现通过静态编译的文件明显比动态编译的要大. 然后再次执行./hello 可以看到屏幕上出现了久违的Hello,World!
原因:内核文件与可执行程序使用了不同的编译器进行编译,导致动态链接库对应不上。
如何彻底解决,而不是每次都使用静态编译?
既然是动态库引起的问题,那么应该和编译器的版本有关. 一般编译应用程序对编译器版本没有限制,而编译Linux内核和 u-boot时,手册上会指定编译器。
摸索答案:查看了板子的内核介绍,发现是arm-hisiv300-linux系列的编译器编译的,而项目的工程师说要用arm-hisiv400-linux系列的编译器去编译,所以就一直陷入arm-hisiv400-linux这个无底洞里面,最后改了编译器就成功了~
最终解决方案:使用arm-hisiv300-linux就可以成功
再次编译hello.c,
arm-XXX-linux-gcc -o hello
在目标板上执行 hello ,成功显示”hello world ”
可以看到系统中动态库的支持和编译器还是有关的.
编译内核和编译可执行文件的编译器需要保持一致!!!
5.查看动态库依赖
虚拟机Ubuntu系统查看动态库 :
$ ldd libXXX.so
XXX.so.1 => ...
XXX.so.2 => ...
...
查看嵌入式平台上,交叉编译好的可执行程序,或者库文件的相关依赖
在PC Linux上执行命令:arm-linux-readelf -a YOURPRJ | grep "Shared"
(arm-linux-readelf 这个命令需要安装 )
例如查看二进制可执行文件:
$ arm-linux-readelf -a YOUPRJ | grep "Shared"
0x00000001 (NEEDED) Shared library: [XXX.so.1]
0x00000001 (NEEDED) Shared library: [XXX.so.2]
6.ptlib、H323plus交叉编译(编译器arm-hisiv300-linux)
由于接下来ptlib、H323plus交叉编译还会进一步更新和完善,为了避免篇幅过于冗长,这一篇博客就作为一个打基础篇,欢迎参见我的下一篇博客:
嵌入式linux移植——ptlib、H323plus交叉编译 II