再谈Intel x86_64 LBR功能

2023-11-21 01:40
文章标签 功能 x86 64 intel 再谈 lbr

本文主要是介绍再谈Intel x86_64 LBR功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、LBR
    • 1.1 LBR简介
    • 1.2 LBR format
    • 1.3 PDCM
    • 1.4 MSR_LBR_SELECT
  • 二、代码演示
    • 2.1 用户态demo
    • 2.2 msr-tools
    • 2.3 taskset
    • 2.4 MSR寄存器地址
    • 2.5 完整代码
  • 三、perf使用lbr
  • 总结
  • 参考资料

前言

在这篇文章 Intel x86_64 LBR & BTS功能 中我简单的描述了Intel处理器的 LBR功能,最近我又看了Intel手册相关章节,决定再详细写一篇 LBR功能的介绍和使用。

先简单介绍下MSR寄存器:
MSR(Model Specific Register)是x86架构中的概念,指的是在x86架构处理器中,一系列用于控制CPU运行、功能开关、调试、跟踪程序执行、监测CPU性能等方面的寄存器。
不同的CPU型号或不同的CPU厂商(Intel&AMD),它的MSR寄存器可能是不一样的,它会根据具体的CPU型号的变化而变化,每款新的CPU都有可能引入新的MSR寄存器。

一、LBR

1.1 LBR简介

LBR 通过保存产生的分支和其它的控制流程在处理器的寄存器来记录软件的执行历史路径。LBR记录的FROM地址和TO地址保存在两个MSR寄存器中。
MSR_LASTBRANCH_N_FROM_IP:保存操作的源 IP,即跳转的from地址。
MSR_LASTBRANCH_N_TO_IP:保存操作的目的IP,即跳转的to 地址。

在这里插入图片描述

LBR的数目根据处理器的型号不同而不一样,如下图所示。
在这里插入图片描述

LBR记录存储在寄存器中的顺序是按照时间来的,最近的分支LBR entry 存储在 IA32_LBR_0_FROM_IP/IA32_LBR_0_TO_IP/
IA32_LBR_0_INFO中,然后下一个存储在就是IA32_LBR_1_FROM_IP/IA32_LBR_1_TO_IP/IA32_LBR_1_INFO中,以此类推。
我以 LBR Stack size = 32为例子,如下图所示:
在这里插入图片描述

1.2 LBR format

在我们使用LBR之前,应该查询关于存储在LBR堆栈中的地址的格式。主要有四种编码格式
通过查询MSR IA32_PERF_CAPABILITIES寄存器的 bit[5:0],即0-5位。
在这里插入图片描述

(1)000000B
32-bit record format — Stores 32-bit offset in current CS of respective source/destination.

(2)000001B
64-bit LIP record format — Stores 64-bit linear address of respective source/destination.

(3)000010B / 000011B / 000100B / 000101B
64-bit EIP record format — Stores 64-bit offset (effective address) of respective source/destination.

(4)000110B / 000111B
64-bit LIP record format — Stores 64-bit linear address (CS.Base + effective address) of respective source/destination.

1.3 PDCM

在使用LBR之前先查询是否支持性能和调试功能。
通过CPUID指令查询是否提供处理器对架构 MSR IA32_PERF_CAPABILITIES 的支持。
CPUID.01H:ECX[PERF_CAPAB_MSR] (bit 15).
关于cpuid指令可以看我这篇文章:Intel x86_64使用cpuid指令获取CPU信息
在这里插入图片描述
PDCM:值为 1 表示处理器支持性能和调试功能 MSR IA32_PERF_CAPABILITIES
判断代码如下:

