mmap函数_分析由 mmap 导致的内存泄漏

背景

一个程序链接 TCMalloc ,同时调用 mmap / munmap 管理一部分较大的内存

通过 TCMalloc 的统计信息,判断内存泄漏不是由 new / malloc 等常规接口导致的

因此怀疑是 mmap 导致的内存泄漏

hook

hook mmap / munmap 记录下每一次调用,可以分析出是哪部分导致的内存泄漏

如何存储调用信息?

这涉及到三个问题的回答:

  1. buffer 是 thread local 还是 global ?
  2. 如何处理 buffer 满的情况?
  3. 什么时候将 buffer 写出?

thread local / global

thread local 的优势是不需要任何同步手段,劣势是时序关系无法保证

内存的分配与释放未必是同一个线程,如果多线程之间 mmap / munmap 的时序关系没有记录下来,后期很难恢复,也很难知道是哪个线程导致的泄漏

global buffer 的劣势是需要同步手段,同步手段可以选择原子变量(比锁轻)

// 1. 用原子变量抢写入空间
uint64_t index = mEndIndex.fetch_add(2, std::memory_order_relaxed);
mBuffer[index] = GenFirstValue(Type::eMunmap, cycle, p);
// 2. 写入
mBuffer[index + 1] = GenSecondValue(isSucceed, munmapSize);

一旦将写入位置定下来,不同线程的写入并不会发生冲突

fetch_add 注意用最松的 memory order 来保证性能受到最低限度的影响

如何处理 buffer 满的情况?

三种处理手段:不写入、扔掉前面的信息、等待 buffer 刷新

等待 buffer 刷新不可避免地引入 PV 等同步手段(生产者、消费者模型),这会导致性能受到的影响不可控

不写入和扔掉前面的信息本质上是同一种处理手段,在无法判断信息重要性的前提下,两者任意选一种皆可

最终选择扔掉前面的信息,理由如下:

  1. 扔掉前面的信息实现简单
  2. 如果待调查的问题是内存暴涨,那么越新的信息越重要

如果发生信息覆盖,需要留下标记,方便分析(至少可以提示用户)

引入长度为 2 bits 的 cycle 字段,cycle = the lowest 2 bits of (index / buffer size)

*cycle = (index / mBufferSize) & 0x3;

将 cycle 字段写出到 buffer ,当分析程序看到 cycle 变化较快的时候,就知道出现了信息丢弃的情况

什么时候将 buffer 写出?

  1. buffer 满的时候异步写出
  2. buffer 满的时候同步写出
  3. 另起一个线程写出

以 buffer 满作为写出条件会导致一个问题:如何处理 buffer 未满的情况?如果一个程序 mmap / munmap 的次数较少,记录不足以写满 buffer ,那么 buffer 只能在进程结束的时候通过全局变量的析构函数一次性写出。但不是所有的程序都是 gracefully shutdown 的,特别是某些因为内存超限被 OOM Killer 杀掉的程序,这些程序的析构函数未必有机会得到调用。

另外,异步写出与写入 buffer 有竞争关系,可能导致数据混乱

另起一个线程写出有一个比较坑的地方:不要调用 std::thread 或者 pthread_create 来启动一个线程

因为我们的动态链接库是很早加载的(这样才能 hook mmap / munmap),此时 libpthread.so 还没有加载进来,直接调用函数会导致异常

mPThreadLib = dlopen("libpthread.so", RTLD_LAZY | RTLD_LOCAL);
// 启动线程
using FuncType = void* (*)(void*);
using PThreadCreateType =int (*)(pthread_t*, pthread_attr_t*, FuncType, void*);
auto pthreadCreate = reinterpret_cast<PThreadCreateType>(dlsym(mPThreadLib, "pthread_create"));
auto pf = &RingedBuffer::Dump;
pthreadCreate(&mDumpThread, nullptr, *reinterpret_cast<FuncType*>(&pf), this);
// 停止线程
using PThreadJoinType = int (*)(pthread_t, void**);
auto pthreadJoin = reinterpret_cast<PThreadJoinType>(dlsym(mPThreadLib, "pthread_join"));
void* ret = nullptr;
pthreadJoin(mDumpThread, &ret);

