在程序开发中,内存复制操作(如memcpy)往往是性能瓶颈的关键来源——尤其是大型内存块的复制,可能导致缓存失效、带宽占用过高等问题。为了精准定位这些潜在的性能热点,开发者需要一种能自动识别程序中memcpy调用,并提取其关键信息(如复制大小、所在位置)的工具。本文将解析一款基于LLVM的memcpy静态分析工具,探讨其设计思路、实现原理及相关技术背景。

工具功能概述

这款工具的核心目标是静态扫描LLVM IR(中间表示)或bitcode文件,自动识别其中所有的llvm.memcpy调用(LLVM中内存复制的标准内在函数),并输出以下关键信息:

  • 每次memcpy的复制大小(以字节为单位);
  • 调用所在的函数;
  • 对应的源代码位置(文件名、行号)——依赖调试信息(Debug Info);
  • 支持按复制大小排序(从大到小),并可切换"详细信息"和"摘要"两种输出模式。

简单来说,它就像一个"内存复制探测器",能帮开发者快速找到程序中哪些地方在进行大型内存复制,以及这些操作对应源代码的具体位置。

设计思路:从需求到方案的映射

工具的设计围绕"如何高效定位并提取memcpy关键信息"展开,核心思路可概括为**“层次化遍历+精准筛选+信息聚合”**。

1. 层次化遍历:从文件到指令的解析路径

要分析memcpy调用,首先需要"读懂"LLVM IR文件。LLVM IR是一种结构化的中间表示,其核心层次结构为:模块(Module)→ 函数(Function)→ 基本块(BasicBlock)→ 指令(Instruction)

工具的遍历逻辑正是遵循这一层次:

  • 先加载整个IR文件作为一个"模块"(Module),这是LLVM IR的顶层容器;
  • 遍历模块中的所有函数(Function)——函数是程序行为的基本单元;
  • 每个函数由多个基本块(BasicBlock)组成,遍历每个基本块;
  • 最终遍历基本块中的每一条指令(Instruction),因为memcpy调用本质上是一条函数调用指令。

这种层次化遍历确保不会遗漏任何潜在的memcpy调用,符合LLVM IR的结构化设计特点。

2. 精准筛选:锁定llvm.memcpy调用

在遍历指令的过程中,需要从海量指令中精准识别出memcpy调用。关键筛选逻辑基于两点:

  • 指令类型memcpy是函数调用,因此只关注CallInst(函数调用指令)类型的指令;
  • 被调用函数名:LLVM中内存复制操作统一通过内在函数llvm.memcpy实现(其变体如llvm.memcpy.p0i8.p0i8.i64等也包含"memcpy"标识),因此通过检查被调用函数的名称是否包含"memcpy"即可锁定目标。

3. 信息聚合:提取关键数据

找到memcpy调用后,需要提取三类核心信息:

  • 复制大小llvm.memcpy的函数签名为void @llvm.memcpy.p0i8.p0i8.i64(i8* %dst, i8* %src, i64 %size, i32 %align, i1 %isvolatile),其中第三个参数正是复制大小(%size)。工具通过解析该参数(需确保其为常量,否则无法确定具体值),获取字节数;
  • 所在函数:记录该memcpy调用属于哪个函数,用于定位上下文;
  • 调试信息:通过LLVM的元数据(Metadata)中的"dbg"字段,解析出对应的源代码文件名、行号甚至所属子程序(Subprogram),实现IR指令到源代码的映射。

4. 排序与输出:聚焦关键热点

为了让开发者快速关注最可能影响性能的大型复制操作,工具会按复制大小从大到小排序结果。同时,提供"详细信息"(包含调试信息、所在函数)和"摘要"(仅显示大小)两种输出模式,兼顾深度分析与快速概览的需求。

实现原理:依托LLVM的核心技术

来看看代码实现

