[linux][elf]ELF格式分析与常用工具以及不常见问题

前言: ELF只linux是最常用的可执行文件格式,这里简单分析一下ELF格式,介绍几个常用命令,并分享几个不常见的问题。 分析: 1,dynamically link 老习惯,从“Hello World”开始。

使用gcc采用默认参数编译,使用file命令查看: ELF 64-bit LSB executable:可见,是ELF格式64bit程序。那么在64位机上是否可以编译出来32bit程序呢?答案是可以的。但是gcc的参数需要多加一个-m32。 dynamically linked:链接方式是动态链接。说明程序在执行之前还需要链接其他的so。查看具体需要链接那些so,可以使用ldd hello查看。同时可见,gcc会默认使用动态链接。 interpreter /lib64/ld-linux-x86-64.so.2:说明程序使用的链接器是/lib64/ld-linux-x86-64.so.2。在用户程序执行之前,需要先用解释器来解释。 not stripped:没有strip过。一般编译的时候,会带着-g选项,可以用来debug。那么会在binary中带有debug symbol。如果使用了strip命令,就会剥离debug symbol。好处是会让binary小很多,并且在binary中不会残留编译机器上的信息;坏处就是出了问题之后,不能debug。比较好的做法是,例如在Android上,会把没有strip过的binary放到一个目录下保存,把strip过的放到板子(或者手机)中。这样子就可以让板子中的磁盘使用率降低,出了问题之后,把coredump文件copy到debug symbol目录下,不影响debug。 2,statically link 同样还是Hello world代码:

使用gcc hello.c -o hello -static编译,再使用file命令查看: statically linked:链接方式是静态链接。此类型可以直接运行,不需要再依赖其他的链接库了。同时也可以看到已经没有interpreter了。 3,*.so 想要编译so,可以给gcc加上-shared选项即可。 4,*.a 想要编译静态链接库,需要先用gcc编译成*.o文件,把*.o文件使用ar命令打包即可。 5,section

ELF格式是由一个或者多个section组成的。使用命令objdump -h hello,可见hello程序的所有section header。 如果要dump出来所有的内容,需要使用命令objdump -D hello .interp就是存放interpreter的地方; .init这个section会比较特殊,存放的代码会在main函数之前执行。这里要说明一点,尽管c语言默认main函数是入口函数,然后ELF还是有办法让代码在main函数之前执行; .rodata就是传说中的“静态常量区”; .bss&.data就是传说中的“全局变量区”,差别在于是否初始化。 objdump是一个比较少用又比较好用的命令。有时候,需要分析汇编代码,这个时候,objdump就该闪亮登场了。 6,interpreter linux-4.0.4/fs/binfmt_elf.c是kernel准备/开始运行ELF的地方,在load_elf_binary函数中:

可见,ELF在解析完binary的section之后,会尽量运行interpreter。所以,binary中使用的interpreter就可以有很大的操作空间,可以让程序共享一部分代码(就是各个so),在运行前进行链接。注意,在pc上,一般是使用ld;而在Android上,使用Google自己实现的linker。 7,LD_LIBRARY_PATH 这个环境变量用来告诉ld可以去哪些目录下搜索so。 很多情况下,未必把so防止到默认的/lib或者/lib64目录下,尤其是单独的用户项目。例如作者的示例代码编译出来的so,不想放到“系统”目录下,就会放到/home/hello/目录下,那么在启动程序之前,需要在shell中输入命令:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hello就行,否则就会报错说找不到so。 说起这个问题,作者曾经在Android上,遇到过linker的bug,就是LD_LIBRARY_PATH环境变量的处理有问题。 8,LD_PRELOAD 如果多个so有相同的symbol,LD_PRELOAD目录下的so中的symbol会被优先使用。 注意,这个环境变量很有用,同时也会带来安全问题。例如,比较用户输入的密码使用strcmp函数,那么在LD_PRELOAD的so中实现了strcmp函数,就可以劫持用户输入的密码了。 再来一例,直接升级glibc是比较危险的,可能会让系统启动不起来。那么可以使用LD_PRELOAD导入新的glibc,验证没有问题之后,再升级。 9,cpp constructor & destructor

如示例代码,会是怎样的打印顺序? 如果使用gdb在main函数处设定断点,还是可以看到打印。如果在析构函数中设置断点,发现bt中并没有main。那么原因是什么呢?

先使用objdump -D hello得到上图的反汇编。