全局对象初始化顺序

我们有一个全局变量 RingedBuffer sRingedBuffer 负责记录调用信息,我们能否依赖构造函数将其成员变量初始化?

要注意:mmap / munmap 并不是只有 main 函数才会调用,TCMalloc / pthread 都会调用这两个函数

即使我们的动态链接库先于这两个库加载,也没有办法保证 sRingedBuffer 的构造函数先于 TCMalloc / pthread 的全局变量调用

因此,需要在每一次记录之前都调用一下 Init 函数

void RecordMmap(void* p, int mmapSize, char** funcNames, int funcNamesSize) {Init();// Do other thing.
}

TCMalloc 中也采用了相同的做法:

void* do_memalign(size_t align, size_t size) {if (Static::pageheap() == NULL) ThreadCache::InitModule();}

如何获取调用栈?

  1. libunwind 提供的 backtrace 函数
  2. glibc 指代的 backtrace 函数
  3. 获取 rsp / rbp 手动遍历
  4. __builtin_frame_address

第 3 种和第 4 种方法都会在开优化编译过的程序上面临 coredump 风险,因为栈底指针的压栈不再是必须的

uint64_t* rbp;
asm("mov %%rbp,%0" : "=r"(rbp));
auto ra = *(rbp + 1);

以上代码在遍历深度不为 1 的时候会碰到 coredump 问题

libunwind 能帮我们处理掉这些 tricky 的角落,用 libunwind 是不错的选择

libunwind 的一些函数使用了不可重入锁,并且关了终端,所以不做特殊处理的话,会看到程序无法用 Ctrl-C 杀死,只能用 kill -9 结束

#0  0x00007f7e5119653d in __lll_lock_wait ()
#1  0x00007f7e51191e1b in _L_lock_883 ()
#2  0x00007f7e51191ce8 in pthread_mutex_lock ()
#3  0x00007f7e513a8aca in ?? ()
#4  0x00007f7e513a91f9 in ?? ()
#5  0x00007f7e513ab206 in _ULx86_64_step ()
#6  0x00007f7e513a6576 in backtrace ()
#7  0x00007f7e5182fc9f in mmap (addr=0x0, length=4096, prot=3, flags=34, fd=-1, offset=0)
#8  0x00007f7e513a937d in ?? ()
#9  0x00007f7e513a9c5b in ?? ()
#10 0x00007f7e506d749c in dl_iterate_phdr ()
#11 0x00007f7e513aa23e in ?? ()
#12 0x00007f7e513a7c2d in ?? ()
#13 0x00007f7e513a8d72 in ?? ()
#14 0x00007f7e513a91f9 in ?? ()
#15 0x00007f7e513ab206 in _ULx86_64_step ()
#16 0x00007f7e513a6576 in backtrace ()
#17 0x00007f7e5182fc9f in mmap (addr=0x0, length=4096, prot=3, flags=34, fd=-1, offset=0)
#18 0x00000000004011dd in main ()

可以看到:

  1. libunwind 将 glibc 提供的 backtrace 换成了自己的实现
  2. _ULx86_64_step 会调用 mmap 函数

为了避免死锁,我们要用一个 thread local 变量记录 libunwind 提供的函数是否已经被调用了

// Initializer::Init 负责用 dlopen 和 dlsym 加载 _ULx86_64_init_local 和 _ULx86_64_stepint _ULx86_64_init_local(unw_cursor_t* cursor, unw_context_t* context) {// Prevent sUnwInitLocal is nullptr if static vars of tcmalloc// is initialized before mmap.Initializer::Init();tBacktracing = true;auto r = Initializer::sUnwInitLocal(cursor, context);tBacktracing = false;return r;
}int _ULx86_64_step(unw_cursor_t* cursor) {// Prevent sUnwStep is nullptr if static vars of tcmalloc// is initialized before mmap.Initializer::Init();tBacktracing = true;auto r = Initializer::sUnwStep(cursor);tBacktracing = false;return r;
}

