Browsed by
分类: C/C++

C/C++ 多动态库标识符冲突

C/C++ 多动态库标识符冲突

场景:
最近在写一个程序的时候,需要调A、B两个服务的客户端动态库,两个客户端动态库中函数有重名(更确切的是重定义)的情况。现象,单写A客户端demo程序运行正常,但集成到程序中死活连不上A服务。


原因:
A、B两个客户端动态库中函数有重定义现象。即都有funcX(int arg)(虽然funcX不是对外接口),但执行过程中A动态库调用了B的funcX,导致程序不正常。而单写A的客户端demo时只用A的动态库,A动态库还是用的A中的funcX,程序正常。

简单来说就是违反了单一定义原则One Definition Rule)。

简单示例说明

fun1.h

#include <stdio.h>
int print1();

fun1.cpp

#include "fun1.h"
int printfun()
{
    printf("1\n");
}
int print1()
{
    printfun();
    return 0;
}

fun2.h

#include <stdio.h>
int print2();

fun2.cpp

#include "fun2.h"
int printfun()
{
    printf("2\n");
}
int print2()
{
    printfun();
    return 0;
}

测试程序test.cpp

#include "fun1.h"
#include "fun2.h"

int main()
{
    print1();
    print2();
    return 0;
}

Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

可以看到我们本意是希望一个打印1一个打印2,结果全部打印的1

解决方案1(-Wl,-Bsymbolic )(不推荐)

创建动态链接库时,添加编译选项-Wl,-Bsymbolic,其中Wl表示将紧跟其后的参数,传递给连接器ld。首先看一下-Bsymbolic,    ld的man手册(意思就是优先使用链接库内的定义实现)

-Bsymbolic
When creating a shared library, bind references to global symbols to the definition within the shared library, if any.  Normally, it is possible for a program linked against a shared library to override the definition within the shared library.  This option can also be used with the –export-dynamic option, when creating a position independent executable, to bind references to global symbols to the definition within the executable.  This option is only meaningful on ELF platforms which support shared libraries and position independent executables.

修改Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so -Wl,-Bsymbolic
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so -Wl,-Bsymbolic
clean:
    rm  -rf libfun1.so libfun2.so test

方案一:执行结果


解决方案2( -fvisibility=hidden )

创建动态库的时,添加编译选项-fvisibility=hidden
首先看一下  -fvisibility=default|internal|hidden|protected,gcc的man手册.

-fvisibility=default|internal|hidden|protected

Set the default ELF image symbol visibility to the specified option—all symbols are marked with this unless overridden within the code. Using this feature can very substantially improve linking and load times of shared object libraries, produce more optimized code, provide near-perfect API export and prevent symbol clashes(防止标志符冲突).  It is strongly recommended that you use this in any shared objects you distribute(强烈建议使用在你写的共享库).

修改头文件
fun1.h

#include <stdio.h>
__attribute__ ((visibility("default"))) int print1();

fun2.h

#include <stdio.h>
__attribute__ ((visibility("default"))) int print2();

修改Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so -fvisibility=hidden
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so -fvisibility=hidden
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

解决方案3( 命名空间 )

严格使用命名空间。

fun1.h

#include <stdio.h>
namespace fun1 {
    
int print1();

}

fun1.cpp

#include "fun1.h"
namespace fun1 {
int printfun()
{
    printf("1\n");
}
int print1()
{
    printfun();
    return 0;
}
}

fun2.h

#include <stdio.h>
namespace fun2 {
    
int print2();

}

fun2.cpp

#include "fun2.h"
namespace fun2 {
int printfun()
{
    printf("2\n");
}
int print2()
{
    printfun();
    return 0;
}
}

test.cpp

#include "fun1.h"
#include "fun2.h"

int main()
{
    fun1::print1();
    fun2::print2();
    return 0;
}

Makefile

test: libfun1.so libfun2.so
    g++ test.cpp -o test -L ./ -lfun1 -lfun2
libfun1.so:
    g++ fun1.cpp -shared -fpic -o libfun1.so
libfun2.so:
    g++ fun2.cpp -shared -fpic -o libfun2.so
clean:
    rm  -rf libfun1.so libfun2.so test

执行结果

范例(leveldb)

项目中CMakeLists.txt片段,当编成动态库时添加编译选项-fvisibility=hidden

