动态库中的单例出现多例问题
1. 单例模式是最常用的一种设计模式。然而如果一个单例类C在动态库A中,而另一个动态库B中也使用C,此时如果同时加载动态库,A,B;则会出现A,B中各有一个C对象。使得单例不再是单例。
解决方法:单例对象不要定义在.h中,定义在.cpp中。 整个程序要有一个主动态库,其他动态库依赖这个主动态库。这种单例就放在这个主动态库中。不要使用函数中的static变量作为单例。如果这个函数在.h中,这个单例会是弱符号,会存在多个。 -rdynamic或者-Wl,--export-dynamic部分解决问题。事实上本来就应该每个动态库里有一个实例. 比如一个Module作为一个动态库。加载时应该获取这个动态库里预设好的全局变量名,然后从中获取已经注册好的符号
so依赖so,怎么不用设置LD_LIBRARY_APTH
-Wl,-rpath=$ORIGIN
在bazel BUILD文件中:linkopts = ["-lpthread","-lgomp", "-lpython2.7", "-Wl,-rpath=$$ORIGIN"],
rpath可以指定加载时动态库搜索路径。一般加个. 和$ORIGIN $ORIGIN就是动态库自己的路径
bazel中 cc_library打包库时,没用到的变量函数会被strip掉
解决方法:
.bazelrc添加 build --nolegacy_whole_archive
cc_library中添加alwayslink = True,
多动态库符号冲突问题
比如Protobuf, 这相当于“动态库中的单例不出现多例问题”中单例被定义在两个动态库,如果这个单例是factory,那么有可能注册相同的东西,会冲突
解决方法: 这两个动态库都依赖protobuf的.h文件。不要把cc文件打包进去。
最终连接成二进制时,或者main函数所在二进制才依赖protobuf的cc文件。
另一种解法可以尝试使用linux 的dlopen时指定 RTLD_LOCAL模式加载
python以RTLD_GLOBAL方式加载扩展so
有时候想写个so, so里有个单例。 这个单例给其他so用。这时只能以RTLD_GLOBAL加载
而python默认import 是RTLD_LOCAL方式
import sys
import ctypes
old_flags = sys.getdlopenflags()
sys.setdlopenflags(old_flags|ctypes.RTLD_GLOBAL)
import xxx #这是要import的so
sys.setdlopenflags(old_flags)
del old_flags
注意,xxx这个so要尽量简单,不要依赖太多东西,否则和python里的符号冲突就完蛋了。
我一般会搞两个so. 把最核心的单例,factory打成so,用GLOBAL方式加载。其他功能代码,都依赖这个核心so, 并以LOCAL方式加载
依赖bazel大项目
如果你有一个小项目A,想依赖另一个复杂的大项目B(比如tensorflow). 最好把B的WORKSPACE文件和.bazelrc文件复制到A来改。 反过来会更复杂。
bazel修改默认gcc
set -e
export CC=/software/usr/local/bin/gcc
export CXX=/software/usr/local/bin/g++
export LD_LIBRARY_PATH=/software/usr/local/lib64/
#bazel build --verbose_failures -s -c dbg trans_acc
bazel run --verbose_failures -s -c dbg cxx20
.bazelrc文件
build --cxxopt="-std=c++20"
build --cxxopt="-g"
BAZEL踩坑
项目不复杂的话在顶层放个BUILD文件就行了,不要在每个目录下放BUILD文件了。
要发布的动态库用cc_library(name=libxxx.so, linkshared=True)来打包。
cc_import 来导入依赖的动态库和.h放在另一个cc_library里。
.bazelrc里注意这两个选项,当编译时找不到文件:
build --spawn_strategy=standalone
build --strategy=Genrule=standalone
.bazelrc里用
ABI问题
依赖动态库,打包库要注意ABI. 默认ABI=1. 如果ABI不同,链接时找不到符号。 undefined reference.
C++动态库之间,动态库和可执行程序之间符号冲突解决
a.so/a.out和b.so中有同名全局符号
a.so/a.out和b.so中有引用了同个库的不同版本
可能在链接时就冲突,可能在运行时冲突。
解决方案1: 隐藏不必要导出的符号
解决Linux多个动态库间的符号冲突问题_hacker_lpy的博客-CSDN博客_动态库符号冲突
1. 手动指定动态库中哪些符号导出
个人觉得这种方式最好,隐藏也很彻底。
-Bsymbolic和-Bsymbolic-functions可以让so内的符号优先使用内部符号,找不到再去全局找。
gcc链接选项加上
--retain-symbols-file指定静态库符号导出控制脚本
--version-script=yyy.map指定动态库符号导出控制脚本
--retain-symbols-file=xxx.map --version-script=yyy.map
{
global:
extern "C++" {
ns::cls::*;
ns::cls::*;
};
local:
*;
};
https://sourceware.org/binutils/docs/ld/VERSION.html
.bazelrc这么写:
build:online --linkopt=-Wl,--version-script=xxx.map
再来一个例子
{
global:
extern "C++" {
tenantflow::*;
vtable*; 导出vtable.
typeinfo*; 导出typeinfo
};
local:
*;
};
c++filt -t _ZTIN10aaaaaaaaaa5bbbbbE
typeinfo for aaaaaaaaaa::bbbbb 所以typeinfo前缀可以导出所有typeinfo
2. 使用gcc 编译选项 -fvisibility=hidden
Code Gen Options - Using the GNU Compiler Collection (GCC)
默认隐藏所有符号。然后要导出的符号自己标注。
struct __attribute__ ((visibility ("default"))) MyExportClass {
std::string a;
int64_t b;
};
__attribute__ ((visibility ("default")))可以放在类名,全局变量函数名前表示要导出
如果库是别人的,我们只有.h,这么隐藏他的符号
void f() { }
#pragma GCC visibility push(default) 开始隐藏下面的符号
void g() { }
void h() { }
#include "other_lib.h"
#pragma GCC visibility pop
pragma GCC visibility push(default)_疲惫小耳朵的博客-CSDN博客_gcc pragma
看看符号有没有隐藏
nm -C -D --defined-only bazel-bin/src/client/libtenant_client.so
解决方案2: 手动加载动态库
dlopen打开动态库。用上RTLD_DEEPBIND选项。
优先使用自己的符号
-Wl,-Bsymbolic,避免链接到别人的so中的符号
静态符号直接库级别隐藏
--exclude-libs lib,lib,lib可以隐藏静态库的符号
-Wl,--exclude-libs,ALL
解决方案3
这一组可以控制符号导出 man ld (1): The GNU linker
--dynamic-list=dynamic-list-file
dynamic-list-file格式
{
A::*;
B::*;
};
终极方法4 自己修改符号属性吧
ELF — LIEF Documentation 这里一个python库。玩转elf.
http://www.skyfree.org/linux/references/ELF_Format.pdf ELF文件格式
动态库找不到时调试
LD_DEBUG=all 打开搜索日志
LD_PRELOAD
当解决了这问题,可能同一份代码,同一个库在不同动态库里都有一份。虽然不冲突,但是用的时候,最好用其中之一吧。有一个全局的东西,如果跨两个库使用可能也会有问题。
内存问题
c++内存泄露,内存越界访问,并发访问数据结构,访问已经析构的对象成员等导致的各种内存问题解决起来十分棘手。
内存涨不一定是泄露:看看这些文章吧
https://blog.csdn.net/u013860464/article/details/122807501
https://blog.csdn.net/u013920085/article/details/52847464
http://t.zoukankan.com/Lifehacker-p-jemalloc_settings.html
https://www.jianshu.com/p/38a4bcf564d5
http://t.zoukankan.com/yorkyang-p-7738231.html
https://blog.csdn.net/MOU_IT/article/details/118460045
https://blog.csdn.net/struggle_chong/article/details/109706672
C++一些 容器,如folly::ConcurrentHashMap在删除元素后不立即释放
工具
一些工具:点评五款用于Linux编程的内存调试器_Linux系统教程_红联Linux门户
简单暴力工具mtrace,在分配和释放内存的时候记录一下:内存泄漏定位工具之 mtrace(一) - 大橙子疯 - 博客园
jemalloc: posix_memalign(3)
jemalloc泄露内存,利用Jemalloc进行内存泄漏的调试 - pokpok - 博客园
#用jemalloc启动服务器a.out.注意,a.out中不能有jemalloc.
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:60000,muzzy_decay_ms:60000,lg_tcache_max:24,prof_leak:true,prof:true,lg_prof_interval:34" LD_PRELOAD=/root/je/lib/libjemalloc.so ./a.out
#启动后会在a.out目录下生成很多jeprof.文件
#查看两个输出之间的memory diff
./je/bin/jeprof --svg worker --base=jeprof.252.31.i31.heap jeprof.252.1449.i1449.heap > jew3.svg
#最后请相信工具。工具中指出问题要分析源码,无论是自己写的,还是三方库里的源码。
#分配内存只能通过brk和mmap来分配。不排除有些库直接使用mmap.
mmap/munmap跟踪
程序中可能有直接使用mmap分配内存的情况,此时用strace命令跟踪mmap/munmap
strace -o /trojan_data/trace.log -p 153 -f -e trace=munmap,mmap -k
grep 'mmap(' trace.log |awk '{print "mmap", $9}' > mmap.txt
grep 'munmap(' trace.log |awk -F '(' '{print $2}'|awk -F ',' '{print "munmap", $1}' > munmap.txt
cat mmap.txt munmap.txt|awk '{print $2}'|sort|uniq -c
次数是奇数的就是泄露的内存。只有mmap没有munmap
strace分析脚本
#!/bin/env python
import re
class Memory:
def __init__(self, pid, tm, name, addr, size, stack):
self.pid, self.tm, self.name, self.addr, self.size, self.stack = pid, tm, name, addr, int(size), stack
def add_stack(self, s):
self.stack.append(s)
def __str__(self):
return ",".join([str(self.pid), self.tm, self.name, self.addr, str(self.size)])
def __repr__(self):
return ",".join([str(self.pid), self.tm, self.name, self.addr, str(self.size)])
def stack(self):
return self.stack
# strace -o trace.log -t -p 153 -f -e trace=munmap,mmap -k
def parse_mmap(L):
m = re.match(r'(\d+) +(\d+:\d+:\d+) +mmap\(.+, (\d+),.+\) += +(.+)', L)
#m = re.match(r'(\d+) +(\d+:\d+:\d+) mmap.*', L)
if m:
return Memory(int(m.group(1)), m.group(2), "mmap", m.group(4), m.group(3), [])
m = re.match(r'(\d+) +(\d+:\d+:\d+) +mmap\(.+, (\d+),.+', L)
if m:
return Memory(int(m.group(1)), m.group(2), "mmap", "", m.group(3), [])
def parse_munmap(L):
m = re.match(r'(\d+) +(\d+:\d+:\d+) +munmap\((.+), +(\d+).+', L)
if m:
return Memory(m.group(1), m.group(2), "munmap", m.group(3), m.group(4), [])
def parse_mmap_resumed(L):
m = re.match(r'(\d+) +(\d+:\d+:\d+).+\) += +(.+)', L)
if m:
return Memory(m.group(1), m.group(2), " mmap_resumed", m.group(3),0, [])
def parse_munmap_resumed(L):
m = re.match(r'(\d+) +(\d+:\d+:\d+).+', L)
if m:
return Memory(m.group(1), m.group(2), " munmap resumed", "", 0, [])
def parse_strace_log(fname):
f2 = open(fname, 'r')
mms = []
cur = None
for line in f2.readlines():
L = line.strip()
if L.find("mmap(") >=0:
if cur:
mms.append(cur)
cur = parse_mmap(L)
elif L.find("munmap(") >= 0:
if cur:
mms.append(cur)
cur = parse_munmap(L)
elif L.find("mmap resumed") >= 0:
if cur:
mms.append(cur)
cur = parse_mmap_resumed(L)
elif L.find("munmap resumed") >= 0:
if cur:
mms.append(cur)
cur = parse_munmap_resumed(L)
else:
if not cur:
print(L)
else:
cur.add_stack(L)
if not cur:
print("xxxx", L)
return mms
def calc_mem_leak(mms):
leak = dict()
unfinished = []
for m in mms:
if m.name == "mmap":
if m.addr in leak:
print("error mmap same addr: ", m, "premap", leak[m.addr])
continue
leak[m.addr] = m
elif m.name == "munmap":
if m.addr in leak:
del leak[m.addr]
else:
unfinished.append(m)
return leak, unfinished
def stack_leak(leak):
stack = dict()
for addr, m in leak.items():
stk = "\n".join(m.stack)
if stk not in stack:
stack[stk] = [[], 0]
stack[stk][0].append(m)
stack[stk][1] += m.size
return stack
def human_size(num):
return str(num)+' B, ' + str(num//1024) + " KB, "+ str(num/1024//1024)+ " MB, " + str(num/1024/1024//1024)+" GB"
if __name__ == "__main__":
mms = parse_strace_log("trace.log")
leak, unfinished = calc_mem_leak(mms)
total_size = 0
for addr, m in leak.items():
total_size += m.size
print("leak", addr, "size", m.size)
print("total track", len(mms), "total leak: ", human_size(total_size), "unfinished num", len(unfinished))
stk = stack_leak(leak)
print(list(stk.items())[0][1][1])
for k, v in sorted(list(stk.items()), key = lambda x : x[1][1], reverse=True):
print "========================="
print "total leak: ", human_size(v[1])
print v[0]
for s in k.split("\n"):
print s
glibc会泄漏内存:export MALLOC_ARENA_MAX =1解决 JVM 调优之 glibc 引发的内存泄露 - 知乎
有时候没办法,只能二分删除代码看看能不能解决。试起来也十分麻烦。
memwatch:https://blog.csdn.net/q297299899/article/details/24253441
借助一些工具吧:valgrind, gcc的asan等。jemalloc也能定位问题jemalloc for Native memory leak tracking!!
bytehound是rust写的工具: Getting started - Memory profiling for fun and profit
dmalloc dmalloc工具的使用_JDI_疾风小象的博客-CSDN博客_dmalloc
dmalloc用法快速入门 - wangkangluo1 - 博客园
memleax https://github.com/WuBingzheng/memleax
pmap diff对比: c - How can I find a memory leak of a running process? - Unix & Linux Stack Exchange
gcc asnan用法
动态检测内存错误利器ASan_瞻邈的博客-CSDN博客_asan内存检测
linkopts = ["-lasan"],
copts = ["-fsanitize=address","-fno-omit-frame-pointer"]
选项:export ASAN_OPTIONS=halt_on_error=0:use_sigaltstack=0:detect_leaks=1:malloc_context_size=15:log_path=./log
sudo yum install libasan
jemalloc 突然释放大量内存造成rt抖动
1. 缓慢异步释放,搞个异步线程慢慢释放
2. 使用mempool, 不要释放内存
可能是TLB shootdown
https://www.jabperf.com/how-to-deter-or-disarm-tlb-shootdowns/
https://juejin.cn/post/6844904084957315086
C++ABI兼容问题
多半是接口中用了STL,比如std::string.
有源码可以统一ABI.:-D_GLIBCXX_USE_CXX11_ABI=0
没有源码得用适配器模式封装。动态库对外接口不能用STL.
C++奇葩符号解析
St3mapIiiSt4lessIiESaISt4pairIKiiEEE 是什么意思?里边的数字是表示后边字符数
C++filt -t xxxx
或者用abi::__cxa_demangle解析出名字
http://www.int0x80.gr/papers/name_mangling.pdf
https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
上述工具都不管用?自己写个内存跟踪?-Wl,--wrap,malloc
技术干货丨通过wrap malloc定位C/C++的内存泄漏问题 - 知乎
多线程,看题目知道有多坑了吧
std::vector<bool>, 不可并发访问。比如vec[0],vec[1],是同一个字节,并发会覆盖的
提交任务到线程池后join,可以把最后一个任务留给当前线程。当前线程不用空等.如果是只有一个任务还不用提交到线程池。batch_size, shard, join得注意一下有没有join助。lambda函数捕获列表保持有效
gdb使用:常用的GDB调试小结_剑神卓不凡的博客-CSDN博客_gdb -ex
用clang+bazel构建C++
C++20 以 Bazel & Clang 开始_u012804784的博客-CSDN博客
cmake等这样设置下就行
export CC=clang
export CXX=clang++
tree .
.
|-- BUILD
|-- WORKSPACE
|-- hello.cc
`-- toolchain
|-- BUILD
`-- cc_toolchain_config.bzl
bazel run --config=clang_config hello
INFO: Analysed target //:hello (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:hello up-to-date:
bazel-bin/hello
INFO: Elapsed time: 0.138s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
The answer is 42.
可执行文件中的符号想导出给so用
LD_DEBUG=symbols ./exe 来看符号查找
默认可以执行文件中的符号是不会导出的。要显示指定。nm -C -D能显示出来 的才能被外部使用
c++ - having object file symbols become dynamic symbols in executable - Stack Overflow
"-Wl,--dynamic-list $(location //:dynlist.lds)"
{
extern "C++" {
a::b::*;
};
a::b::c;
};
C++符号问题总结
符号undefined, 符号冲突是C++程序员经常遇到的问题,尤其在使用了动态库后,问题更多。
符号定义
C++语言中字符串标识符。
符号分类
全局符号: 在动态库或者main执行文件中显示定义的符号。比如定义的函数,类,全局变量等。全局符号只能有一个,同名会冲突。
弱符号:没有显示定义,如函数中的static变量。或者手动指定的__attribute__((weak))的符号。多个动态库的弱符号不会冲突,选择占用空间大的。如果同时有一个动态库里是强符号,就会优先用强符号。 -Bsymbolic和-Bsymbolic-functions 这两个选项可以使得优先用本动态库里的符号。