主要参考资料:
HelloWorld的一生:https://zhuanlan.zhihu.com/p/513307151
前置任务:
1.VIM
Ubuntu自带的vim tiny版本很不好用,需要重新安装
apt isntall vim-gtk3
VIM基础使用可查询:
https://zdaiot.com/Tools/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB/VIM%E5%85%A5%E9%97%A8/
了解VIM后简单写一个hello world
#include <stdio.h>
int main(){
printf("Hello,World\n");
return 0;
}
使用GCC编译
gcc在编译一个C源文件需要经过4步:
预处理
将头文件的代码、宏转换成C代码附加到要编译的C代码前,输出.i文件
gcc -E hello.c | less ,可以查看预处理的结果,通过/printf 可以搜索到printf函数所在的头文件
# 292 "/usr/include/stdio.h" 3 4
extern FILE *fmemopen (void *__s, size_t __len, const char *__modes)
__attribute__ ((__nothrow__ , __leaf__)) ;
...(中间代码省略)
extern int printf (const char *__restrict __format, ...);
使用GCC的参数 “-E”,可以让编译器生成 .i 文件,参数 “-o”,可以指定输出文件的名字。
预处理后的文件主要包含:
- 外部库.h的文件路径及其标志位(关于.i文件中外部库文件标志位的解释:https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html)
- 类型重命名
- 引用外部变量、函数
编译
将hello.i 翻译成 汇编语言
gcc -S -masm=intel hello.c -o hello.s 以intel指令集输出汇编代码,从如下代码可以看到编译器优化了printf使用puts替代,HelloWorld字符串中也删除了\n
.file "hello.c"
.intel_syntax noprefix//intel指令集标记,若没有使用-masm=intel,则没有这条
.text//定义代码段,类似.CODE?
.section .rodata //使用section定义了rodata只读数据段
.LC0:
/*
;.LCn:是 Local Constant 的缩写。
;.LFBn:是 Local Function Beginning 的缩写。
;.LFEn:是 Local Function Ending 的缩写。
;.LBBn:是 Local Block Beginning 的缩写。
;.LBEn:是 Local Block Ending 的缩写。
*/
.string "Hello,World"//这里可以看到我们要打印的常量字符串是存在LC0中的
.text
.globl main//定义一个全局symbol
.type main, @function//这个symbol的类型为一个函数
main:
.LFB0:
.cfi_startproc//.cfi_ 开头的汇编指示符用来告诉汇编器生成相应的 DWARF 调试信息,主要是和函数有关。.cfi_startproc 定义函数开始,.cfi_endproc 定义函数结束。
endbr64//Intel的间接跳转保护标记,https://blog.csdn.net/clh14281055/article/details/117446588
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
lea rdi, .LC0[rip]
call puts@PLT
mov eax, 0
pop rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
汇编
将汇编语言转换为可执行文件,即ELF文件。gcc -c hello.c -o hello.o
可以通过objdunmp查看hello.o文件 objdump -drs hello.o
也可以通过readelf命令查看,关于ELF文件格式解析参考:从零解析ELF目标文件(附源码) - 知乎 (zhihu.com)
(准备写个ELF文件格式的帖子____________)
root@iotsec-VirtualBox:/home/iotsec/code_test# readelf hello.o -a
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 792 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000001b 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000258
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 0000005b
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 0000005b
000000000000000c 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000067
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000093
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000098
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 000000b8
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000288
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 000000f0
0000000000000138 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000228
0000000000000029 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 000002a0
0000000000000074 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
本文件中没有程序头。
There is no dynamic section in this file.
重定位节 '.rela.text' at offset 0x258 contains 2 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
00000000000b 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000010 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4
重定位节 '.rela.eh_frame' at offset 0x288 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 27 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
No version information found in this file.
Displaying notes found in: .note.gnu.property
所有者 Data size Description
GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature: IBT, SHSTK
root@iotsec-VirtualBox:/home/iotsec/code_test#
链接
将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。
链接器在软件开发中扮演着一个关键的角色,因为它使得分离编译成为可能。我们不用将一个大型应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中地一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。即使对hello这样一个非常简单的小程序,链接的作用也是巨大的。
关于静态链接和动态链接的细节和区别推荐阅读《程序员的自我修养》
# 在hello.o所在的目录执行如下命令
# 动态链接,生成名为hello的可执行文件,动态链接需要依赖外部的库文件,可以使用ldd命令查看,因此体积更小
gcc hello.o –o hello
# 也可以直接使用C文件一步生成,与上面的命令等价
gcc hello.c -o hello
# 静态链接,使用--static参数,生成名为hello_static的可执行文件,静态链接则不需要外部库支持,所以体积更大
gcc hello.o –o hello_static --static
# 也可以直接使用C文件一步生成,与上面的命令等价
gcc hello.c -o hello_static --static
我们分别查看动态链接和静态链接生成的文件发现他们的ELF文件头的Type字段也不同,分别是DYN和EXEC,这里的区别__________中会讲
共享库和程序预装载
1.生成共享库
gcc -shared -o mylibso.so testso.c
gcc -shared -fPIC -o mylibso_fPIC.so testso.c
这里通过查看两个so文件的elf head 可以确认二者完全一致,证明Ubuntu的gcc默认是开启fPIC的
root@iotsec-VirtualBox:/home/iotsec/code_test# readelf -h libmyso_fPIC.so
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x1060
程序头起点: 64 (bytes into file)
Start of section headers: 14280 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
root@iotsec-VirtualBox:/home/iotsec/code_test# readelf -h libmyso.so
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x1060
程序头起点: 64 (bytes into file)
Start of section headers: 14280 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
通过objdump查看也可以看到是通过plt表寻址到printf函数的
2.链接共享库
gcc testmain.c -L. -lmyso -o testmain_1
LIBRARY_PATH=. gcc testmain.c -lmyso -o testmain_2
在静态库和动态库中,Linux有自己的命名约定,我们在使用的时候可以使用全名,也可以使用简单缩写名,即
libxxxx.a 等价于 -lxxxx
libxxxx.so 等价于 -lxxxx
参见https://zhuanlan.zhihu.com/p/377651202
链接成功之后不代表能够运行,通过ldd命令可以看到libmyso库是找不到路径的
那Linux中搜索.so文件的逻辑是什么?
在二进制文件中 DT_RPATH 动态段属性中指定的目录下搜索(不推荐)
- 在环境变量 LD_LIBRARY_PATH 中指定的路径下搜索(除非在安全执行模式下——不讨论)
- 在二进制文件中 DT_RUNPATH 动态段属性中指定的目录下搜索
- 在文件 /etc/ld.so.cache 中缓存的 .so 名字里搜索
- 在系统默认库路径(/lib、/usr/lib 等)下搜索
通过LD_DEBUG=libs ./tstmain_1可以看到.so文件搜索的过程
root@iotsec-VirtualBox:/home/iotsec/code_test# LD_DEBUG=libs ./testmain_1
329468: find library=libachk.so [0]; searching
329468: search cache=/etc/ld.so.cache
329468: trying file=/lib/libachk.so
329468:
329468: find library=libmyso.so [0]; searching
329468: search cache=/etc/ld.so.cache
329468: search path=/lib/x86_64-linux-gnu/tls/x86_64/x86_64:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls/x86_64:/lib/x86_64-linux-gnu/tls:/lib/x86_64-linux-gnu/x86_64/x86_64:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu/x86_64:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls/x86_64:/usr/lib/x86_64-linux-gnu/tls:/usr/lib/x86_64-linux-gnu/x86_64/x86_64:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu/x86_64:/usr/lib/x86_64-linux-gnu:/lib/tls/x86_64/x86_64:/lib/tls/x86_64:/lib/tls/x86_64:/lib/tls:/lib/x86_64/x86_64:/lib/x86_64:/lib/x86_64:/lib:/usr/lib/tls/x86_64/x86_64:/usr/lib/tls/x86_64:/usr/lib/tls/x86_64:/usr/lib/tls:/usr/lib/x86_64/x86_64:/usr/lib/x86_64:/usr/lib/x86_64:/usr/lib (system search path)
329468: trying file=/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/tls/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/tls/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/tls/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/x86_64/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/x86_64/libmyso.so
329468: trying file=/lib/x86_64-linux-gnu/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/tls/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/tls/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/x86_64/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64-linux-gnu/libmyso.so
329468: trying file=/lib/tls/x86_64/x86_64/libmyso.so
329468: trying file=/lib/tls/x86_64/libmyso.so
329468: trying file=/lib/tls/x86_64/libmyso.so
329468: trying file=/lib/tls/libmyso.so
329468: trying file=/lib/x86_64/x86_64/libmyso.so
329468: trying file=/lib/x86_64/libmyso.so
329468: trying file=/lib/x86_64/libmyso.so
329468: trying file=/lib/libmyso.so
329468: trying file=/usr/lib/tls/x86_64/x86_64/libmyso.so
329468: trying file=/usr/lib/tls/x86_64/libmyso.so
329468: trying file=/usr/lib/tls/x86_64/libmyso.so
329468: trying file=/usr/lib/tls/libmyso.so
329468: trying file=/usr/lib/x86_64/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64/libmyso.so
329468: trying file=/usr/lib/x86_64/libmyso.so
329468: trying file=/usr/lib/libmyso.so
329468:
./testmain_1: error while loading shared libraries: libmyso.so: cannot open shared object file: No such file or directory
那如何解决载入路径中没有链接的库呢?
方法一:执行时带入环境变量
方法二:从可执行文件所在的路径下寻找链接的库·
gcc testmain.c -L. -lmyso -Wl,-rpath,'$ORIGIN'