if(BUILD_SHARED_LIBS)
  # Only export LEVELDB_EXPORT symbols from the shared library.
  add_compile_options(-fvisibility=hidden)
endif(BUILD_SHARED_LIBS)

头文件export.h

// Copyright (c) 2017 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#ifndef STORAGE_LEVELDB_INCLUDE_EXPORT_H_
#define STORAGE_LEVELDB_INCLUDE_EXPORT_H_

#if !defined(LEVELDB_EXPORT)

#if defined(LEVELDB_SHARED_LIBRARY)
#if defined(_WIN32)
//Windows 系列
#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __declspec(dllexport)
#else
#define LEVELDB_EXPORT __declspec(dllimport)
#endif  // defined(LEVELDB_COMPILE_LIBRARY)

#else  // defined(_WIN32)
//Linux gcc系列
#if defined(LEVELDB_COMPILE_LIBRARY)
#define LEVELDB_EXPORT __attribute__((visibility("default")))
#else
#define LEVELDB_EXPORT
#endif
#endif  // defined(_WIN32)

#else  // defined(LEVELDB_SHARED_LIBRARY)
#define LEVELDB_EXPORT
#endif

#endif  // !defined(LEVELDB_EXPORT)

#endif  // STORAGE_LEVELDB_INCLUDE_EXPORT_H_

头文件db.h片段

...
namespace leveldb {
...
class LEVELDB_EXPORT DB {
...
};
...
}

总结

不推荐使用第一种做法的原因:你是首先用自己定义的标志符,但别人不一定这么做了,有可能其他的动态库没有添加优先使用本动态库标志符的选项。

自己提供动态库时,规范做法应该如leveldb项目,严格使用命名空间,并且使用-fvisibility=hidden,只导出要导出的定义,其他的隐藏。

linux 定位段错误的一个方法

linux 定位段错误的一个方法

背景:程序出现段错误,没有core文件产生,日志没来得及写堆栈信息。

步骤1:使用dmesg查看系统运行信息。

cstaspee[10259]: segfault at 4 ip 00007fdb92acd1df sp 00007fd9c5e65d20 error 6 in libcsta_scm.so[7fdb92aba000+1c000]

发现在cstaspee在调用libcsta_scm.so动态库出现段错误。

步骤2:计算错误点在动态库的地址

7fdb92aba000为模块(libcsta_scm.so)载入地址,
00007fdb92acd1df为程序崩溃点ip指令地址,
相减   00007fdb92acd1df - 7fdb92aba000 = 131DF,
差值为错误点在动态库的地址。

步骤3:使用objdump命令反汇编 或 addr2line

objdump -d libcsta_scm.so > /tmp/xxx.txt

xxx.txt部分文件内容

