LinuxC链接过程
前言
链接器基础、链接形式、库的发布
一、链接器背景
C/C++ 应用程序 极少不依赖外部程序模块而能够单独运作。
外部模块(也有人叫做第三方或者3pp)一般有几种形式:“第三方源码”、“静态库”、“动态库”。
“将应用程序和第三方模块拼接在一起从而完成一个可以运行的完整程序”的工具是链接器。
静态库和动态库的链接过程分别如下:
二、链接形式:动态/静态链接
1.静态链接
假设可执行文件为X,静态链接形式下X= {X.o, Ext1.a, Ext2.a …},
优势在于链接完毕后就是一个不存在对外依赖的可执行程序;
缺点在于链接完毕后可执行程序X文件体积是各个模块之和,较为庞大
2.动态链接
假设可执行文件为Y,动态链接形式下Y={Y.o} + 附加动态库信息(此部分占用空间极少),
优势在于链接完毕后,执行文件体积小
缺点在于链接完毕后,执行文件依赖于系统提供的动态库(动态库不存在或者动态库不兼容容易造成程序错误)
3. 应用对比
第三方模块发布时大多采取了共享库(动态链接)的形式
三、一些示例
$pwd
/home/jianleya/trainning/C/print
$tree .
.
├── 3pp_src
│ ├── my_console.h
│ └── print.c
└── src
└── hello.c
$cat 3pp_src/my_console.h
#ifndef __MY_CONSOLE_H
#define __MY_CONSOLE_H
int my_printf(char const* format, ...);
#endif //__MY_CONSOLE_H
$cat 3pp_src/print.c
#include "my_console.h"
#include <stdarg.h>
#include <stdio.h>
void print_int(int i)
{
int mod = 0;
if(i != 0)
{
while(i > 10)
{
mod = i % 10;
i = i/10;
putchar(mod + '0');
}
putchar(i + '0');
}
}
void print_string(char const* string)
{
if(NULL != string)
{
while(*string != '\0')
{
putchar(*string++);
}
}
}
int print_default(char const *format)
{
int cnt = 0;
if(format != NULL)
{
while(*format == '%')
{
putchar(*format);
format++;
cnt++;
}
}
return cnt;
}
int my_printf(char const* format, ...)
{
if(NULL == format)
return 0;
char c;
int i;
float f;
char* s;
va_list ap;
va_start(ap,format);
while(*format != '\0')
{
if (*format == '%')
{
format++;
switch (*format)
{
case 'c':
c = va_arg(ap,int);
putchar(c);
break;
case 'd':
i = va_arg(ap,int);
print_int(i);
break;
case 's':
s = va_arg(ap,char*);
print_string(s);
break;
default:
format += print_default(format);
break;
}
format++;
}
else
{
putchar(*format);
format++;
}
}
va_end(ap);
}
$cat src/hello.c
#include "../3pp_src/my_console.h"
int main(void)
{
my_printf("%d %c%s\n",99,'R',"oses");
return 0;
}
1. make 静态库
#under 3pp_src
$gcc print.c -c -o print.o
$ar -rv libprint.a print.o
#under src
$gcc hello.c -L ../3pp_src/ -lprint
$tree .
.
├── 3pp_src
│ ├── libprint.a
│ ├── my_console.h
│ ├── print.c
│ └── print.o
└── src
├── a.out
└── hello.c
$./a.out
99 Roses
2. make 动态库
#under 3pp_src
$gcc -shared -g -fPIC print.c -o libprint.so
#under src
$gcc hello.c -L ../3pp_src/ -lprint
$export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/jianleya/trainning/C/print/3pp_src/
$tree .
.
├── 3pp_src
│ ├── libprint.so
│ ├── my_console.h
│ └── print.c
└── src
├── a.out
└── hello.c
$./a.out
99 Roses
3. 库的发布、使用
一般来讲,第三方库的提供者并不会提供源代码;一般是静态库或者动态库的形式;对使用者暴露的接口是头文件"*.h"
*.h 和 库文件被安装在系统目录下(通过设置shell环境变量),在此笔者列举几个可能的环境变量,对具体的实现过程不再追究
3.1 链接时环境
- 选项 -Bprefix
- 环境变量COMPILER_PATH
- 环境变量GCC_EXEC_PREFIX
- 环境变量PATH
这四项指定可执行文件cpp, cc1, as and ld的路径,优先顺序依次是-B选项,GCC_EXEC_PREFIX(默认是/usr/lib/gcc),COMPILER_PATH (编译器),PATH
除此:-B 选项 环境变量GCC_EXEC_PREFIX也被用于解释成-L选项
- 选项 -L
Add directory dir to the list of directories to be searched for -l
-l选项 后的库文件名,需要从-L指定的路径中检索 - 环境变量 LIBRARY_PATH
链接检索顺序 -L,其次LIBRARY_PATH,再次GCC_EXEC_PREFIX
3.2 运行时环境
除默认运行时的动态库路径之外,可以认为指定库文件的路径
环境变量:LD_LIBRARY_PATH
四、调试技巧
1. 文件格式检查
$file sysbol.o
symbol.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$file hello.elf
hello.elf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=fe1fe362cb4e81ba401e5904b56e981941117fe7, for GNU/Linux 3.2.0, not stripped
$file a.out
a.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b4d584f63975f9a5adce1f105af6dab92144fd82, for GNU/Linux 3.2.0, not stripped
$file libprint.so
libprint.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=7c3803a89a5dd0c4a6493f4a82fcd41aaf206c7e, with debug_info, not stripped
$file /bin/bash
bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a6cb40078351e05121d46daa768e271846d5cc54, for GNU/Linux 3.2.0, stripped
$gcc helloworld.c -static -o hello.elf
$file hello.elf
hello.elf: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=0caf2c42f66c3c7be0756d84db060841e2b4ce01, for GNU/Linux 3.2.0, not stripped
2. obj符号表检查
$cat symbol.c
static int static_int = 99;
int global_int = 100;
extern int extern_fun1(void);
extern int extern_val1;
static char *static_str = "this is a string";
char *global_str = "this is another string";
void fool()
{
extern_fun1();
extern_val1 = 0;
}
void static bar()
{
}
$nm a.out
000000000000001a t bar
U extern_fun1
U extern_val1
0000000000000000 T fool
0000000000000004 D global_int
U _GLOBAL_OFFSET_TABLE_
0000000000000008 D global_str
0000000000000000 d static_int
0000000000000000 d static_str
3. 库文件依赖
#递归性的依赖检查
$ldd a.out
linux-vdso.so.1 (0x00007ffc5e3fc000)
libprint.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe97fbed000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe97fdfe000)
#直接关联的依赖
$objdump -p a.out | grep NEEDED
NEEDED libprint.so
NEEDED libc.so.6