目录
研究的问题
问题分类
可执行程序及其依赖动态库升级问题,有以下几种可能:
-
可执行程序是依赖老版本动态库编译的,未升级,动态库升级
-
可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。
-
可执行程序是依赖新版本动态库编译的,已升级,运行环境动态库也已经升级
第3中情况制定是没有问题,但是1,2种情况是否有问题呢?而且看一些开源库经常能看到结构体定义中会预留几个字段,很是迷惑,如果之后需要添加结构体字段,直接在后续版本中添加不就可以了吗?为什么需要还没有使用就预留呢。
测试程序介绍
工程目录
src 源码目录
so 是动态库编译目录
install 是安装目录
动态库程序
dym.c
#include <stdio.h>
#include "dym.h"
#ifdef __DYM_V_1__
int dym_dump(dym_t* d)
{
if(!d) {
return 0;
}
printf("dym = {id:%d;name:%s;att1:%d}\n", d->id, d->name, d->attr1);
return 0;
}
#elif defined __DYM_V_2__
int dym_dump(dym_t* d)
{
if(!d) {
return 0;
}
printf("dym2 = {id:%d;name:%s;att1:%d;name2=%s;}\n", d->id, d->name, d->attr1, d->name2);
return 0;
}
#endif
dym.h
#ifndef __DYM_H__
#define __DYM_H__
typedef struct dym_s
{
int id;
char name[256];
#ifdef __DYM_V_2__
char name2[4];
#endif
int attr1;
}dym_t;
int dym_dump(dym_t* d);
#endif
so-makefile
VER = 1
DYM_NAME = dym
OBJS = dym.o
LIB = lib$(DYM_NAME).so.$(VER)
LIB_LNS = lib$(DYM_NAME).so
DYM_V = __DYM_V_$(VER)__
CFLAG = -D$(DYM_V) -O0 -g3
all:$(LIB)
@rm -f $(LIB_LNS)
@ln -s $(LIB) $(LIB_LNS)
$(LIB):$(OBJS)
gcc -shared -Wl,-soname,libdym.so -o $@ $^
%.o:%.c
gcc $(CFLAG) -c -fPIC -o $@ $^
clean:
@rm -f $(OBJS)
@rm -f lib$(DYM_NAME).so*
install:
@cp -d lib* ../../install/
主程序源码
main.c
#include <stdio.h>
#include <string.h>
#include "so/dym.h"
int main(void)
{
dym_t d1 = {123, "d1",
#ifdef __DYM_V_2__
"d1",
#endif
1};
dym_t d2;
d2.id = 124;
strcpy(d2.name, "d2");
#ifdef __DYM_V_2__
strcpy(d2.name2, "d2");
#endif
d2.attr1 = 2;
#ifdef __DYM_V_1__
printf("main-d1 = {id:%d;name:%s;att1:%d}\n", d1.id, d1.name, d1.attr1);
printf("main-d2 = {id:%d;name:%s;att1:%d}\n", d2.id, d2.name, d2.attr1);
#elif defined __DYM_V_2__
printf("main-d1 = {id:%d;name:%s;att1:%d;name2=%s}\n", d1.id, d1.name, d1.attr1, d1.name2);
printf("main-d2 = {id:%d;name:%s;att1:%d;name2=%s}\n", d2.id, d2.name, d2.attr1, d2.name2);
#endif
dym_dump(&d1);
dym_dump(&d2);
}
main-makefile
VER = 2
ELF_NAME = main
LINK = -L./so -ldym
OBJS = main.o
DYM_V = __DYM_V_$(VER)__
CFLAG = -D$(DYM_V) -O0 -g3
ALL:$(ELF_NAME)
$(ELF_NAME):$(OBJS)
gcc -o $@ $^ $(LINK)
%.o:%.c
gcc $(CFLAG) -c $^ -o $@
clean:
@rm $(OBJS) $(ELF_NAME)
install:
@cp main ../install/
测试思路
1、so-makefile 和 main-makefile 通过设置ver参数改变编译的版本
2、通过main 和 so 版本不同,例如main-1版本依赖so-2版本运行,然后安装到install目录观察结果。
只升级so,可执行程序不升级
正常情况:main-1版本依赖so-1版本编译和运行
- main-makefile 和 so-makefile 的VER变量 都设置为1
- 在src/so 目录执行make clean;make;make install
- 在src 目录执行make clean;make;make install
- 在install 目录执行./main 结果如下:
./main
main-d1 = {id:123;name:d1;att1:1}
main-d2 = {id:124;name:d2;att1:2}
dym = {id:123;name:d1;att1:1}
dym = {id:124;name:d2;att1:2}
异常情况:main-1版本依赖so-2版本编译和运行
- 在“正常情况”基础上,把so-makefile VER修改为2
- 在src/so 目录执行make clean;make;make install
- 在install 目录执行./main 结果如下:
./main
main-d1 = {id:123;name:d1;att1:1}
main-d2 = {id:124;name:d2;att1:2}
dym2 = {id:123;name:d1;att1:4195760;name2=;}
dym2 = {id:124;name:d2;att1:-1411195112;name2=;}
分析
- 由于main还是中是依赖dym_t-1版本编译的,所以dym_t结构体成员的访问,还是用dym_t-1版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
- 当main中调用dym_dump函数,传入的还是dym_t-1版本的地址结构(此处用gdb打印了一下,但main函数中dym_t的sizeof是264,dym_dump函数中打印是268),所以在dym_dump 中按dym_t-2版本访问dym_t-1版本长度的内存会出现问题。
- 其实在dym_dump中访问attr1的时候已经越界来,所以attr1是传入dym_dump函数dym-t-1版本268字节后的四个字节。
- 而name2现在使其访问的是main函数中attr1的地址,结果如下:
> x/4xb d->name2
0x7fffffffead4: 0x01 0x00 0x00 0x00
可执行程序依赖新版本so编译,但是运行时依赖老版本so
异常情况:main-2版本依赖so-1版本运行
- 在“正常情况”基础上,把main-makefile和dym-makefile的VER变量修改为2
- 在src/so,执行make clean;make(不要执行make install)
- 在src目录执行main clean;make;make install;
- 在install目录 运行./main
> ./main
main-d1 = {id:123;name:d1;att1:1;name2=d1}
main-d2 = {id:124;name:d2;att1:2;name2=d2}
dym = {id:123;name:d1;att1:12644}
dym = {id:124;name:d2;att1:12900}
分析
- 由于main还是中是依赖dym_t-2版本编译的,所以dym_t结构体成员的访问,还是用dym_t-2版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
- 当调用dym_dump函数时,实际是把dym_t-2版本的内存结构(268字节)传送给dym_dump函数,犹豫dym_dump 是按so-1版本编译的,所以会按dym_t-1版本成员地址访问dym_t-2版本内存结果。
- 所以dym_t-1版本attr1,实际访问的dym_t-2版本的name2地址,结果如下:实际值为d2
> x/4xb &d->attr1
0x7fffffffe9c4: 0x64 0x32 0x00 0x00
结论
1、可执行程序是依赖老版本动态库编译的,未升级,动态库升级
此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。
2、可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。
此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。
综上所述:
改变动态库结构体可能带来向下的可执行程序不兼容。所以如果一些结构体未来可能改变,可以把接头体声明为对外结构不可见的,通过提供全面函数操作结构体。
源码在0001so-update.tar.gz中