仅仅 hook 这两个函数是不够的,因为 libunwind 提供的 backtrace 函数在编译时可以看见 _ULx86_64_init_local_ULx86_64_step ,不会动态加载这两个函数

所以还需要 hook backtrace 函数

int backtrace(void** returnAddrs, int skipCount, int maxDepth) {void* ip = nullptr;unw_cursor_t cursor;unw_context_t uc;unw_getcontext(&uc);int ret = unw_init_local(&cursor, &uc);assert(ret >= 0);// Do not include current frame.for (int i = 0; i < skipCount + 1; i++) {if (unw_step(&cursor) <= 0) {return 0;}}int n = 0;while (n < maxDepth) {if (unw_get_reg(&cursor, UNW_REG_IP, reinterpret_cast<unw_word_t*>(&ip)) < 0) {break;}returnAddrs[n] = ip;n++;if (unw_step(&cursor) <= 0) {break;}}return n;
}

backtrace 函数的实现可以借鉴 TCMalloc 的 GET_STACK_TRACE_OR_FRAMES 函数

如何将返回地址解释成符号?

这里要做一个选择:原地解释还是事后解释?

一般来说,事后解释优势很明显:性能好

但是,有一些程序会反复调用 dlopen 和 dlclose ,这个时候事后解释就会面临信息不全的问题

补充一个冷知识:如果不考虑 dlopen 和 dlclose ,每一次进程启动,库加载到虚拟内存的位置是固定的

再补充一个冷知识:addr2line 2.27 有 bug ,解释结果可能和 gdb 不一致

所以这个版本用了原地解释的方案

void* returnAddrs[10];
int n = backtrace(reinterpret_cast<void**>(&returnAddrs), 1, 10);
char** funcNames = backtrace_symbols(returnAddrs, n);
// This array is malloced by backtrace_symbols(), and must be freed by the caller. (The strings pointed to by the array of pointers need not and should not be freed.)
free(funcNames);

boost 用了一种更加折中的方案:开一个子进程来解释(这在理论上也会有 gap )

事后解释具有实现的可能性:RTLD-AUDIT 能够审计动态链接库的加载与卸载,这会放在下一篇文章讲

性能分析

单线程下的火焰图(编译时未开优化)

bfc4e719dfbc25775e6523f37946ab5b.png

RecordMmap 在单线程下的表项并不算优异,经过分析,主要是字符串拷贝等操作消耗了很多时间

f9298cd1550544b67889f9f02a8bc9ee.png

每个线程分别调用 10000 次 mmap 和 munmap ,可以看到:

  1. hook 后 mmap / munmap 的耗时大概是 hook 前的 35 倍
  2. hook 后变慢程度并没有随着线程的增长而增长
g++ -std=c++11 mmap.cpp ringed_buffer.cpp -ltcmalloc -lunwind -lpthread -ldl -O3 -ggdb -shared -fPIC -o libmmap_analyser.so
g++ -std=c++11 test.cpp -lpthread -ltcmalloc -lunwind -O3 -ggdb -o test
time ./test
time env LD_PRELOAD="libmmap_analyser.so" test

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何一站式快速构建企业全场景数据库管理平台?

简介&#xff1a; Gartner 的报告显示预计到2022年将有75%数据库将采用云数据库&#xff0c;与此同时&#xff0c;IDC预计到2024年传统部署数据库市场将达到13亿美元&#xff0c;企业数字化转型升级&#xff0c;积极拥抱开源、云原生数据库成为重要趋势&#xff0c;也是必然选择…

编程能力差,学不好Python、AI、Java等技术,90%是输在了这点上!

据了解&#xff0c;超90%的人在学习Python、Java、AI等技术时&#xff0c;都是在网上随便找个入门的教程就开始学起来。然而多数人在看了不少教程后&#xff0c;还是很难独立完成项目&#xff0c;甚至反思自己为什么学了这么久编程能力还是这么差&#xff01;因为你在刚刚开始学…

