编译相关问题整理

本文整理一下遇到的编译问题,最常见的就是编译时缺符号,重定义

但是一般来说cmake生成的make语句会隐藏实际的gcc语句,如果手写的makefile,也有可能是目标规则没配置正确

make的debug

  • verbose = 1

    查看实际的gcc语句

  • make -n

    只输出规则不运行

  • make -d

    将打印 Make 为每个目标尝试的所有规则(包括内置规则)

单步编译链接语句debug

拿到gcc语句以后,就可以只运行这一句gcc语句来进行debug了

  • 编译搜索顺序问题

    最常见的是因为头文件include顺序,导致的报错的符号和预期的不一致:

    此时gcc -v -E拼接实际的gcc语句输出搜索的头文件路径,-v输出详细信息,-E是预处理截断

  • 链接缺符号/重定义

    nm libA.a或者reaelf -s libA.a看缺什么符号或者多了什么符号,对应的预期的库有没有这些符号

    • 缺符号的报错
      • 如果预期的库有这些符号,那就是链接搜索顺序问题
      • 如果没有这个符号,那就是有bug
    • 重定义的报错
      • 如果预期的库没有这些符号,那就是链接搜索顺序问题
      • 如果有这些符号,那就是有bug
  • 链接搜索顺序问题

    最常见的是因为库文件路径的搜索顺序有问题,导致引入了非预期的库文件:

    gcc -Wl,--verbose拼接实际的gcc语句,输出搜索库文件路径的顺序

执行阶段动态库错误debug

由于动态链接阶段的GOT表覆盖规则,因此不会发生重定义的问题

  • 动态链接缺符号

    ldd -r看缺什么库和符号,对应的预期的库有没有这些符号

    • 如果有的话,那就是链接搜索顺序问题
    • 如果没有的话,那就是预期的库不对
  • 动态链接搜索顺序问题

​ 执行时库文件路径的搜索顺序有问题,导致动态链接器引入了非预期的库文件

​ LD_DEBUG=libs ./xxx输出搜索动态库文件路径的顺序,看看是否和预期一致

其他编译系统

bazel

  • 得到单条gcc语句bazel -s

    例如bazel --output_user_root=/tmp/cache build :libtest.so -s

    这时输出.o文件是怎么编译的,这里面可能会缺一些头文件,需要自己-I一下

    也有如何链接的,但是链接语句是/usr/bin/gcc @bazel-out/k8-fastbuild/bin/libtest.so-2.params的形式,cat出来就可以看到了,但是同样的,有可能会缺一些库文件,需要自己改下libtest.so-2.params的语句

实战

在mysql静态库编译报错,提示重定义

背景

3.4.5.9开始taf框架引入ssl依赖,为了20.04能直接使用16.04的编译结果,我把ssl静态编译入taf框架

解析

libcrypto.a(sha256.o)和libmysqlclient.a(my_sha2.cc.o)的SHA224重定义

这是因为mysql库和ssl库本来就有冲突

  • mysqlclient

    1
    2
    3
    4
    5
    6
    7
    8
    $ dpkg -S /usr/lib/x86_64-linux-gnu/libmysqlclient.a
    libmysqlclient-dev: /usr/lib/x86_64-linux-gnu/libmysqlclient.a

    $ apt-cache madison libmysqlclient-dev
    libmysqlclient-dev | 5.7.33-0ubuntu0.16.04.1 | http://mirrors.aliyun.com/ubuntu xenial-security/main amd64 Packages

    $ nm /usr/lib/x86_64-linux-gnu/libmysqlclient.a |grep SHA224
    00000000000001e0 T SHA224

  • ssl

    静态库

    1
    2
    3
    4
    5
    6
    7
    8
    $ dpkg -S /usr/lib/x86_64-linux-gnu/libcrypto.a
    libssl-dev:amd64: /usr/lib/x86_64-linux-gnu/libcrypto.a

    $ apt-cache madison libssl-dev
    libssl-dev | 1.0.2g-1ubuntu4.20 | http://mirrors.aliyun.com/ubuntu xenial-security/main amd64 Packages

    $ nm /usr/lib/x86_64-linux-gnu/libcrypto.a|grep SHA224
    0000000000000510 T SHA22

    动态库:

    1
    2
    3
    4
    5
    6
    7
    8
    $ dpkg -S /lib/x86_64-linux-gnu/libcrypto.so.1.0.0
    libssl1.0.0:amd64: /lib/x86_64-linux-gnu/libcrypto.so.1.0.0

    $ apt-cache madison libssl1.0.0
    libssl1.0.0 | 1.0.2g-1ubuntu4.20 | http://mirrors.aliyun.com/ubuntu xenial-security/main amd64 Packages

    $ nm -D /lib/x86_64-linux-gnu/libcrypto.so.1.0.0|grep SHA224
    000000000006b230 T SHA224

如上,5.7.33的mysqlclient和1.0.2g的ssl(不管是动态库还是静态库),都存在SHA224这个符号

都进行静态编译的时候,小部分业务在mysqlclient用到了SHA224符号的时候才会出问题,这也是为什么我没测出来问题

当ssl动态编译的时候,根据got表的覆盖规则,可执行文件的同名符号会覆盖动态链接的符号,由于mysqlclient作为静态库已经被链接到可执行文件里面了,所以会覆盖ssl的符号从而不会发生问题

  • PS:20.04的mysqlclient修复了这个问题
1
2
3
4
$ apt-cache madison libmysqlclient-dev
libmysqlclient-dev | 8.0.35-0ubuntu0.20.04.1 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages
$ nm /usr/lib/x86_64-linux-gnu/libmysqlclient.a |grep SHA224|wc -l
0

小结

20.04的mysqlclient不再有兼容性问题,此时ssl可以静态编译,但是不可能为20.04单独维护一个ssl静态编译的版本

因此ssl必须动态编译,每个ubuntu版本需要单独编译一次taf框架

在libkafka静态库链接报错,提示缺符号

背景

3.4.5.9引入ssl依赖的时候,我使用submodle的方式clone了openssl,然后通过口口相传的知识,使用了1.0.0的ssl版本

解析

1
2
3
4
/usr/local/sbin/ld: /home/tafjce/HUYA/KafkaHelper/librdkafka/librdkafka.a(rdkafka_ssl.o): in function 'rd_kafka_transport_ssl_set_endpoint_id':
/root/librdkafka/src/rdkafka_ssl.c:439 undefined reference to 'SSL_get0_param'
/usr/local/sbin/ld: /root/librdkafka/src/rdkafka_ssl.c:441: undefined reference to 'X509_VERIFY_PARAM_set1_host'
collect2: error: ld returned 1 exit status

这是因为librdkafka这个静态库编译的时候,依赖的ssl版本(1.0.2g)和我clone的版本(1.0.0)不一致

librdkafka依赖的ssl符号,在我的版本里面是没有的

经过研究,16.04的开发环境和运行环境统一使用了1.0.2g版本的ssl,我测试checkout到1.0.2g版本的静态库和动态库,都不会出现该报错

所以这里的主要问题还是版本不匹配的问题