00000000000130df <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii>:
   130df:    55                       push   %rbp
   130e0:    48 89 e5                 mov    %rsp,%rbp
   130e3:    48 83 ec 30              sub    $0x30,%rsp
   130e7:    48 89 7d e8              mov    %rdi,-0x18(%rbp)
   130eb:    89 75 e4                 mov    %esi,-0x1c(%rbp)
   130ee:    89 55 e0                 mov    %edx,-0x20(%rbp)
   130f1:    89 4d dc                 mov    %ecx,-0x24(%rbp)
   130f4:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%rbp)
   130fb:    e9 16 02 00 00           jmpq   13316 <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0x237>
   13100:    48 8b 0d d1 8e 20 00     mov    0x208ed1(%rip),%rcx        # 21bfd8 <[email protected]@Base-0x428>
   13107:    8b 45 fc                 mov    -0x4(%rbp),%eax
   1310a:    48 63 d0                 movslq %eax,%rdx
   1310d:    8b 45 e4                 mov    -0x1c(%rbp),%eax
   13110:    48 63 f0                 movslq %eax,%rsi
   13113:    48 89 d0                 mov    %rdx,%rax
   13116:    48 c1 e0 03              shl    $0x3,%rax
   1311a:    48 01 d0                 add    %rdx,%rax
   1311d:    48 c1 e0 06              shl    $0x6,%rax
   13121:    48 69 d6 d8 39 28 00     imul   $0x2839d8,%rsi,%rdx
   13128:    48 01 d0                 add    %rdx,%rax
   1312b:    48 01 c8                 add    %rcx,%rax
   1312e:    48 05 c0 09 04 00        add    $0x409c0,%rax
   13134:    8b 40 04                 mov    0x4(%rax),%eax
   13137:    83 f8 01                 cmp    $0x1,%eax
   1313a:    0f 85 8e 00 00 00        jne    131ce <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0xef>
   13140:    8b 45 fc                 mov    -0x4(%rbp),%eax
   13143:    48 63 d0                 movslq %eax,%rdx
   13146:    48 89 d0                 mov    %rdx,%rax
   13149:    48 c1 e0 03              shl    $0x3,%rax
   1314d:    48 01 d0                 add    %rdx,%rax
   13150:    48 c1 e0 06              shl    $0x6,%rax
   13154:    8b 55 e4                 mov    -0x1c(%rbp),%edx
   13157:    48 63 d2                 movslq %edx,%rdx
   1315a:    48 69 d2 d8 39 28 00     imul   $0x2839d8,%rdx,%rdx
   13161:    48 01 d0                 add    %rdx,%rax
   13164:    48 8d 90 c0 09 04 00     lea    0x409c0(%rax),%rdx
   1316b:    48 8b 05 66 8e 20 00     mov    0x208e66(%rip),%rax        # 21bfd8 <[email protected]@Base-0x428>
   13172:    48 01 d0                 add    %rdx,%rax
   13175:    48 83 c0 0c              add    $0xc,%rax
   13179:    0f b6 00                 movzbl (%rax),%eax
   1317c:    84 c0                    test   %al,%al
   1317e:    74 4e                    je     131ce <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0xef>
   13180:    48 8b 0d 51 8e 20 00     mov    0x208e51(%rip),%rcx        # 21bfd8 <[email protected]@Base-0x428>
   13187:    8b 45 fc                 mov    -0x4(%rbp),%eax
   1318a:    48 63 d0                 movslq %eax,%rdx
   1318d:    8b 45 e4                 mov    -0x1c(%rbp),%eax
   13190:    48 63 f0                 movslq %eax,%rsi
   13193:    48 89 d0                 mov    %rdx,%rax
   13196:    48 c1 e0 03              shl    $0x3,%rax
   1319a:    48 01 d0                 add    %rdx,%rax
   1319d:    48 c1 e0 06              shl    $0x6,%rax
   131a1:    48 69 d6 d8 39 28 00     imul   $0x2839d8,%rsi,%rdx
   131a8:    48 01 d0                 add    %rdx,%rax
   131ab:    48 01 c8                 add    %rcx,%rax
   131ae:    48 05 00 0a 04 00        add    $0x40a00,%rax
   131b4:    0f b7 40 0c              movzwl 0xc(%rax),%eax
   131b8:    0f b7 c0                 movzwl %ax,%eax
   131bb:    89 c7                    mov    %eax,%edi
   131bd:    e8 8e 0d ff ff           callq  3f50 <[email protected]>
   131c2:    83 f8 01                 cmp    $0x1,%eax
   131c5:    75 07                    jne    131ce <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0xef>
   131c7:    b8 01 00 00 00           mov    $0x1,%eax
   131cc:    eb 05                    jmp    131d3 <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0xf4>
   131ce:    b8 00 00 00 00           mov    $0x0,%eax
   131d3:    84 c0                    test   %al,%al
   131d5:    0f 84 37 01 00 00        je     13312 <_Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii+0x233>
   131db:    48 8b 45 e8              mov    -0x18(%rbp),%rax
   131df:    c7 40 04 01 00 00 00     movl   $0x1,0x4(%rax)
   131e6:    48 8b 45 e8              mov    -0x18(%rbp),%rax
   131ea:    c7 80 08 01 00 00 00     movl   $0x0,0x108(%rax)

在xxx.txt文件中查找步骤2的地址131DF,
可以知道问题出在csta_scm_baseServiceEventSetup
(echo _Z30csta_scm_baseServiceEventSetupP13STRUCT_CM_MSGiii |   c++filt     )

或者直接使用addr2line命令查看错误出在哪个文件哪一行。

addr2line -e libcsta_scm.so 131df

总结

dmesg 查看崩溃的信息,计算崩溃地址。

objdump或addr2line 找出断点所在的文件及位置。

sprintf 段错误

sprintf 段错误