更丰富的云原生应用治理能力让业务快速生长

简介&#xff1a; 据 Gartner 的报告显示&#xff0c;到 2022 年将有 50% 的应用软件将容器化&#xff0c;2023 年高可控应用 PaaS 的市场规模将达到 332 亿元&#xff0c;年复合增长率约为 18.7%。企业数字化转型的道路上&#xff0c;积极拥抱云原生&#xff0c;加速业务系统容…

“建木”萌芽,聚木成林

据 Github 2021 年度报告显示&#xff0c;目前 Github 用户数已超 7300 万&#xff0c;中国 Github 开发者 755万&#xff0c;开源吞噬世界的当下&#xff0c;越来越多中国开发者和企业积极参与开源建设。 有一位从事开源 10 多年的从业人员&#xff0c;戏称自己为未来希望成为…

还在为多集群管理烦恼吗?RedHat 和蚂蚁、阿里云给开源社区带来了OCM

简介&#xff1a; 为了让开发者、用户在多集群和混合环境下也能像在单个 Kubernetes 集群平台上一样&#xff0c;使用自己熟悉的开源项目和产品轻松开发功能&#xff0c;RedHat 和蚂蚁、阿里云共同发起并开源了 OCM&#xff08;Open Cluster Management&#xff0c;项目官网 &a…

lol模型导入ue4_Houdini amp; UE4 程序化建模——石头(一)基础工作流

导言最近程序化建模的风气在国内兴起&#xff0c;建立好程序化建模流程&#xff0c;通过调参就可以生成丰富的美术资源。可以程序化建模的内容有很多&#xff0c;国外的程序化曼哈顿、林中小屋等都是很好的例子&#xff0c;所有带有规律的模型都可以程序化来做。程序化可以做从…

iOS 端容器之 WKWebView 那些事

简介&#xff1a; 本文主要是关于在端容器设计开发过程中&#xff0c;WKWebView 使用上遇到的一些问题和解决办法。 一 背景 熟悉 iOS\macOS Hybrid 混合开发的同学应该都有体会&#xff0c;WKWebView 虽然是苹果作为替代 UIWebView\WebView 而推出的"新"组件&…

95后热搜哪些事,夸克用AI引擎发布2021年度关键词

编辑 | 宋慧 出品 | CSDN 云计算 岁末将至&#xff0c;哪些心潮澎湃的瞬间与难以忘怀的言语&#xff0c;能够代表你的2021&#xff1f; 12月14日&#xff0c;在夸克Meet AI开放日上&#xff0c;阿里巴巴智能信息事业群旗下的智能搜索APP夸克与中国传媒大学互联网信息研究院联合…

MaxCompute Spark 资源使用优化祥解

简介&#xff1a; 本文主要讲解MaxCompute Spark资源调优&#xff0c;目的在于在保证Spark任务正常运行的前提下&#xff0c;指导用户更好地对Spark作业资源使用进行优化&#xff0c;极大化利用资源&#xff0c;降低成本。 本文作者&#xff1a;吴数傑 阿里云智能 开发工程师 …

tika设置文件长度限制_MySQLInnoDB某些你没注意过的限制

01—目录02—前言最近一个朋友遇到一个面试题&#xff1a;MySQL的InnoDB的一个表最多能存多少数据&#xff1f;这么一问&#xff0c;我瞬间也蒙圈了&#xff0c;这是我的知识盲区啊&#xff0c;之前还从来没考虑过这样的问题。那我怎么能甘心的&#xff0c;只好去网上一顿查资料…

如何保证 Serverless 业务部署更新的一致性?

简介&#xff1a; 代码在其他场景被更新&#xff0c;需要我们在当前得到感知&#xff0c;这个事情其实是非常重要的&#xff0c;和代码的安全发布密不可少。而此时&#xff0c;通过 Serverless Devs 是可以做到的。 作者&#xff5c;Anycodes 从我做 Serverless 工具开始&…

