gcc ld 链接器相关知识,调试指令(程序员的自我修养----链接、装载与库)
最近解决一个动态链接上的问题,因为以前从来没有接触过这方面的知识,所以恶补了一下,首先要了解gcc编译指令(makefile),ld链接器的选项(还有连接脚本section指定内存位置),熟悉查看连接状态是否成功的指令工具(其中又有elf格式,ld、elf相关指令)。然后反汇编理解动态链接的实际执行过程,总的说来这里面的东西还真很多,主要是网上比较深入资料很少,大部分博文也是一知半解,所以后续有问题应直接阅读标准文档。《程序员的自我修养----链接、装载与库》这本书详细介绍了一个应用程序在编译、链接和运行时刻所发生的各种事项,包括:代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现。
Makefile:
LOCAL_SRC_FILES := hello.c PREFIX := arm-anykav200-linux-uclibcgnueabi-
LOCAL_SHARED_LIBRARIES := pthread
LOCAL_MODULE := hello
CC := $(PREFIX)gcc CPP := $(PREFIX)g++ AS := $(CC) LD := $(PREFIX)ld
BUILD_DIR=./
SONAME := $(LOCAL_MODULE).elf
CROSS_PATH ?= /opt/arm-anykav200-crosstool/usr ARM_LIBC_PATH ?=$(CROSS_PATH)/arm-anykav200-linux-uclibcgnueabi/sysroot/usr/lib ARM_LIBGCC_PATH ?=$(CROSS_PATH)/lib/gcc/arm-anykav200-linux-uclibcgnueabi/4.8.5 CLIB = $(ARM_LIBC_PATH)/libm.a CLIB += $(ARM_LIBC_PATH)/libc.a CLIB += $(ARM_LIBGCC_PATH)/libgcc.a CLIB += $(ARM_LIBGCC_PATH)/libgcc_eh.a
CFLAGS := -Wall -fPIC -fno-builtin -nostdlib -mlong-calls LDFLAGS := -p -X -y memset -y memcpy -y mi -Map maps.txt -T link.lds --wrap=malloc -l:../so/libmi.so -l:../ko/libko.so
C_SRC := $(filter %.c,$(LOCAL_SRC_FILES)) C_OBJ := $(patsubst %.c,$(BUILD_DIR)/%.o,$(C_SRC))
.PHONY: all clean
LIBC_OS := $(wildcard libc/*.os)
TARGET := $(BUILD_DIR)/$(SONAME)
all: $(TARGET)
#主要是这里的链接顺序问题,优先链接libc的静态库,防止链接到了动态库里的libc段
$(TARGET): $(C_OBJ) $(LD) -o $@ $^ -dn $(CLIB) -dy $(LDFLAGS)
$(C_OBJ): $(BUILD_DIR)/%.o: %.c $(CC) $(CFLAGS) -c $< -o $@
clean: rm -rf *.o *.so *.elf *.txt
// 学习笔记
Gcc 链接器ld选项参数解释: -shared: 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号)。不用该标志外部程序无法连接。
相当于一个可运行文件
-fpic: 表示编译为位置独立的代码。不用此选项的话编译后的代码是位置相关的所以动态加载时是通过代码拷贝的方式来满足不同进程的须要,而不能达到真正代码段共享的目的。
-l 选项告诉编译器要使用hello这个库。奇怪的地方是动态库的名字是libhello.so,这里却使用hello. 但这样还不行。编译会出错。
In function `main': test.c:(.text+0x1d): undefined reference to `hello' collect2: ld returned 1 exit status 这是由于hello这个库在我们自己的路径中,编译器找不到。
须要使用-L选项,告诉hello库的位置 gcc test.c -lhello -L. -o test -L .告诉编译器在当前文件夹中查找库文件
-Wl选项告诉编译器将后面的参数传递给链接器。(给编译器用的)
-soname则指定了动态库的soname(简单共享名,Short for shared object name)
当要升级系统中的一个库时,并且新库的soname和老库的soname一样,用旧库链接生成的程序使用新库依然能正常运行。这个特性使得在Linux下,升级使得共享库的程序和定位错误变得十分容易。
-Wl,rpath=<your_lib_dir>选项
gcc编译链接动态库时,很有可能编译通过,但是执行时,找不到动态链接库,那是
因为-L选项指定的路径只在编译时有效,编译出来的可执行文件不知道-L选项后面的值,
当然找不到。可以用ldd <your_execute>看看是不有 ‘not found’在你链接的库后面,
解决方法是通过-Wl,rpath=<your_lib_dir>,使得execute记住链接库的位置
-nostdlib:不链接C语言的标准库 仅搜索那些在命令行上显式指定的库路径. 在连接脚本中(包含在命令行上指定的连接脚本)指定的库路径都被忽略.
-fno-builtin -fno-builtin-function 不接受没有 __builtin_ 前缀的函数作为内建函数。
-y SYMBOL'`--trace-symbol=SYMBOL'打印出所有SYMBOL出现的被连接文件的名字. 这个选项可以被多次使用. 在很多系统中,这在预先确定底线时很有必要.当你拥有一个未定义的符号,但不知道这个引用出自哪里的时候,这个选项有用.
–as-needed
只连接使用到的动态库,连接顺序从左往右
Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是: $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。
工具指令:
1、查看调用关系 arm-anykav200-linux-uclibcgnueabi-ldd hello.elf
2、readelf -r hello.elf 显示动态连接,初始的symbol是0
Relocation section '.rel.dyn' at offset 0x893c contains 3 entries:
Offset Info Type Sym.Value Sym. Name
800009e0 00000d15 R_ARM_GLOB_DAT 00000000 __wrap_malloc
800009e4 00001015 R_ARM_GLOB_DAT 00000000 mi
800009e8 00001315 R_ARM_GLOB_DAT 00000000 memset
3、readelf -s -D hello.elf
Ndx为1表示在.text属于静态连接;Ndx是UND,表明这个符号没有在SimpleSection.o中定义,仅仅是被引用
Symbol table for image:
Num Buc: Value Size Type Bind Vis Ndx Name
24 0: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __fsymtab_start
17 0: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __text_end
10 2: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __rtmsymtab_end
20 3: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __vsymtab_start
6 3: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __fsymtab_end
5 3: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __rt_init_start
21 4: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __vsymtab_end
15 4: 800009f8 0 NOTYPE GLOBAL DEFAULT 9 __data_end
8 4: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __rt_init_end
14 5: 800005b0 0 NOTYPE GLOBAL DEFAULT 1 __rodata_start
12 6: 800009f8 0 NOTYPE GLOBAL DEFAULT 9 __bss_end
13 8: 00000000 0 FUNC GLOBAL DEFAULT UND __wrap_malloc
4、arm-anykav200-linux-uclibcgnueabi-objdump -h -D libmi.so > mi.txt
将中间文件dump到反汇编文件中
Elf相关概念:(动态连接及执行过程)
首先理解下plt是procedure linkage table,got是global offset table。got表中存放的是外部符号的地址。plt表中存放的是函数地址
如果一个动态库函数是第一次被调用,那么plt表中是不存在该函数的地址的,通过ld库中的函数,将这个地址取出来存放到got表中,那么当第二次调用该函数时,plt表中就有了这个函数的地址,直接跳转到该地址,而不再需要去取地址,也就是动态链接。
GOT 是 data section, 是一个 table, 除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section, 是一段一段的 code,执行中不需要修改。
elf文件中的.plt .rel.dyn .rel.plt .got .got.plt的关系
.plt的作用是一个跳板,保存了某个符号在重定位表中的偏移量(用来第一次查找某个符号)和对应的.got.plt的对应的地址(延时绑定)
.rel.dyn重定向表,在程序启动时就需要重定位完成。
.rel.plt保存了重定位表的信息,可以使用lazy(延时绑定)的连接方式
.got据说是保存了elf文件本身的各个符号的偏移量,即不要动态链接,未证明
.got.plt保存了重定位地址。
比如printf是一个重定位符号,需要连接该符号时过程是这样:
main函数call .plt段中的一个地址,这里的第一句话就是调转到.got.plt中的保存的printf的地址,如果是第一次,那么保存的地址就是.plt中的下一句话,这个下一句话就是压入这个符号在.rel.plt中的重定位表的偏移量,然后ld程序就会根据重定位表中的信息加上这个偏移量找到这个地址,保存到重定位表所指向的地址中,这个地址其实就是.got.plt段的一个地址。
第二次调用时就可以直接获取到.got.plt中保存的地址了。
附录,参考文献:
1、https://blog.csdn.net/dean_yanqing/article/details/6669352 ld链接器选项说明
2、http://www.cnblogs.com/OCaml/archive/2012/06/18/2554086.html#sec-1-6-1 链接顺序说明
3、https://www.cnblogs.com/gatsby123/p/9750187.html https://greek0.net/elf.html elf格式说明
4、
gcc ld 链接器相关知识,调试指令(程序员的自我修养----链接、装载与库)的更多相关文章
- bss、弱符号强符号、common块、未初始化的全局变量------程序员的自我修养-链接装载与库
- 程序员的自我修养九Windows下的动态链接
9.1 DLL简介 DLL即动态链接库的缩写,它相对于Linux下的共享对象. Windows下的DLL文件和EXE文件实际上是一个概念,它们都是有PE格式的二进制文件. 微软希望通过DLL机制加强软 ...
- vs2008调试 Release(链接器来生成调试信息)
VS2008 Release 修改配置: 1.项目——>属性——>C/ C++ ——> 常规 ——>调试信息格式——>用于“编辑并继续”的程序数据库(/ZI) 2.项目— ...
- 你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事
2.1.3 我的父亲母亲:编译器和链接器 从表面上看,我是由Visual Studio创建的,而实际上,真正负责编译源代码创建生成可执行程序HelloWorld.exe的却是Visual Studi ...
- linux实用指令 | 程序员线上排查必知必会linux指令(持续更新中)
Linux线上排查程序员实用指南 一.乱码问题 二.帮助指令 1. help命令 2. man命令 3. info命令 三.性能监测与优化 1. top命令 参考资源 Linux线上排查程序员实用指南 ...
- npm包管理器相关知识
关于npm包安装命令的介绍,如下图:
- linux下c程序的链接、装载和库(1)
读完<程序员的自我修养--链接.装载和库>相关章节,想来总结一下,若有错误,请指正,多谢. 1. 什么叫目标文件? 你的工程里有很多xxx.c这样的源文件,这些文件是文本文件,只有人能够认 ...
- 笔记:LNK2001不代表链接器真的需要链接相关符号
环境:VS2008 我们都知道,链接器在生成可执行程序时,会忽略那些没有用到的符号.但是昨天遇到一个链接问题,看起来与这条基本策略并不相符.首先看一个静态链接库的结构: lib | |---- ...
- muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor
目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...
随机推荐
- 认识enum
今天在看别人代码的时候发现他的使用enum的方法,我是不会用的,因此记录一下. 这个是一个语音合成,今天改为使用百度的语音,可以免费支持离线.在线. 在语音合成的使用,会使用许多的词句让其合成语音,用 ...
- rest参数与扩展运算符
rest参数与扩展运算符 rest参数 当遇上这样一种需求:对于输入的参数,求和返回,但传入的参数个数并不确定. // 在es5中,通常是使用函数自身的arguments对象实现的 function ...
- vue项目中如何使用less
首先你的vue-cli下载完成 第一步 安装less-loader 依赖 npm install less less-loader --save-dev 直接自动就配置上了,不用手动配置 ...
- 《Pro SQL Server Internals, 2nd edition》
设计和优化索引 定义一种应用于所有地方的索引策略是不可能的.每个系统都是独特的,需要基于工作,业务需求和其他一些因素的自己的索引方法.然而,有几个设计的注意事项和指导方针可以被应用到每个系统. 在我们 ...
- Python 常见字符串常量和表达式
常见字符串常量和表达式 操作 解释 s = '' 空字符串 s = "spam's" 双引号和单引号相同 S = 's\np\ta\x00m' 转义序列 s = "&qu ...
- MySQL之内连接、左连接和右连接
数据表内数据如下: books表: ...
- 如何使用idea给系统平台添加子应用和应用的模块
1.添加模块smartcity-portal,由于模块是在smartcity-framework工程下的,所以按照图片所示添加 2.smartcity-portal模块添加完成后,由于portal-d ...
- Linux下忘记MySQL密码的解决方法和输入mysqld_safe --skip-grant-tables &后无法进入MySQL的解决方法
在Linux下忘记MySQL密码后我们可以通过一个mysql的参数--skip-grant-tables &轻松解决这个问题 亲测在CentOS有效 其中 --skip-grant-table ...
- LSC问题(不连续问题)
转载:http://blog.csdn.net/v_JULY_v/article/details/6110269 本文是动态规划算法中,网上写得最好的一个之一.看完很容易理解.需要重点理解的部分,我会 ...
- wxPython制作跑monkey工具(python3)-带事件百分比显示界面
一. wxPython制作跑monkey工具(python3)-带事件百分比显示界面 源代码 Run Monkey.py #!/usr/bin/env python import wx import ...