最近迁移代码测试功能的时候发现一个段错误,打印堆栈显示std::string析构时段错误。最后分析原因:sprintf造成字符数组内存溢出,踩内存,导致附近std::string对象被改修,析构时产生段错误。

原代码

if(response_code == 200)
{
	sprintf(temp,"HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);
	
	return TRUE;
}

strResponce为http的响应结果,平时的都是本公司的soap服务,返回
字节数都很少,测试时的服务返回长度超过temp长度,溢出,踩内存。

修改后

if(response_code == 200)
{
	//sprintf易造成越界,导致段错误
	snprintf(temp, sizeof(temp), "HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);

	return TRUE;
}

慎用sprintf,尽量使用snprintf。
原来工作中就遇到过sprintf造成的段错误。

printf/sprintf/NULL 空指针问题

printf/sprintf/NULL 空指针问题

最近从windows到linux 迁移代码,遇到了一个sprintf参数空指针段错误。

很疑惑,按说项目这么久不该出现段错误。

1.代码

原代码(用到msxml)

sprintf(pItemData.szNodeText,"%s",(const char*)nodeptr->Gettext());

移植后的代码(用到libxml2)

xmlCharTemp = xmlNodeGetContent(xmlChildNode);

sprintf(pItemData.szNodeText,"%s", (const char*)xmlCharTemp);

xmlFree(xmlCharTemp);

用什么xml解析库并不关键,关键是sprintf的参数都有可能是空指针。

于是乎实验了一下,发现vc++6.0 和 g++编译器存在差异性。

 

 

Windows-vc++6.0-sprintf截图

 

 

Linux-g++sprintf-截图

2.G++为什么会出现这种情况?

输出汇编代码

g++ test.cpp -S -o test.S

 .file "test.cpp"
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq $0, -8(%rbp)
leaq -32(%rbp), %rax
movl $0, %edx
movl $.LC0, %esi
movq %rax, %rdi
movl $0, %eax
call sprintf
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movq -8(%rbp), %rdx
leaq -32(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call strcpy
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits


会发现两次调用sprintf,第一次调用的call sprintf,第二次调用的call strcpy。

3.printf也会存在类似的段错误

Windows-vc++6.0-printf截图

 

Linux-g++-printf-截图

 

g++汇编代码

.file "test2.cpp"
.section .rodata
.LC0:
.string "%s"
.LC1:
.string "%s \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movq $0, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits

会发现前两次调用的call printf,而第三次调用call puts。

 

4.总结

尽量不要期待编译器去做空指针的判断优化,因为大多数情况下你不知道编译器是怎么实现的,况且编译器间存在差距,自己保证参数的不为空指针。

 

最后代码无奈改成了这个样子。

sprintf(pItemData.szNodeText,"%s", xmlCharTemp!=NULL?(const char*)xmlCharTemp:"(null)");

参考:https://stackoverflow.com/questions/11589342/what-is-the-behavior-of-printing-null-with-printfs-s-specifier

Hash 散列函数

Hash 散列函数

今天读leveldb代码,看到murmur散列方法,突然想起以前公司关于hash的故事。所在产品线的一套代码十几年,一直在维护,新需求来了就是在上面增加模块,不带第三方库毫不夸张几十万行,记得某个文件没重构之前3w行。经手的人一多,加上产品线平台基础库推广力度不够,就是各种造轮子。



其中用的最多的哈希表 初始化是这个样子的,(当然hash表的实现也很多) :

//** bucket_cnt     :桶的个数
//** hash_key_func_t:哈希函数指针
//** hash_compare_t :键值比较函数指针
//** hash_destroy_func_t:释放节点中data数据的函数指针
//** hash_nodesize_cnt_t:哈希节点占用内存大小计算函数指针
//** bucket_depth	:桶的深度,设置为0表示不做限制
//**************************************************************************
hash_t *hash_init (	int	bucket_cnt, 
						hash_key_func_t     key_func, 
						hash_compare_t      compare_func,
						hash_destroy_func_t destroy_node_func,
						hash_nodesize_cnt_t nodesize_cnt_func = NULL,
						int	bucket_depth = 0);



感觉接口设计挺好的,但,hash函数需要自己实现,导致系统种各种散列函数的存在,加乘异或移位,其中当然有好的实现,但是那么一两个我竟然看到了&,为什么没出事我也不太清楚,可能是数据不多。



某一天,系统(DPDK收包然后处理)出现丢包现象,最后的定位问题是哈希表散列性不好,导致某些桶特别深,查询出现热点,相当于在一个相当长的list上遍历比较,一直返回不了,数据量一大就出现丢包,最后修改散列函数后改善。此事之后凡是用到哈希表,单元测试都要求对其随机分布性做测试。

Leveldb使用的murmur hash变种,redis也有用到murmur hash。

MurmurHash 是一种非加密哈希函数,适用于一般的哈希检索操作。由Austin Appleby在2008年发明,并出现了多个变种,都已经发布到了公有领域(public domain)。与其它流行的哈希函数相比,对于规律性较强的key,MurmurHash的随机分布特征表现更良好。–wiki

在这里贴一下leveldb的源码:

#ifndef FALLTHROUGH_INTENDED
#define FALLTHROUGH_INTENDED do { } while (0)
#endif

namespace leveldb {

uint32_t Hash(const char* data, size_t n, uint32_t seed) {
  // Similar to murmur hash
  const uint32_t m = 0xc6a4a793;
  const uint32_t r = 24;
  const char* limit = data + n;
  uint32_t h = seed ^ (n * m);

  // Pick up four bytes at a time
  while (data + 4 <= limit) {
    uint32_t w = DecodeFixed32(data);
    data += 4;
    h += w;
    h *= m;
    h ^= (h >> 16);
  }

  // Pick up remaining bytes
  switch (limit - data) {
    case 3:
      h += static_cast<unsigned char>(data[2]) << 16;
      FALLTHROUGH_INTENDED;
    case 2:
      h += static_cast<unsigned char>(data[1]) << 8;
      FALLTHROUGH_INTENDED;
    case 1:
      h += static_cast<unsigned char>(data[0]);
      h *= m;
      h ^= (h >> r);
      break;
  }
  return h;
}


}  // namespace leveldb

C 结构体初始化

C 结构体初始化

1.前言

今天阅读linux kernel oom代码,看到一种结构体初始化方式,原来没见过,记录一下。

//结构体定义
struct oom_control {
	/* Used to determine cpuset */
	struct zonelist *zonelist;

	/* Used to determine mempolicy */
	nodemask_t *nodemask;

	/* Memory cgroup in which oom is invoked, or NULL for global oom */
	struct mem_cgroup *memcg;

	/* Used to determine cpuset and node locality requirement */
	const gfp_t gfp_mask;

	/*
	 * order == -1 means the oom kill is required by sysrq, otherwise only
	 * for display purposes.
	 */
	const int order;

	/* Used by oom implementation, do not set */
	unsigned long totalpages;
	struct task_struct *chosen;
	unsigned long chosen_points;
};
//初始化方式
struct oom_control oc = {
	.zonelist = NULL,
	.nodemask = NULL,
	.memcg = NULL,
	.gfp_mask = 0,
	.order = 0,
};

2.struct四种初始化的方式

初始化方式1: 定义时赋值(不可乱序,不可缺省)
初始化方式2: 定义后赋值
初始化方式3: 定义时赋值(可乱序 可缺省. 点号)
初始化方式4: 定义时赋值(可乱序 可缺省 : 冒号)

//测试代码
#include <stdlib.h>
#include <stdio.h>

struct test_struct {

	int    a;
	char*   p;
};

int main()
{
	struct test_struct imp;
	imp.a = 100;
	imp.p = "hello world100";

	struct test_struct imp0 = {
		.a = 0,
		.p = "hello world0"
		};
	struct test_struct imp1 = {
		a : 1,
		p : "hello world1"
	};
	struct test_struct imp2 = {2, "hello world2"};

	printf("imp  a:%d  p: %s\n", imp.a, imp.p);
	printf("imp0 a:%d  p: %s\n", imp0.a, imp0.p);
	printf("imp1 a:%d  p: %s\n", imp1.a, imp1.p);
	printf("imp2 a:%d  p: %s\n", imp2.a, imp2.p);
	
	return 0;
}

3.总结

相比顺序初始化而言,乱序初始化成员可以不按照顺序初始化,而且可以只初始化部分成员,乱序初始化有两种方式,一种是用点(.)符号,一种是用冒号(:)。

参考:https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html