使用命令:c++filt _ZN5HelloD1Ev可以看到cpp的symbol名称,即Hello的析构函数;同理_ZN5HelloC1Ev是Hello::Hello()。 先看例子中的4008ad,调用了构造函数Hello::Hello()。 注意例子中的40090a这个地址,就是Hello::~Hello()的起始地址。在4008bc这个地址上,把40090a放到了edi中,在x86上,edi就是第一个参数的参数的地址,然后调用了__cxa_atexit函数,可见,g++编译的时候,已经帮我们把全局类变量的析构函数注册给了atexit。 这里看似是一个小技巧,可以在main函数之前和之后执行代码,实际使用起来,往往带来的负面效果更多。例如,如果声明了两个不同的类的全局变量,那么哪个先执行呢?如果声明 了同一个类的两个实例,哪个实例先构造呢?聪明的你可能会发现,先声明的实例先执行构造,那么如果在不同的.h文件中声明的呢?怎么确定哪个先被引用到的? 作者曾经在Android上遇到过一个bug:因为多个instance析构的顺序有问题而引起segmentation fault。这可是google大神写的binder的代码呦。 以作者的经验来看,绝大多数情况下,在main的第一行执行,或者在main函数之前执行,差别并不大。而且,atexit是把函数注册到一个table上,那么这个table会不会overflow呢,而且,可以尝试一下,直接使用syscall退出进程,看看是不是还会有这里的打印呢?总之,这里最好注意,轻易不要声明全局类型的instance!!! 10,weak symbol 同名symbol冲突的时候,可以使用weak symbol解决。 11,constructor 在编译的时候,指定某些函数的attribute是constructor,那么也可以让函数在main之前执行。 后面分析catchseg的时候,再具体分析源代码。 12,tools 另外,还有相关的工具,如addr2line,nm,objcopy。。。 后记: ELF遇到的问题,挺多时候都挺痛苦的。good luck~

原文发布于微信公众号 - AlwaysGeek(gh_d0972b1eeb60)

原文发表时间:2017-02-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏分布式系统进阶

Kafka源码分析-配置文件

作为Class KafkaConfig的伴生类,定义了创建KafkaConfig对象的工厂方法:

9910
来自专栏张善友的专栏

使用自定义行为扩展 WCF

Windows® Communication Foundation (WCF) 提供了许多扩展点,供开发人员自定义运行时行为,从而实现服务调度和客户代理调用。您...

39470
来自专栏Python爬虫与算法进阶

Python函数超时,用装饰器解决

我们在自定义一个函数后,会调用这个函数来完成我们想要的功能。 就拿爬虫来举例,你发送请求,服务器给你响应,但是有可能服务器没有给你任何数据,无论是他识别了爬虫、...

42420
来自专栏Python中文社区

用Python实现微信接口(一)

專 欄 ❈爱撒谎的男孩,Python中文社区专栏作者 博客:https://chenjiabing666.github.io ❈ 安装 sudo pip in...

60960
来自专栏云飞学编程

python爬虫小知识,中文在url中的编码解码

有时候我们做爬虫经常会遇到这种编码格式,大概的样式为 %xx%xx%xx,对于这部分编码,python提供了一个quote的方法来编码,对应的解码为unquot...

10430
来自专栏北京马哥教育

面试分享系列 | 17道Python面试题,让你在求职中无往不利

今天给大家分享的是Python面试题系列的第一篇文章,后续我也会陆续整理Python相关的问题给大家,无论是求职者还是新人都可以通过面试题来考察自己的能力缺陷。...

38240
来自专栏Java架构

Java并发之Condition的实现分析

回忆 synchronized 关键字,它配合 Object 的 wait()、notify() 系列方法可以实现等待/通知模式。

8420
来自专栏北京马哥教育

Python黑科技 | Python中四种运行其他程序的方式

在Python中,可以方便地使用os模块来运行其他脚本或者程序,这样就可以在脚本中直接使用其他脚本或程序提供的功能,而不必再次编写实现该功能的代码。为了更好地控...

560120
来自专栏程序员同行者

django基础之二

15040
来自专栏闪电gogogo的专栏

Python初学——多线程Threading

接着上篇继续跟着沫凡小哥学Python啦 1.1 什么是多线程 Threading 多线程可简单理解为同时执行多个任务。 多进程和多线程都可以执行多个任务,线程...

22950

扫码关注云+社区

领取腾讯云代金券