一个C程序的一生——开发态(Linux版)

主要参考资料:

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'

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值