linux so 中结构体改变对已有可执行程序运行的影响

目录

 

研究的问题

问题分类

测试程序介绍

工程目录

动态库程序

dym.c

dym.h

so-makefile

主程序源码

main.c

main-makefile

测试思路

只升级so,可执行程序不升级

正常情况:main-1版本依赖so-1版本编译和运行

异常情况:main-1版本依赖so-2版本编译和运行

分析

可执行程序依赖新版本so编译,但是运行时依赖老版本so

异常情况:main-2版本依赖so-1版本运行

分析

结论


研究的问题

问题分类

可执行程序及其依赖动态库升级问题,有以下几种可能:

  1. 可执行程序是依赖老版本动态库编译的,未升级,动态库升级

  2. 可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。

  3. 可执行程序是依赖新版本动态库编译的,已升级,运行环境动态库也已经升级

第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版本编译和运行

  1. main-makefile 和 so-makefile 的VER变量 都设置为1
  2. 在src/so 目录执行make clean;make;make install
  3. 在src 目录执行make clean;make;make install
  4. 在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版本编译和运行

  1. “正常情况”基础上,把so-makefile VER修改为2
  2. 在src/so 目录执行make clean;make;make install
  3. 在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=;}

分析

  1. 由于main还是中是依赖dym_t-1版本编译的,所以dym_t结构体成员的访问,还是用dym_t-1版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
  2. 当main中调用dym_dump函数,传入的还是dym_t-1版本的地址结构(此处用gdb打印了一下,但main函数中dym_t的sizeof是264,dym_dump函数中打印是268),所以在dym_dump 中按dym_t-2版本访问dym_t-1版本长度的内存会出现问题。
  3. 其实在dym_dump中访问attr1的时候已经越界来,所以attr1是传入dym_dump函数dym-t-1版本268字节后的四个字节。
  4. 而name2现在使其访问的是main函数中attr1的地址,结果如下:
 > x/4xb d->name2
0x7fffffffead4: 0x01    0x00    0x00    0x00

可执行程序依赖新版本so编译,但是运行时依赖老版本so

异常情况:main-2版本依赖so-1版本运行

  1. “正常情况”基础上,把main-makefile和dym-makefile的VER变量修改为2
  2. 在src/so,执行make clean;make(不要执行make install)
  3. 在src目录执行main clean;make;make install;
  4. 在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}

分析

  1. 由于main还是中是依赖dym_t-2版本编译的,所以dym_t结构体成员的访问,还是用dym_t-2版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
  2. 当调用dym_dump函数时,实际是把dym_t-2版本的内存结构(268字节)传送给dym_dump函数,犹豫dym_dump 是按so-1版本编译的,所以会按dym_t-1版本成员地址访问dym_t-2版本内存结果。
  3. 所以dym_t-1版本attr1,实际访问的dym_t-2版本的name2地址,结果如下:实际值为d2
> x/4xb &d->attr1
0x7fffffffe9c4: 0x64    0x32    0x00    0x00

结论

1、可执行程序是依赖老版本动态库编译的,未升级,动态库升级

此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。

2、可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。

此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。

综上所述:

改变动态库结构体可能带来向下的可执行程序不兼容。所以如果一些结构体未来可能改变,可以把接头体声明为对外结构不可见的,通过提供全面函数操作结构体。

源码在0001so-update.tar.gz中

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值