#include <linux/kernel.h>
#include <linux/module.h>//Processor’s support for the architectural MSR IA32_PERF_CAPABILITIES
#define PDCM_IS_SUPPORT (1<<15)//内核模块初始化函数
static int __init lkm_init(void)
{	unsigned int eax = 0;unsigned int ebx = 0;unsigned int ecx = 0;unsigned int edx = 0;cpuid(1, &eax, &ebx, &ecx, &edx);if(!(ecx & PDCM_IS_SUPPORT)) {printk("Don't support PDCM feature\n");return 0;}printk("support PDCM feature\n");return 0;}//内核模块退出函数
static void __exit lkm_exit(void)
{printk(KERN_DEBUG "exit\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");

1.4 MSR_LBR_SELECT

MSR_LBR_SELECT(该寄存器的地址为1C8H) 在 RESET 时会清零,并且 LBR filtering被禁用,即所有的分支记录类型都将被捕获。
MSR_LBR_SELECT提供位字段,用于指定不被LBR捕获的分支类型。
在这里插入图片描述
比如:
将MSR_LBR_SELECT的bit0置为1,那么LBR将不会捕获内核态(ring = 0)的分支跳转指令。
将MSR_LBR_SELECT的bit1置为1,那么LBR将不会捕获用户态(ring > 0)的分支跳转指令。

二、代码演示

实验平台:
Intel x86_64
centos 7.8
注意是在物理机上实验,虚拟机不支持LBR。

这里我为了简单起见,采用shell脚本来进行代码演示,用来捕获用户态的代码执行流的记录。

2.1 用户态demo

这里是一个最简单的while循环demo,这个dmeo将会产生许多的jmp指令。

#include <stdio.h>int main()
{int i = 0;while(1) {i++;}return 0;
}

让我们来看看其二进制反汇编代码,objdump -d a.out:
在这里插入图片描述
从反汇编我们可以看出while循环一直在执行jmp指令,产生的跳转记录如下:

{From : 4004fc , to : 4004f8}   //jmp指令

2.2 msr-tools

在这里我通过msr-tools工具包在linux shell命令上来读取或写MSR寄存器值。
下载地址有两个,我选择的是下面的一个:
https://pkgs.org/download/msr-tools
https://mirrors.edge.kernel.org/pub/linux/utils/cpu/msr-tools/
在这里插入图片描述
下面便是wrmsr和rdmsr的使用方法,我标记为红色的将是我要用的参数选项。
在这里插入图片描述

在这里插入图片描述

2.3 taskset

taskset 用于在给定 PID 的情况下设置或读取正在运行的进程的 CPU 亲和性或者启动一个新的进程时设置其 CPU亲和性。CPU 亲和性是一种调度程序属性,它将进程“绑定”到系统上给定的一组 CPU上。Linux 调度程序将遵循给定的 CPU 亲和性,并且该进程不会在任何其他 CPU 上运行

我这里主要是用来把给定的进程指定在某个cpu上工作。

(1)运行a.out程序时,将该进程绑定在CPU 1上运行

taskset -c 1 ./a.out 

(2)获取a.out进程运行在哪个CPU上:

taskset -p 进程号

2.4 MSR寄存器地址

查询当前CPU与LBR有关的MSR寄存器地址
可以通过cpuid指令获取CPU的DF_DM,我这里直接通过lscpu命令查看:
在这里插入图片描述
根据DF_DM查询Intel手册:
MSR_IA32_DEBUGCTL 寄存器的地址 0x1D9
MSR_LBR_TOS寄存器的地址:0x1C9
MSR_LBR_SELECT寄存器的地址:0x1C8

支持32对 FROM TO 记录:
MSR_LASTBRANCH_0_FROM_IP - MSR_LASTBRANCH_31_FROM_IP:0x680 - 0x69F
MSR_LASTBRANCH_0_TO_IP - MSR_LASTBRANCH_31_TO_IP:0x6C0 - 0x6DF

2.5 完整代码

# Model Specific Registers address
MSR_LASTBRANCH_0_FROM_IP=680
MSR_LASTBRANCH_0_TO_IP=6C0
MSR_IA32_DEBUGCTL=1D9
MSR_LBR_TOS=1C9
MSR_LBR_SELECT=1C8# Define ADDR_FROM and ADDR_FROM Var
ADDR_FROM=$MSR_LASTBRANCH_0_FROM_IP
ADDR_TO=$MSR_LASTBRANCH_0_TO_IP# Configuration
CORE=1   # Run the target workload on core 1 (taskset -c 1 process)
N_LBR=32 # Number of LBR records # enable MSR kernel module
sudo modprobe msr# enable LBR
sudo ./wrmsr -p ${CORE} 0x${MSR_IA32_DEBUGCTL} 0x1# do not capture branches in ring 0
sudo ./wrmsr -p ${CORE} 0x${MSR_LBR_SELECT} 0x1# wait a bit for the workload to issue enough branches
sleep 0.1# read all LBR records
for i in `seq 1 ${N_LBR}`; 
#for(( i = 0; i < ${N_LBR}; i++))
doecho "LBR record : $i"echo  0x$ADDR_FROM echo  "from address: "sudo ./rdmsr -p ${CORE} 0x${ADDR_FROM}echo  0x$ADDR_TOecho  "to address: "sudo ./rdmsr -p ${CORE} 0x${ADDR_TO}# increament ADDR_FROM (in hex) by 1  ADDR_FROM=`echo "obase=16; ibase=16; ${ADDR_FROM} + 1;" | bc`# increament ADDR_TO (in hex) by 1 ADDR_TO=`echo "obase=16; ibase=16; ${ADDR_TO} + 1;" | bc`
done

(1)设置进程CPU亲和性:

taskset -c 1 ./a.out &

在这里插入图片描述
1:表示CPU1(运行在第二个CPU上)。
22896 :表示进程的PID号。

(2)运行shell脚本,查看结果:
在这里插入图片描述
在这里插入图片描述
可见获取到了用户态程序的控制执行流程,并与预期一致:

{From : 4004fc , to : 4004f8}   //jmp指令

三、perf使用lbr

NAMEperf-record - Run a command and record its profile into perf.data
DESCRIPTIONThis command runs a command and gathers a performance counter profile from it, into perf.data - without displaying anything.This file can then be inspected later on, using perf report.-F, --freq=Profile at this frequency.-a, --all-cpusSystem-wide collection from all CPUs (default if no target is specified).-gEnables call-graph (stack chain/backtrace) recording.--call-graphSetup and enable call-graph (stack chain/backtrace) recording, implies -g. Default is "fp".Allows specifying "fp" (frame pointer) or "dwarf"(DWARF's CFI - Call Frame Information) or "lbr"(Hardware Last Branch Record facility) as the method to collectthe information used to show the call graphs.In some systems, where binaries are build with gcc--fomit-frame-pointer, using the "fp" method will produce boguscall graphs, using "dwarf", if available (perf tools linked tothe libunwind or libdw library) should be used instead.Using the "lbr" method doesn't require any compiler options. Itwill produce call graphs from the hardware LBR registers. Themain limitation is that it is only available on new Intelplatforms, such as Haswell. It can only get user call chain. Itdoesn't work with branch stack sampling at the same time.When "dwarf" recording is used, perf also records (user) stack dumpwhen sampled.  Default size of the stack dump is 8192 (bytes).User can change the size by passing the size after comma like"--call-graph dwarf,4096".
[root@localhost perf]# perf record -F 99 -a --call-graph lbr
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.726 MB perf.data (225 samples) ][root@localhost perf]# perf script
perf  9409 [000] 524426.499793:          1 cycles:ppp:26a328 native_write_msr_safe (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)209794 __intel_pmu_enable_all.isra.13 (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)209800 intel_pmu_enable_all (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)20591a x86_pmu_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a4c8d perf_pmu_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a5862 ctx_resched (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a59eb __perf_event_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)39e368 event_function (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a008a remote_function (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)311101 generic_exec_single (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3111af smp_call_function_single (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)39f213 cpu_function_call (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a3f21 event_function_call (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a4021 _perf_event_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)39f07a perf_event_for_each_child (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a87e0 perf_ioctl (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)456210 do_vfs_ioctl (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)4564b1 sys_ioctl (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)974ddb system_call (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)26a328 native_write_msr_safe (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)

注意,LBR通常受限于堆栈深度(8、16或32帧),因此它可能不适合于深层堆栈或火焰图生成,因为火焰图需要走到公共根进行合并。

以下是使用默认帧指针遍历采样的相同程序:

[root@localhost perf]# perf record -F 99 -a -g
^C[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.635 MB perf.data (100 samples) ][root@localhost perf]#
[root@localhost perf]# perf script
swapper     0 [000] 524994.265011:          1 cycles:ppp:26a328 native_write_msr_safe (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)209794 __intel_pmu_enable_all.isra.13 (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)209800 intel_pmu_enable_all (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)20591a x86_pmu_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a4c8d perf_pmu_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a5862 ctx_resched (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a59eb __perf_event_enable (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)39e368 event_function (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)3a008a remote_function (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)311373 flush_smp_call_function_queue (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)311a73 generic_smp_call_function_single_interrupt (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)25715d smp_call_function_single_interrupt (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)9770a2 call_function_single_interrupt (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)7ae10e cpuidle_idle_call (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)2366de arch_cpu_idle (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)2fc3ba cpu_startup_entry (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)94feb7 rest_init (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)11861c6 start_kernel ([kernel.vmlinux].init.text)118572f x86_64_start_reservations ([kernel.vmlinux].init.text)1185885 x86_64_start_kernel ([kernel.vmlinux].init.text)2000d5 start_cpu (/usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux)

如果不需要很长的堆栈可以使用lbr来追踪指令流。

总结

上述就是简单的使用LBR来获取应用程序的程序执行流程,可用来进行安全领域的对抗检测。

参考资料

Intel 手册 vol 3
https://sorami-chi.hateblo.jp/entry/2017/12/17/230000
https://github.com/soramichi/LBR-sample/blob/master/README.en.md
https://www.brendangregg.com/perf.html

这篇关于再谈Intel x86_64 LBR功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/398936

相关文章

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

苹果macOS 26 Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色

《苹果macOS26Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色》在整体系统设计方面,macOS26采用了全新的玻璃质感视觉风格,应用于Dock栏、应用图标以及桌面小部件等多个界面... 科技媒体 MACRumors 昨日(6 月 13 日)发布博文,报道称在 macOS 26 Tahoe 中

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

MybatisPlus service接口功能介绍

《MybatisPlusservice接口功能介绍》:本文主要介绍MybatisPlusservice接口功能介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录Service接口基本用法进阶用法总结:Lambda方法Service接口基本用法MyBATisP

Java反射实现多属性去重与分组功能

《Java反射实现多属性去重与分组功能》在Java开发中,​​List是一种非常常用的数据结构,通常我们会遇到这样的问题:如何处理​​List​​​中的相同字段?无论是去重还是分组,合理的操作可以提高... 目录一、开发环境与基础组件准备1.环境配置:2. 代码结构说明:二、基础反射工具:BeanUtils

Druid连接池实现自定义数据库密码加解密功能

《Druid连接池实现自定义数据库密码加解密功能》在现代应用开发中,数据安全是至关重要的,本文将介绍如何在​​Druid​​连接池中实现自定义的数据库密码加解密功能,有需要的小伙伴可以参考一下... 目录1. 环境准备2. 密码加密算法的选择3. 自定义 ​​DruidDataSource​​ 的密码解密3

SpringCloud使用Nacos 配置中心实现配置自动刷新功能使用

《SpringCloud使用Nacos配置中心实现配置自动刷新功能使用》SpringCloud项目中使用Nacos作为配置中心可以方便开发及运维人员随时查看配置信息,及配置共享,并且Nacos支持配... 目录前言一、Nacos中集中配置方式?二、使用步骤1.使用$Value 注解2.使用@Configur

SpringBoot后端实现小程序微信登录功能实现

《SpringBoot后端实现小程序微信登录功能实现》微信小程序登录是开发者通过微信提供的身份验证机制,获取用户唯一标识(openid)和会话密钥(session_key)的过程,这篇文章给大家介绍S... 目录SpringBoot实现微信小程序登录简介SpringBoot后端实现微信登录SpringBoo