加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_新乡站长网 (https://www.0373zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

linux内核修改动态库,so文件动态替换方法及Linux动态库的用法

发布时间:2022-11-24 12:46:20 所属栏目:Linux 来源:
导读:  在替换so文件时,如果在不停程序的情况下,直接用 cp new.so old.so 的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。解决的办法是采用“rm+cp” 或“mv+cp” 来替代直接&ldq
  在替换so文件时,如果在不停程序的情况下,直接用 cp new.so old.so 的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。解决的办法是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。linux系统的动态库有两种使用方法:运行时动态链接库,动态加载库并在程序控制之下使用。
 
  1、为什么在不停程序的情况下,直接用 cp 命令替换程序使用的 so 文件,会使程序崩溃?
 
  很多同学在工作中遇到过这样一个问题,在替换 so 文件时,如果在不停程序的情况下,直接用cp new.so old.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃,退出。这与 cp 命令的实现有关,cp 并不改变目标文件的 inode,cp 的目标文件会继承被覆盖文件的属性而非源文件。实际上它是这样实现的:
 
  strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so
 
  open("libnew.so", O_RDONLY|O_LARGEFILE) = 3
 
  open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4
 
  在 cp 使用“O_WRONLY|O_TRUNC” 打开目标文件时,原 so 文件的镜像被意外的破坏了。这样动态链接器 ld.so 不能访问到 so 文件中的函数入口。从而导致 Segmentation fault,程序崩溃。ld.so 加载 so 文件及“再定位”的机制比较复杂,详情可参见参考文献2。
 
  2、怎样在不停止程序的情况下替换so文件,并且保证程序不会崩溃?
 
  答案是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。
 
  在用新的so文件 libnew.so 替换旧的so文件 libold.so 时,如果采用如下方法:
 
  rm libold.so
 
  cp libnew.so libold.so
 
  采用这种方法,目标文件 libold.so 的 inode 其实已经改变了linux动态库,原来的 libold.so 文件虽然不能用”ls”查看到,但其 inode 并没有被真正删除,直到内核释放对它的引用。同理,mv只是改变了文件名,其 inode 不变,新文件使用了新的 inode。这样动态链接器 ld.so 仍然使用原来文件的 inode 访问旧的 so 文件。因而程序依然能正常运行。
 
  到这里,我们回想在上线操作中在替换可执行程序时,为什么直接使用“cp new old”这样的命令时,系统会禁止这样的操作,并且给出这样的提示“cp: cannot create regular file `old': Text file busy”。这时,我们采用的办法仍然是用“rm+cp”或者“mv+cp”来替代直接“cp”,这跟以上提到的so文件的替换有同样的道理。
 
  但是,为什么系统会阻止 cp 覆盖可执行程序,而不阻止覆盖 so 文件呢?这是因为 Linux 有个 Demand Paging 机制,所谓“Demand Paging”,简单的说,就是系统为了节约物理内存开销,并不会程序运行时就将所有页(page)都加载到内存中,而只有在系统有访问需求时才将其加载。
 
  “Demand Paging”要求正在运行中的程序镜像(注意,并非文件本身)不被意外修改,因此内核在启动程序后会锁定这个程序镜像的 inode。对于 so 文件,它是靠 ld.so 加载的,而ld.so毕竟也是用户态程序,没有权利去锁定inode,也不应与内核的文件系统底层实现耦合。
 
  3、linux动态库的使用方法 Linux 支持两种类型的库静态库和动态库。 静态库包含在编译时静态绑定到一个程序的函数。我们这里关心的是动态库。动态库是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。
 
  程序使用动态库的方法有两种:
 
  (1)、在运行时动态链接库
 
  这种方法是动态的将程序和共享库链接并让 Linux 在执行时加载库。
 
  例:
 
  fc@fengchun~/project/so$ cat foo.c
 
  #include
 
  void foo()
 
  {
 
  printf("infoo\n");
 
  }
 
  cat test1.c
 
  #include
 
  int main(int argc, char * argv[])
 
  {
 
  loop:
 
  foo();
 
  sleep(1);
 
  goto loop;
 
  return 0;
 
  }
 
  首先将文件 foo.c 编译成动态库 libtest.so:
 
  gcc foo.c -fPIC -shared -o libtest.so
 
  然后将文件 test1.c 编译可执行程序:
 
  gcc test1.c -L. -ltest -o test1
 
  编译参数 “-L.” 指定编译器在当前目录(.)查找动态库文件,编译参数 “-ltest”指定编译器连接库文件 libtest.so。
 
  执行可执行程序 test1
 
  ./tsest1
 
  程序将在屏幕上每间隔1秒打印1次“infoo”,此时可以偿试执行一下用直接cp覆盖的方法替换libtest.so文件。
 
  cp libtest.so libtest1.so #即使用完全相同的so文件覆盖
 
  cp libtest1.so libtest.so
 
  可以看到程序立即崩溃退出。
 
  fc@fengchun~/project/so$ ./test1
 
  infoo
 
  infoo
 
  Segmentation fault
 
  (2)、在运行时动态加载库并在程序控制之下使用它们。
 
  通过这种方法使用动态库,对于库文件本身的编写和编译与方法1是相同的。但在可执行程序中需要使用一个称为动态加载的过程,这样程序可以有选择地调用库中的函数。动态加载(Dynamic Loading,DL)API 就是为了动态加载而存在的,它允许共享库对用户空间程序可用。尽管非常小,但是这个 API 提供了所有需要的东西,而且很多困难的工作是在后台完成的。
 
  动态加载API主要包括以下函数:
 
  #include
 
  void *dlopen(const char *filename, int flag);
 
  char *dlerror(void);
 
  void *dlsym(void *handle, const char *symbol);
 
  int dlclose(void *handle);
 
  以下是一个使用动态加载API的例子:
 
  fc@fengchun~/project/so$ cat foo.c
 
  #include
 
  void foo()
 
  {
 
  printf("infoo\n");
 
  }
 
  fc@fengchun~/project/so$ cat test2.c
 
  #include
 
  #include
 
  #include
 
  int main( )
 
  {
 
  void *dl_handle;
 
  float (*func)();
 
  char *error;
 
  /* Open the shared object */
 
  dl_handle = dlopen( "./libtest.so", RTLD_LAZY );
 
  if (dl_handle) {
 
  printf( "!!! %s\n", dlerror() );
 
  return;
 
  }
 
  while (1) {
 
  /* Resolve the symbol (method) from the object */
 
  func = dlsym( dl_handle, "foo" );
 
  error = dlerror();
 
  if (error = NULL) {
 
  printf( "!!! %s\n", error );
 
  return;
 
  }
 
  /* Call the resolved method and print the result */
 
  (*func)();
 
  sleep(1);
 
  }
 
  /* Close the object */
 
  dlclose( dl_handle );
 
  return;
 
  }
 
  首先将文件 foo.c 编译成动态库 libtest.so:
 
  gcc foo.c -fPIC -shared -o libtest.so
 
  然后将文件 test2.c 编译可执行程序:
 
  gcc -rdynamic -o test2 test2.c -ldl
 
  编译参数“-rdynamic” 用来通知链接器将所有符号添加到动态符号表中(目的是能够通过使用 dlopen 来实现向后跟踪),编译参数 “-ldl”指定编译器连接库 libdl
 
  执行可执行程序 test2:
 
  ./tsest2
 
  程序将在屏幕上每间隔1秒打印1次“infoo”,此时可以偿试执行一下用直接cp覆盖的方法替换libtest.so文件。
 
  cp libtest.so libtest1.so #即使用完全相同的so文件覆盖
 
  cp libtest1.so libtest.so
 
  可以看到程序立即崩溃退出。
 
  fc@fengchun~/project/so$ ./test2
 
  infoo
 
  infoo
 
  Segmentation fault
 

(编辑:开发网_新乡站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!