Linux | c&cpp | Email | github | QQ群:425043908 关注本站

itarticle.cc

您现在的位置是:网站首页 -> Linux 文章内容

动态库so被覆盖导致coredump-itarticl.cc-IT技术类文章记录&分享

发布时间: 5年前Linux 129人已围观返回

一、为何cp覆盖进程的动态库(so)会导致coredump?

1.应用程序通过dlopen打开so的时候,kernel通过mmap把so加载到进程地址空间,对应于vma里的几个page.

2.在这个过程中loader会把so里面引用的外部符号例如malloc printf等解析成真正的虚存地址。

3.当so被cp覆盖时,确切地说是被trunc时,kernel会把so文件在虚拟内存的页清除掉。

4.当运行到so里面的代码时,因为虚拟内存的页已经清除掉了,这时会产生一次缺页中断。

5.缺页中断会导致Kernel从so文件中拷贝对应的页到内存中去:

  • a) 如果so里面依赖了外部符号,但是这时的外部符号并没有经过重新解析,当调用到时就产生segment fault
  • b) 如果需要的文件偏移大于新的so的地址范围,就会产生bus error.


cp并不改变目标文件的inode,事实上它的实现是这样的:

# strace cp test2 test 2>&1 | grep open.*test

open("test2", O_RDONLY|O_LARGEFILE) = 3

open("test", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4


下面就a这种情况用代码分析验证下。

//test.c

#include<stdio.h>

void test1(void){

int j=0;

printf("test1:j=%dn", j);

return ;

}

void test2(void){

int j=1;

return ;

}

执行下面命令生成so文件

gcc -fPIC -shared -o libtest.so test.c -g

//main.c

#include <stdio.h>

#include <dlfcn.h>

int main()

{

void *lib_handle;

void (*fn1)(void);

void (*fn2)(void);

char *error;

//表示要将库装载到内存,准备使用

lib_handle = dlopen("libtest.so", RTLD_LAZY);

if (!lib_handle)

{

fprintf(stderr, "%sn", dlerror());

return 1;

}

//获得指定函数(symbol)在内存中的位置(指针)

fn1 = dlsym(lib_handle, "test1");

if ((error = dlerror()) != NULL)

{

fprintf(stderr, "%sn", error);

return 1;

}

printf("fn1:0x%xn", fn1);

fn1();

fn2 = dlsym(lib_handle, "test2");

if ((error = dlerror()) != NULL)

{

fprintf(stderr, "%sn", error);

return 1;

}

printf("fn2:0x%xn", fn2);

fn2();

dlclose(lib_handle);

return 0;

}

执行命令:

gcc -o main main.c -ldl -g 

首先进行测试1,断点设置在27行,fn1()执行之前

Breakpoint 1, main () at main.c:27

//这时我们在另外一个终端执行下面的命令

//cp libtest.so libtest2.so

//cp libtest2.so libtest.so

27 fn1();

(gdb) s

test1 () at test.c:4

4 int j=0; //没有报错

(gdb) n

5 printf("test1:j=%dn", j);

(gdb) n

//出错,因为引用了printf外部函数,而全局符号表并没有经过重新解析,找不到printf函数

Program received signal SIGSEGV, Segmentation fault.

0x00000396 in ?? ()

(gdb) bt

#0 0x00000396 in ?? ()

#1 0xb7fd84aa in test1 () at test.c:5

#2 0x08048622 in main () at main.c:27

下面进行测试2,断点设置在38行,fn2执行之前。

然后在另一个终端执行和测试1相同的cp操作

Breakpoint 1, main () at main.c:38

38 fn2();

(gdb) s

test2 () at test.c:10

10 int j=1;

(gdb) n

12 }

(gdb) n

main () at main.c:40

40 dlclose(lib_handle);

(gdb) n

42 return 0;

(gdb)

43 }//程序正常结束

从这两个测试例子中,我们可以得到这样的结论:

当用新的so文件去覆盖老的so文件时候:

A)如果so里面依赖了外部符号,程序会core掉

B)如果so里面没有依赖外部符号,so部分代码可以正常运行


二、中为什么动态换bin程序不会core而换so容易core?

  Linux中, 如果一个程序正在运行中,那么要动态替换程序,cp new old, 会发现报“text file busy"。用 strace 查看cp命令输出,会发现报:open old的时候,用了 O_WRONLY|O_TRUNC,open 返回 ETXTBSY (Text file busy)。也就是说,这时候这个文件已经是不可更改的了。如果用 cp -rf 复制,检验下又会发现,其实复制得到的文件的文件虽然还是原来的名字,但是 inode 已经变了。也就是说,cp -rf 其实还是没有真正的覆盖成功。

  这些都是为什么呢?首先不得不说下linux中二进制文件执行的时候的延迟加载。也就是说如果一个bin文件并不会一次性加载进内存,而是按需逐步加载的。为了防止bin文件修改后动态按需load的时候出错,所以内核系统就会把文件锁死,使得不能随便更改。这解释了为什么会“text file busy”。同时也说明了,rm + cp方式动态替换程序的时候,或者动态删除 bin 的时候,“延迟加载”不会导致程序出core。因为文件的inode还没有释放,等于说原文件还存在。

  对于 .so 动态库文件,动态覆盖容易导致出core,是因为系统没有对so作特殊保护,不会"text file busy"之故。

发布时间: 5年前Linux129人已围观返回回到顶端

很赞哦! (1)

文章评论

  • 请先说点什么
    热门评论
    128人参与,0条评论

站点信息

  • 建站时间:2016-04-01
  • 文章统计:728条
  • 文章评论:82条
  • QQ群二维码:扫描二维码,互相交流