Gartner最新报告:阿里云计算、存储、网络、安全均获得最高分

12月15日&#xff0c;国际权威机构Gartner发布最新报告&#xff0c;全面评估全球顶级云厂商整体能力。阿里云IaaS基础设施能力拿下全球第一&#xff0c;在计算、存储、网络、安全四项核心评比中均斩获最高分&#xff0c;这也是中国云首次超越亚马逊、微软、谷歌等国际厂商。 《…

软件工程软件产品质量要求与评价_软件质量保证(Quality Assurance)中常见的活动...

质量保证QA关注在软件产品生成的整个过程&#xff0c;主要验证软件产品开发过程中相关实施过程的完整性、一致性和有效性&#xff0c;确保开发活动和测试活动等遵循正确的过程&#xff0c;为软件产品达到合适的质量级别提供信心。为了实现过程的可重用性和持续改进&#xff0c;…

普诺飞思公布发明者社区,启发基于事件视觉技术的创新

2021 年 12 月 15 日&#xff0c;全球领先的神经拟态视觉传感公司普诺飞思&#xff08;Prophesee&#xff09;正式对外公开其发明者社区&#xff0c;展示基于事件的 Metavision 技术的工作及技术创新成果。该发明者社区创建于 2014 年&#xff0c;由来自各行业的研究人员、学者…

揭秘!业界创新的代码仓库加密技术

简介&#xff1a; 原理与演示。 01 / 什么是代码加密&#xff1f; 云端加密代码服务是云效团队的自研产品&#xff0c;是目前国内率先支持代码加密的托管服务&#xff0c;也是目前世界范围内率先基于原生Git实现加密方案的代码托管服务。 通过在云端对托管在云效Codeup的代码…

360 政企安全集团基于 Flink 的 PB 级数据即席查询实践

简介&#xff1a; Threat Hunting 平台的架构与设计&#xff0c;及以降低 IO 为目标的优化与探索。为什么以及如何使用块索引。 本文整理自 360 政企安全集团的大数据工程师苏军以及刘佳在 Flink Forward Asia 2020 分享的议题《基于 Flink 的 PB 级数据即席查询实践》&#xf…

支持mysql的报表开发工具_你不知道的mysql的3W法,内附超好用的报表工具

WHAT? 什么是MySQL?MySQL是一种关系型数据库管理系统&#xff0c;关系数据库将数据保存在不同的表中&#xff0c;而不是将所有数据放在一个大仓库内&#xff0c;这样就增加了速度并提高了灵活性。WHY&#xff1f;为什么需要MySQL工具&#xff1f;MySQL现已经成为大多数中小企…

中文巨量模型“源1.0”的学习优化方法

最近&#xff0c;浪潮人工智能研究院发布了中文巨量模型“源1.0”&#xff0c;参数量达2457亿&#xff0c;超越美国OpenAI组织研发的GPT-3。“源1.0”在语言智能方面表现优异&#xff0c;获得中文语言理解评测基准CLUE榜单的零样本学习&#xff08;zero-shot&#xff09;和小样…

阿里集团业务驱动的升级 —— 聊一聊Dubbo 3.0 的演进思路

简介&#xff1a; 阿里云在 2020年底提出了“三位一体”理念&#xff0c;目标是希望将“自研技术”、“开源项目”、“商业产品”形成统一的技术体系&#xff0c;令技术的价值可以达到最大化。Dubbo 3.0 作为三位一体架构的首推方案&#xff0c;在集团内被寄予了厚望。它完美融…

淘宝小部件:全新的开放卡片技术!

简介&#xff1a; 淘宝的开放技术目前主要有两种形态&#xff0c;第一种是小程序&#xff0c;第二种是今天的主角小部件。它是基于小程序技术体系&#xff0c;面向标准化、轻量化、高性能的开放卡片场景。本文我们将通过技术设计策略、核心技术设施、业务场景接入、技术演进路线…