#include "llvm/IR/Constants.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Debug.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Support/SourceMgr.h"#include "llvm/Support/raw_ostream.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include <iostream>...void print_all(vector<tuple<CallInst*, uint64_t, Function*>> &memcpys) {for (auto& i : memcpys) {
...cout << "memcpy" << " of " << size << " in " << function->getName().data() << " @ " << std::endl;MDNode* metadata = callInst->getMetadata("dbg");if (!metadata) {cout << "  no debug info" << endl;continue;}DILocation *debugLocation = dyn_cast<DILocation>(metadata);while (debugLocation) {DILocalScope *scope = debugLocation->getScope();cout << "  ";if (scope) {DISubprogram *subprogram = scope->getSubprogram();if (subprogram) {const char* name = subprogram->getName().data();cout << name << " ";}}cout << debugLocation->getFilename().data() << ":" << debugLocation->getLine() << std::endl;debugLocation = debugLocation->getInlinedAt();}}
}void print_summary(vector<tuple<CallInst*, uint64_t, Function*>> &memcpys) {for (auto& i : memcpys) {auto callInst = get<0>(i);auto size = get<1>(i);auto function = get<2>(i);cout << "memcpy" << " of " << size << std::endl;}
}int main(int argc, char **argv) {
...if (argc > 2) {if (std::string(argv[2]) == "summary") {summary = true;}}Expected<std::unique_ptr<Module> > m = parseIRFile(fileName, Err, context);if (!m) {errs() << toString(m.takeError()) << "\n";}vector<tuple<CallInst*, uint64_t, Function*>> memcpys;{auto &functionList = m->get()->getFunctionList();for (auto &function : functionList) {//printf("%s\n", function.getName().data());for (auto &bb : function) {for (auto &instruction : bb) {//printf("  %s\n", instruction.getOpcodeName());CallInst *callInst = dyn_cast<CallInst>(&instruction);if (callInst == nullptr) {continue;}//printf("have call\n");Function *calledFunction = callInst->getCalledFunction();if (calledFunction == nullptr) {//printf("no calledFunction\n");continue;}StringRef cfName = calledFunction->getName();if (cfName.find("llvm.memcpy") != std::string::npos) {auto size_operand = callInst->getOperand(2);auto size_constant = dyn_cast<ConstantInt>(size_operand);if (!size_constant) {//printf("not constant\n");continue;}auto size = size_constant->getValue().getLimitedValue();memcpys.push_back({callInst, size, &function});}}}}}std::sort(memcpys.begin(), memcpys.end(), [](auto& x, auto &y) {if (get<1>(y) == get<1>(x)) {return get<2>(x) > get<2>(y);} else {return get<1>(x) > get<1>(y);}});if (summary) {print_summary(memcpys);} else {print_all(memcpys);}
}

If you need the complete source code, please add the WeChat number (c17865354792)

工具的实现深度依赖LLVM的API和中间表示特性,其核心原理可归纳为三点:

1. LLVM IR的结构化访问

LLVM提供了一套完整的API用于遍历和操作IR结构:

  • 通过parseIRFile加载IR文件并解析为Module对象;
  • 通过Module::getFunctionList()遍历所有函数;
  • 函数的基本块可通过Function::begin()Function::end()遍历;
  • 基本块中的指令可通过BasicBlock::begin()BasicBlock::end()遍历。

这种结构化访问方式让工具能高效遍历程序中的所有指令,无需关心IR的底层语法细节。

2. 内在函数与指令解析

LLVM的内在函数(如llvm.memcpy)是编译器内部用于表达特定操作的特殊函数,其名称和参数列表具有固定规范。工具通过以下步骤解析memcpy指令:

  • dyn_cast<CallInst>判断指令是否为函数调用;
  • CallInst::getCalledFunction()获取被调用函数;
  • 检查函数名是否包含"memcpy",确认是内存复制操作;
  • 提取第三个参数(CallInst::getOperand(2)),并通过dyn_cast<ConstantInt>转换为整数,获取复制大小(仅处理常量大小,动态大小无法在静态分析中确定具体值)。

3. 调试元数据的解析

LLVM IR中通过元数据(Metadata)存储调试信息,遵循DWARF标准(一种广泛使用的调试信息格式)。工具通过以下方式解析调试信息:

  • CallInst::getMetadata("dbg")获取与指令关联的调试元数据;
  • 元数据节点(MDNode)可转换为DILocation对象,包含文件名(getFilename())、行号(getLine())等信息;
  • 对于内联函数调用,通过DILocation::getInlinedAt()追溯原始调用位置,确保调试信息的完整性。

相关领域知识点

这款工具涉及多个编译器与程序分析领域的核心概念,理解这些概念有助于深入把握工具的价值:

1. LLVM与中间表示(IR)

LLVM是一个模块化的编译器框架,其核心是中间表示(IR)。IR是一种与平台无关、与源语言无关的中间代码,既能被编译器优化阶段处理,也能被转换为机器码。由于IR的结构化和规范性,它成为程序静态分析的理想载体——开发者无需针对C、Rust等不同源语言单独开发分析工具,只需处理IR即可。

2. 静态分析技术

静态分析是指在不执行程序的情况下,通过分析代码结构提取信息的技术。这款工具正是静态分析的典型应用:它在编译期(基于IR)识别memcpy调用,无需运行程序即可定位潜在的性能热点。静态分析广泛用于代码检查、性能优化、漏洞检测等场景。

3. memcpy的性能意义

memcpy是C标准库中的内存复制函数,在程序中频繁用于数据块复制(如结构体复制、缓冲区操作等)。大型memcpy(如复制数MB甚至GB级数据)可能成为性能瓶颈:一方面,它会占用大量内存带宽;另一方面,可能导致CPU缓存失效,增加访问延迟。因此,定位并优化大型memcpy是性能调优的重要方向。

4. 调试信息与DWARF

调试信息是连接中间代码/机器码与源代码的桥梁,包含变量名、函数名、文件名、行号等映射关系。DWARF是一种通用的调试信息格式,被LLVM、GCC等主流编译器采用。工具通过解析DWARF格式的元数据,实现了从IR指令到源代码位置的精准映射,让开发者能直接在源代码中找到需要优化的memcpy调用。

总结与应用场景

这款基于LLVM的memcpy分析工具,通过层次化遍历IR、精准筛选指令、聚合关键信息,为开发者提供了定位大型内存复制操作的高效手段。其设计思路紧扣LLVM IR的结构特点,实现原理依托于LLVM的API和调试元数据机制,最终服务于程序性能优化这一核心需求。

在实际开发中,它可用于:

  • 性能瓶颈定位:快速找到大型memcpy调用,评估其对程序性能的影响;
  • 代码优化指导:结合源代码位置,将大型memcpy替换为更高效的实现(如分块复制、利用SIMD指令等);
  • 编译流程分析:辅助理解程序在编译过程中的内存操作转化(如Rust中某些数组操作可能被编译为memcpy)。

总之,这款工具是LLVM生态在程序静态分析领域的一个典型应用,展示了中间表示在连接编译与开发优化中的关键作用。

Welcome to follow WeChat official account【程序猿编码

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

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

相关文章

使用 Conda 安装 xinference[all](详细版)

1. 安装 Miniconda&#xff08;若未安装&#xff09; Miniconda 是 Anaconda 的轻量版&#xff0c;仅包含 Conda 和 Python&#xff0c;适合服务器环境。 下载并安装 Miniconda 下载地址&#xff1a;Index of /miniconda &#xff0c;可以自行选择适合的版本 # 下载最新版 …

服务器登上去,显示 failed to send WATCHDOG 重启有效吗?

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 当你登录服务器时&#xff0c;看到类似以下提示&#xff1a; failed to send WATCHDOG: Resource temporarily unavailable这通常和系统的 systemd 服务有关&#xff0c;尤其是那些启用了 watchdog&#xff08;看门…

重学React(五):脱围机制一

背景&#xff1a; 之前将React的基础知识以及状态管理相关的知识都过了一遍&#xff0c;查漏补缺的同时对React也有了一些新鲜的认知&#xff0c;接下来这个模块的名字很有意思&#xff1a;脱围机制&#xff0c;内容也比之前的部分难理解一些。但整体看下来&#xff0c;理解之后…

去除Edge微软浏览器与Chrome谷歌浏览器顶部出现“此版本的Windows不再支持升级Windows 10”的烦人提示

前言 在 Windows 7 中&#xff0c;安装 Microsoft Edge 109 版本后&#xff0c;启动浏览器时会弹出提示&#xff1a; 此版本的 Windows 不再支持 Microsoft Edge。升级到 Windows 10 或更高版本&#xff0c;以获取常规功能和安全更新。 同样地&#xff0c;安装 Google Chrome 1…

PWM、脉冲

要求&#xff1a;一、PWM输出PWM波生成原理在此处使用TIM2生成PWM&#xff0c;PA1输出PWM波。CNT小于CCR时&#xff0c;输出高电平&#xff1b;CNT大于CCR时&#xff0c;输出低电平。 输入捕获测量频率的原理输入捕获的捕获意思是它在PWM波上升沿或者下降沿的时候&#xff0c;会…

文件IO(1)

.文件IO1.概念标准IO是有缓存的IO&#xff0c;文件IO没有缓存&#xff0c;适合于通信、硬件设备操作标准IO是库函数&#xff0c;文件IO是系统调用2.系统调用与库函数系统调用&#xff1a;是Linux内核中的代码&#xff0c;只能在Linux系统中使用库函数&#xff1a;是对系统调用的…

【AI】Pycharm中要注意Python程序文件的位置

博主试着在本地电脑用Pycharm环境运行随便一个机器学习然后做图像识别的模型&#xff0c;Python的程序一直报博主学习图片的路径不正确&#xff0c;博主查了好几遍&#xff0c;也没找出问题&#xff0c;后来借助Deepseek才知道&#xff0c;Python主程序的位置一定要在Project下…

TDengine 可观测性最佳实践

TDengine 介绍 TDengine 是一款开源、高性能、云原生的时序数据库&#xff0c;专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。它不仅提供了高效的数据存储和查询功能&#xff0c;还带有内建的缓存、流式计算、数据订阅等系统功能&#xff0c;能大幅减少系统设…

Jenkins 搭建鸿蒙打包

1、创建流水线工程 选择 Freestyle project 2、配置模板仓库、凭证 配置仓库地址 创建凭证&#xff0c;凭证选择账号-密码&#xff08;能够访问该仓库的个人或管理员 Gitlab 账密&#xff09; 到这里执行构建&#xff0c;便可以克隆仓库到工作目录 3、安装插件 3.1 Rebuild…

【SpringBoot】02 基础入门-什么是Spring Boot?:Spring与SpringBoot

文章目录1、Spring能做什么1.1、Spring的能力1.2、Spring的生态1.3、Spring5重大升级1.3.1、响应式编程1.3.2、内部源码设计2、为什么用SpringBoot2.1、SpringBoot优点2.2、SpringBoot缺点3、时代背景3.2、分布式分布式的困难分布式的解决3.3、云原生上云的困难4、如何学习Spri…

FFmpeg 编译安装和静态安装

FFmpeg 编译安装和静态安装 简介 FFmpeg 是一个领先的多媒体框架&#xff0c;能够解码、编码、转码、复用、解复用、流化、过滤和播放几乎所有人类和机器创建的格式。本指南将详细介绍如何在 CentOS 8.5.2111 系统上从源代码编译并安装 FFmpeg 6.1.1 版本。从源代码编译安装可…

人大BABEC地平线高效率具身导航!Aux-Think:探索视觉语言导航中数据高效的推理策略

作者&#xff1a; Shuo Wang1,3^{1,3}1,3, Yongcai Wang1^{1}1, Wanting Li1^{1}1 , Xudong Cai1^{1}1, Yucheng Wang3^{3}3, Maiyue Chen3^{3}3, Kaihui Wang3^{3}3, Zhizhong Su3^{3}3, Deying Li1^{1}1, Zhaoxin Fan2^{2}2单位&#xff1a;1^{1}1中国人民大学&#xff0c;2^…

01. maven的下载与配置

1.maven的下载与初步配置a.下载并配置仓库地址下载maven压缩包&#xff0c;并解压&#xff0c;解压后应有如下几个文件点击conf&#xff0c;打开settings.xml&#xff08;我用的VScode打开的&#xff09;&#xff0c;我们需要声明一下内部仓库的地址&#xff0c;以及私服的一些…

1701. 请输出所有的3位对称数

问题描述请输出所有的 33 位对称数&#xff0c;对称数指的是一个整数 nn 正过来和倒过来是一样的&#xff0c;比如&#xff1a;101、121、282…101、121、282…请从小到大输出符合条件的3位对称数&#xff0c;每行 11 个。输入无。输出从小到大按题意输出符合条件的数&#xff…

C++算法·排序

排序的定义 这个不用说吧 就是根据某个条件对一个数列进行有序的操作 例如要求从小到大排序、从大到小排序等等 排序的分类 比较排序(Comparison(Comparison(Comparison Sorts)Sorts)Sorts) 特点&#xff1a;通过元素间的比较决定顺序 时间复杂度下限&#xff1a;O(nO(nO(n…

微服务项目中的注册中心——Nacos配置

从零开始&#xff1a;Nacos服务注册与配置中心实战教程 Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;是阿里巴巴开源的服务发现、配置管理工具&#xff0c;集注册中心与配置中心于一体&#xff0c;广泛应用于微服务架构。本文将从环境搭建到实战配…

日期格式化成英文月,必須指定語言環境

如果不指定Locale.ENGLISH 在有些JDK下 輸出6月 INV USD 314,791.77,DUE 25-07 [PAID USD 503,389.56 ON 2025-07-16]Mar INV USD 52,042.00,DUE 25-07 [PAID USD 52,042.00 ON 2025-08-11]所以必…

【6】Transformers快速入门:Transformer 的注意力层 是啥?

一句话看懂注意力层作用&#xff1a;让 AI 像人一样 “抓重点” &#xff08;比如读“猫追老鼠”&#xff0c;自动聚焦 “追” 这个动作&#xff0c;忽略无关词&#xff09;1. 为什么需要注意力&#xff1f; 问题场景&#xff08;翻译例子&#xff09;&#xff1a; 英文&#x…

集合,完整扩展

目录 前言&#xff1a; 一、List接口 1.1 ArrayList 1.2 LinkedList 1.3 Vector 二、Set接口 2.1 HashSet 2.2 TreeSet 2.3 LinkedHashSet 三、应用选择 前言&#xff1a; 本篇文章重点梳理 List 接口和 Set 接口的核心内容&#xff0c;结合代码案例帮大家吃透它们的…

【doris基础与进阶】3-Doris安装与部署

安装前的准备 在windows系统上通过vmwareubuntu 22.04的方式进行安装&#xff0c;由于资源有限&#xff0c;在同1台机器上同时安装fe和be&#xff08;broker本次不安装&#xff0c;极简化安装&#xff09;&#xff0c;安装版本为2.1.10&#xff0c;2.x版本架构不会有大的变化&a…