自学内容网 自学内容网

一次使用LD_DEBUG定位问题的经历

在实际工作中,当遇到段错误,我们会很容易的想到这是非法访问内存导致的,比如访问了已经释放的内存,访问数据越界,尝试写没有写权限的内存等。使用gdb进行调试,查看出异常的调用栈,往往可以定位到问题原因。

但是有些时候,段错误问题的定位也没这么简单。本人在工作中就遇到了一个段错误的问题,问题原因不是那么直观,出问题的代码非常简单,直接按照常规的分析方法,很难分析出原因。往内存越界、内存没有权限这个方向分析无法定位问题。

当我们遇到这种问题的时候,没有思路的时候,往往会采用一种方式,就是把编译环境清空,完全重新再编译一遍。的确,很多时候如果我们的运行环境,有些库和可执行文件,如果不是在一个统一的环境下编译出来的,版本号对应不上,也会引起这样的问题。但是对于符号覆盖的情况,即使重新编译一遍,也无法解决问题。

除了重新编译一遍,我们还可以使用LD_DEBUG来查看调试信息,LD_DEBUG可以查看共享库的搜索和链接过程,也可以看到符号的绑定过程。

问题的实际原因:动态库中有同名的符号,在执行程序的时候出现了符号覆盖。

1问题复现

可执行文件直接依赖两个动态库,两个动态库有同名符号,这样编译可执行文件的时候无法编译通过。如果要复现这个问题,需要间接依赖。

如下图所示,liba.so和libb.so中有重名符号。libaa.so依赖liba.so,libbb.so依赖libb.so,可执行文件依赖libaa.so和libbb.so。可执行文件间接依赖liba.so和libb.so。

liba.so对于类test::example::Hello,在构造函数中申请了8字节的内存,在函数DoSame中对内存进行了写,在析构函数中释放了内存。
libb.solibb.so中的函数test::example::Hello::DoSame与liba.so中的函数重名。
libaa.so调用了liba.so中函数test::example::Hello::DoA
libbb.so调用了libb.so中的test::example::Hello::DoB和和test::example::Hello::DoSame
main在main函数中调用了libaa.so中的DoAA和libbb.so中的DoSame

main函数中调用了3个函数,分别为DoAA、DoBB、DoSame,调用关系如下图所示。

1.1liba.so

liba.h

#ifndef _LIB_A_H_
#define _LIB_A_H_

namespace test {
namespace example {
class Hello {
  public:
    Hello();
    ~Hello();
    void DoA();
    void DoSame();

    char *p = nullptr;
};
}
}

#endif

liba.cpp

#include <stdlib.h>
#include <iostream>
#include "liba.h"

namespace test {
namespace example {

Hello::Hello() {
  p = (char *)malloc(8);
  printf("p: %p, this: %p\n", p, this);
}

Hello::~Hello() {
  if (p) {
    std::cout << "free p\n";
    free(p);
  }
}

void Hello::DoA() {
  std::cout << "do a in liba.\n";
}

void Hello::DoSame() {
  std::cout << "do same in liba.\n";
  p[0] = 'a';
}
}
}

1.2libb.so

libb.h

#ifndef _LIB_B_H_
#define _LIB_B_H_

namespace test {
namespace example {
class Hello {
  public:
    void DoB();
    void DoSame();
};
};
};

#endif

libb.cpp

#include <iostream>
#include "libb.h"

namespace test {
namespace example {
void Hello::DoB() {
  std::cout << "do b in libb.\n";
}

void Hello::DoSame() {
  std::cout << "do same in libb.\n";
}
};
};

1.3libaa.so

libaa.h

void DoAA();

libaa.cpp

#include "libaa.h"
#include "liba.h"
#include <iostream>

void DoAA() {
  std::cout <<"do aa\n";
  test::example::Hello hello;
  hello.DoA();
}

1.4libbb.so

libbb.h

#include "libb.h"

void DoBB();
void DoSame();

libbb.cpp

#include <iostream>
#include "libbb.h"
#include "libb.h"

void DoBB() {
  std::cout << "do bb\n";
  test::example::Hello hello;
  hello.DoB();
}

void DoSame() {
  std::cout << "do same\n";
  test::example::Hello hello;
  hello.DoSame();
}

1.5main函数

#include <iostream>
#include <string>
#include "libaa.h"
#include "libbb.h"

int main() {
    DoAA();
    DoBB();
    DoSame();
    return 0;
}

使用如下命令进行编译:

g++ liba.cpp -fPIC --shared -o liba.so -g

g++ libb.cpp -fPIC --shared -o libb.so -g

g++ libbb.cpp libb.so -fPIC --shared -o libbb.so -g

g++ libaa.cpp liba.so -fPIC --shared -o libaa.so -g

g++ main.cpp libaa.so libbb.so -g

运行结果,如下,可以看到,出现了段错误。

使用gdb进行定位,在p[0]='a'这行代码出现了段错误。打印p的话,发现p是一个非法地址。

可以看到,按照我们的预期,main函数中调用DoSame是调用的libbb.so中的DoSame,而libbb.so中的DoSame是调用的libb.so中的test::example::Hello::DoSame。但是根据gdb中的栈可以看到,最终调用到了liba.so中的test::example::Hello::DoSame。

调用函数出现了错乱,与预期的不一致。

编译可执行文件的时候,如果有间接调用的函数,那么最终调用的函数不是在编译的时候确定的。而是在运行的时候动态绑定的。比如libbb.so中DoSame调用的test::example::Hello::DoSame,这个函数最终调用liba.so中的还是libb.so中的,是需要动态绑定的。

在启动a.out的时候,使用LD_DEBUG进行调试,如下命令,就可以看到运行是符号绑定的信息。

LD_DEBUG=bindings ./a.out

如下所示,DoSame最终调用到了libs.so中的函数,所以调用的函数与预期的不一致。

     41428:     binding file /home/wangyanlong/test/symboltest/libbb.so [0] to /home/wangyanlong/test/symboltest/liba.so [0]: normal symbol `_ZN4test7example5Hello6DoSameEv'

2符号优先级

2.1动态库中重复的符号

上一节中,编译main.cpp的时候使用的是下边的命令,libaa.so在libbb.so之前,这样编译的话,查找符号会优先使用liba.so中的符号。

g++ main.cpp  libaa.so libbb.so -g

如果修改两个库的顺序,libbb.so放在libss.so之前,那么就会绑定libb.so中的DoSame。

g++ main.cpp libbb.so libaa.so

2.2静态库和动态库重复符号

静态库中的符号是硬编到可执行文件中的,在绑定符号的时候肯定是可执行文件优先。如下所示,将liba.cpp编译成静态库。编译main.cpp的时候不管libaa.so、liba.a、libbb.so的先后顺序,test::example::Hello::DoSame都会绑定liba.so中的符号。

gcc liba.cpp -c

ar rcs liba.a liba.o

g++ libb.cpp -fPIC --shared -o libb.so -g

g++ libbb.cpp libb.so -fPIC --shared -o libbb.so -g

g++ libaa.cpp -fPIC --shared -o libaa.so -g

g++ main.cpp libbb.so libaa.so liba.a -g


原文地址:https://blog.csdn.net/weixin_38184628/article/details/143078072

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!