Intel x86_64 LBR功能

news2024/11/28 10:56:46

文章目录

  • 前言
  • 一、CPUID指令
    • 1.1 CPUID功能简介
    • 1.2 输入参数01H返回结果
      • 1.2.1 ECX返回结果
      • 1.2.2 EDX返回结果
    • 1.3 Linux中CPUID指令
      • 1.3.1 应用层调用cpid指令
      • 1.3.2 linux内核中调用cpuid指令
  • 二、MSR寄存器
    • 2.1 MSR 寄存器简介
    • 2.2 RDMSR,WRMSR指令介绍
    • 2.3 IA32_DEBUGCTL MSR 寄存器
  • 三、LBR
    • 3.1 LBR简介
    • 3.2 LBR的用法
    • 3.3 代码演示
      • 3.3.1 用户态demo
      • 3.3.2 msr-tools
      • 3.3.3 taskset
      • 3.3.4 MSR寄存器地址
      • 3.3.5 完整代码

前言

本篇文章介绍 Intel 处理器的LBR指令追踪功能,通过该功能可以从硬件层面获取CPU执行的指令信息,原理和流程大致如下:

  1. 通过CPUID指令读取处理器的各种标识和特性信息,判断是否支持硬件调试功能,rdmsr / wrmsr 指令是否可用;
  2. 通过wrmsr指令设置 IA32_DEBUGCTL MSR 寄存器,开启LBR功能;
  3. 通过rdmsr指令读取 IA32_DEBUGCTL MSR 寄存器,获取跳转指令信息;

下面我们将对这几个方能进行更加详细的介绍。

一、CPUID指令

1.1 CPUID功能简介

该指令可以从读取处理器的各种标识和特性信息(比如:CPU型号和支持的功能),并将指令执行完后返回的信息保存在 EAX, EBX, ECX,和 EDX 寄存器中。
CPUID指令有两组功能:一组返回的是基本信息,另一组返回的是扩展信息。
该指令有一个输入参数(可能会有两个),该参数会传递给EAX(ECX)寄存器,一般情况下只输入一个参数,根据输入参数的不同,返回给EAX, EBX, ECX, and EDX寄存器的信息也不一样。简介如下:
在这里插入图片描述
针对不同的输入,返回结果如下:
在这里插入图片描述

1.2 输入参数01H返回结果

这里我们重点关注EAX = 01H时,ECX,EDX返回的结果。

1.2.1 ECX返回结果

在这里插入图片描述

  • PDCM:Perfmon and Debug Capability 。该值为1表示处理器支持性能和调试能力。
  • DS-CPL:CPL Qualified Debug Store。该值为1表示处理器支持对Debug Store特性的扩展,允许根据当前系统处于的特权等级对 branch message 进行过滤。(0:表示内核态,3:表示用户态)
  • DTES64:64-bit DS Area。该值为1表示处理器支持在DS Area存放64位地址。

1.2.2 EDX返回结果

在这里插入图片描述
在这里主要解释一下2个参数,与LBR和BTS有关:

  • DS:Debug Store。处理器支持将调试信息写入内存驻留缓冲区。BTS和PEBS使用该特性。可以理解为处理器是否支持BTS和 PEBS功能。
  • MSR :Model Specific Registers RDMSR and WRMSR Instructions。CPU是否支持指令rdmsr/wrmsr来读写MSR寄存器

1.3 Linux中CPUID指令

1.3.1 应用层调用cpid指令

这里给出一个简单的应用程序来获取处理器厂商ID(vendor ID)和 family ,如下:

#include <stdio.h>

#define X86_VENDOR_INTEL       0
#define X86_VENDOR_AMD         1
#define X86_VENDOR_UNKNOWN     2

#define QCHAR(a, b, c, d) ((a) + ((b) << 8) + ((c) << 16) + ((d) << 24))
#define CPUID_INTEL1 QCHAR('G', 'e', 'n', 'u')
#define CPUID_INTEL2 QCHAR('i', 'n', 'e', 'I')
#define CPUID_INTEL3 QCHAR('n', 't', 'e', 'l')
#define CPUID_AMD1 QCHAR('A', 'u', 't', 'h')
#define CPUID_AMD2 QCHAR('e', 'n', 't', 'i')
#define CPUID_AMD3 QCHAR('c', 'A', 'M', 'D')

#define CPUID_IS(a, b, c, ebx, ecx, edx)	\
		(!((ebx ^ (a))|(edx ^ (b))|(ecx ^ (c))))

static inline void cpuid(int op, unsigned int *eax, unsigned int *ebx,
				             unsigned int *ecx, unsigned int *edx)
{
     asm volatile("cpuid" //asm 表示内核汇编,执行cpuid指令, volatile 表示告诉gcc编译器不要优化代码 
	    : "=a" (*eax),   //第一个冒号后面: 是输出参数。
	      "=b" (*ebx),   //输出操作数约束应该带有一个约束修饰符 "=",指定它是输出操作数
	      "=c" (*ecx),
	      "=d" (*edx)
	    : "0" (*eax)	//第二个冒号后面: 是输入参数  Intel手册也说明ecx有时候也作为输入参数
	    : "memory");     
}

static int x86_vendor(void)
{
	unsigned eax = 0x00000000;
	unsigned ebx, ecx = 0, edx;

	cpuid(0, &eax, &ebx, &ecx, &edx);

	if (CPUID_IS(CPUID_INTEL1, CPUID_INTEL2, CPUID_INTEL3, ebx, ecx, edx))
          printf("GenuineIntel\n");
		return X86_VENDOR_INTEL;

	if (CPUID_IS(CPUID_AMD1, CPUID_AMD2, CPUID_AMD3, ebx, ecx, edx))
          printf("AuthenticAMD\n");
		return X86_VENDOR_AMD;

	return X86_VENDOR_UNKNOWN;
}

static int x86_family(void)
{
	unsigned eax = 0x00000001;
	unsigned ebx, ecx = 0, edx;
	int x86;

	cpuid(1, &eax, &ebx, &ecx, &edx);

	x86 = (eax >> 8) & 0xf;
	if (x86 == 15)
		x86 += (eax >> 20) & 0xff;

	return x86;
}

int main()
{
    unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

     cpuid(0, &eax, &ebx, &ecx, &edx);
     printf("EBX ← %x (“Genu”)EDX ← %x (“ineI”) ECX ← %x (“ntel”)\n", ebx, edx ,ecx);

     int vendor = x86_vendor();
     int family = x86_family();

     printf("%d %d \n", vendor, family);

     return 0;

}

执行结果如下:
在这里插入图片描述
该结果与Intel软件发开者手册一致。
在这里插入图片描述
使用lscpu命令也可以看到Vendor IDCPU family信息。
在这里插入图片描述

1.3.2 linux内核中调用cpuid指令

除了在应用层通过汇编指令调用cpuid指令外,还可以在内核模块直接调用cpuid函数接口
该接口定义在arch/x86/include/asm/processor.h (内核版本3.10.0)中:

/*
 * Generic CPUID function
 * clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx
 * resulting in stale register contents being returned.
 */
static inline void cpuid(unsigned int op,
			 unsigned int *eax, unsigned int *ebx,
			 unsigned int *ecx, unsigned int *edx)
{
	*eax = op;
	*ecx = 0;
	__cpuid(eax, ebx, ecx, edx);
}

#define __cpuid			native_cpuid

static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
				unsigned int *ecx, unsigned int *edx)
{
	/* ecx is often an input as well as an output. */
	asm volatile("cpuid"
	    : "=a" (*eax),
	      "=b" (*ebx),
	      "=c" (*ecx),
	      "=d" (*edx)
	    : "0" (*eax), "2" (*ecx)
	    : "memory");
}

通过一个简单的模块测试cpuid指令:

#include <linux/kernel.h>
#include <linux/module.h>


//内核模块初始化函数
static int __init lkm_init(void)
{
	unsigned int eax = 0;
	unsigned int ebx = 0;
	unsigned int ecx = 0;
	unsigned int edx = 0;

	cpuid(0, &eax, &ebx, &ecx, &edx);
	
	printk("EBX:%xh(“Genu”) EDX:%xh(“ineI”) ECX:%xh(“ntel”)\n", ebx, edx ,ecx);
		
	return 0;
}

//内核模块退出函数
static void __exit lkm_exit(void)
{
	printk(KERN_DEBUG "exit\n");
}

module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

Makefile文件:

obj-m := kcpuid.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

在这里插入图片描述

二、MSR寄存器

2.1 MSR 寄存器简介

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

2.2 RDMSR,WRMSR指令介绍

在前面我们提到,通过CPUID可以查询当前CPU是否支持RDMSR,WRMSR指令,而这两条指令正是Intel处理器提供用来读取/写入 MSR寄存器中数据的。
这两条指令必须在 privilege level 0(linux中的内核态)或实模式下执行:
在这里插入图片描述
在这里插入图片描述
而在linux内核提供了两个接口,位于arch/x86/include/asm/msr.h文件中:在这里插入图片描述

2.3 IA32_DEBUGCTL MSR 寄存器

A32_DEBUGCTL 寄存器其地址为 01D9H(不同的CPU family名字可能会不一样,比如MSR_DEBUGCTLA, MSR_DEBUGCTLB),可以用来调试,跟踪中断、LBR、BTS等等。
下面介绍几个较重要的位:

  1. bit0: LBR (last branch/interrupt/exception) flag 。该位被设置后,处理器开始记录跟踪处理器产生的最近的分支、中断和/或异常,并储存在 LBR stack MSRs中。
  2. bit1: BTF (single-step on branches) flag 。该位被设置后,处理器将EFLAGS寄存器中的TF标志视为:TF as single-step on branches instead of single-step on instructions。(x86_64的gdb单步调试功能就是用EFLAGS寄存器中的TF位—single-step on instructions 来实现的,后面会写一篇x86_64的gdb单步调试跟踪原理的文章)
  3. bit6: TR (trace message enable) flag。该位被设置后,当处理器检测到一个已经产生的分支,中断,或异常;它将这个分支记录作为 branch trace message(BTM)发送到系统总线上。
  4. bit7: BTS (branch trace store) flag。该位被设置后,BTS能够将BTMS保存到DS save area的内存驻留BTS buffer中。
  5. bit8: BTINT (branch trace interrupt) flag。该位被设置后,当BTS缓冲区满时,BTS会产生一个中断。
  6. bit9: BTS_OFF_OS (branch trace off in privileged code) flag。该位被设置后,如果CPL为0,则跳过BTS或BTM,即:在内核态不启用bts功能。
  7. bit10:BTS_OFF_USR (branch trace off in user code) flag。该位被设置后,如果CPL大于0,则跳过BTS或BTM。即:在用户态不启用bts功能。

因此可以通过这两bit BTS_OFF_OS/BTS_OFF_USR 来设置是获取用户态的分支跳转指令还是内核态的分支跳转指令。
CPL:Current Privilege Level,该值为0代表最高优先级,该值为3代表最低优先级,linux 中,0为内核态,3为用户态。
在这里插入图片描述
到这里我们已经介绍了如何查看CPU特性,什么是MSR寄存器,如何操作MSR寄存器,以及IA32_DEBUGCTL 这个专门负责调试的MSR寄存器,下面我们讲进一步介绍IA32_DEBUGCTL 寄存器中LRB功能,并给出一个利用LBR功能获取CPU中跳转指令数据的脚本程序。

三、LBR

3.1 LBR简介

IA32_DEBUGCTL MSR 寄存器的bit0被设置后,处理器开始自动记录 产生的分支,中断,异常等branch records,并存储在 LBR stack MSRs中。下面来介绍下 LBR stack与 TOS Pointer

  1. Last Branch Record (LBR) Stack :LBR由N对msr寄存器组成(N是LBR堆栈大小,如下表所示),msr存储了最近分支的源地址和目的地址。
  2. Last Branch Record Top-of-Stack (TOS) Pointer:TOS Pointer MSR中最低有效的M位包含了一个指向LBR堆栈中MSR的M位指针,该指针包含了记录的最近的分支、中断或异常。

在使用LBR stack记录分支信息时,TOS寄存器指示stack当前位置. 从而可以从LBR stack中正确读取分支记录.

从下表可以看出,LBR堆栈中msr的个数和TOS指针值的有效范围对于不同的处理器家族中会有所不同。
在这里插入图片描述
LBR msr是64位的寄存器。在64位模式下, last branch records存储完整地址。32位模式下,高32位置0,低32为存储最近分支记录。
MSR_LASTBRANCH_0_FROM_IP — (N-1) MSR address 存储分支记录源地址
MSR_LASTBRANCH_0_TO_IP — (N-1) MSR address 存储分支记录目的地址
在这里插入图片描述

3.2 LBR的用法

(1)查询LBR stack存储格式 :IA32_PERF_CAPABILITIES MSR (调用rdmsrl)
在这里插入图片描述
(2)开启LBR功能,设置 IA32_DEBUGCTL MSR寄存的 bit0 = 1 (调用wrmsrl)
在这里插入图片描述
(3) 读取TOS指针位置 (调用rdmsrl)
读取 MSR_LASTBRANCH_TOS 寄存器 ,请参考 Intel vol4
(4) 读取LBR stack寄存器 (调用rdmsrl)
读取 MSR_LASTBRANCH_x_FROM_IP / MSR_LASTBRANCH_x_TO_IP 寄存器 ,请参考 Intel vol4

LBR的优点:分支记录存储在寄存器中,几乎没有性能开销。
LBR的缺点:寄存器组数量有限,这样我们保存的分支记录也有限。

3.3 代码演示

实验平台:
Intel x86_64、centos 7.8
注意是在物理机上实验,虚拟机不支持LBR。
这里我为了简单起见,采用shell脚本来进行代码演示,用来捕获用户态的代码执行流的记录。

3.3.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指令

3.3.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/
在这里插入图片描述

3.3.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 进程号

3.3.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

3.3.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++))
do
    echo "LBR record : $i"
    echo -n 0x$ADDR_FROM
    echo -n ", from address: "
    sudo ./rdmsr -p ${CORE} 0x${ADDR_FROM}
    echo -n 0x$ADDR_TO
    echo -n ", 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 ./demo &

1:表示CPU1(运行在第二个CPU上)。
22896 :表示进程的PID号。

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

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

参考资料

  1. Intel x86_64 CPUID指令介绍
  2. Intel x86_64 LBR & BTS功能
  3. 再谈Intel x86_64 LBR功能
  4. Intel® 64 and IA-32 Architectures Software Developer Manuals

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

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

相关文章

净利暴跌9成,主力业务下滑,这家全球知名CIS供应商如何“翻身”?

消费电子寒冬对上游供应链的影响还在持续。 近日&#xff0c;全球知名的CMOS图像传感器&#xff08;CIS&#xff09;供应商格科微发布三季报显示&#xff0c;前三季度共实现营业收入32.45亿元&#xff0c;同比下降29.01%&#xff1b;实现净利润4972.57万元&#xff0c;同比下降…

开发中常用的SQL语句

开发中常用的SQL语句 1.update更新时不能引用本身表2.备份MySQL3.函数的使用1. case,when的使用2. IF3.其它4.拼接5. 处理时间 4.导出表结构注释等 1.update更新时不能引用本身表 UPDATE student SET valid_flag 0 WHERE id IN (SELECT idFROM (SELECT su.idFROM student su …

如何接入电商数据(淘宝/京东)API接口的对接获取(商品详情|价格|SKU)

双11是电商行业的两个重大节点&#xff0c;这两大节日吸引了大量消费者参与&#xff0c;同时也为电商企业带来了巨大的销售机会和业绩增长。 作为疫情放开之后的第一场“战役”&#xff0c;今年618显然被寄予了厚望。无论是大型电商品牌还是小型电商商家&#xff0c;都在积极探…

SQL练习---511.游戏玩法分析 I

题目描述 分析 题目描述很简单&#xff0c;找出用户第一次登陆的时期&#xff0c;很简单一个用户有多个记录&#xff0c;因此按用户分组即可&#xff0c;但是不知道日期能否求出最小值&#xff0c;事实证明还是可以的。 题解 select player_id,min(event_date) first_login f…

ObjectMapper - 实现复杂类型对象反序列化(天坑!)

目录 一、复杂类型反序列化 1.1、背景 1.2、问题解决 一、复杂类型反序列化 1.1、背景 a&#xff09;例如有 AppResult 对象&#xff0c;如下&#xff1a; Data public class AppResult {private Integer code;private String msg;private Object data;} b&#xff09;App…

Postgresql 常用整理

文章目录 1. 查询1.1数据库表1.1.1 获取指定数据库表1.1.2 获取指定数据库表所有列名 1.2 别名1.2.1 子表指定别名1.2.2 查询结果指定别名 1.3 临时表1.3.1 定义临时表1.3.2 使用临时表 1.4 子表1.5 分组1.5.1 group by1.5.2 partition by 1.6 分组后合并指定列字段&#xff1a…

基于若依的ruoyi-nbcio流程管理系统仿钉钉流程json转bpmn的flowable的xml格式(排它条件网关)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 这个章节来完成并行网关与排它条件网关的功能 1、前端 目前就修改了排它条件网关的前端条件部分&#xf…

SAP 11策略测试简介

下面我们将测试11策略 1、首先准备好物料 成品物料为AB1,在MRP3视图中维护对应的策略组的11 同时选择消耗模式为2.消耗期间都是999 在这个视图上,我们不仅仅是将“策略组”字段维护成11,同时,我们还需要将“综合MRP”字段维护成“2”。这就是11策略很特别的地方。“策略组”…

excel如何加密(excel加密的三种方法)

Excel是一款广泛使用的办公软件&#xff0c;有时候我们需要对一些重要的Excel文件进行加密&#xff0c;以保证文件的安全性。下面将介绍3种常用的Excel加密方法。 方法一&#xff1a;通过路径文件-另存为-工具-常规选项-设置打开或修改权限密码&#xff08;密码只可以使数字、字…

龙讯旷腾半导体缺陷计算大赛发布

2023计算大赛 第二期半导体缺陷计算大赛 选拔赛截止日期11月23日晚 决赛截止日期11月30日晚 线上线下同步 线下11月末杭州 大赛亮点 免费培训、灵活安排时间参与、线上线下&#xff08;杭州&#xff09;同步召开 多次机会冲关决赛奖励金 已购/未购用户均可参加、无身份…

android开发布局知识

插件开发的视频笔记&#xff1a;

PM - 项目管理 产品管理区别

产品管理和项目管理是两个在企业中至关重要的职能部门&#xff0c;它们各自承担着不同的职责和任务。虽然两者在某些方面存在重叠&#xff0c;但它们的核心目标和方法有很大的不同。本文将对产品管理和项目管理进行详细的比较和分析。 “项目管理和产品管理有什么区别&#xff…

雷达波形之一——LFM线性调频波形

文章目录 前言一、线性调频信号的形式1、原理2、时域表达式3、频域表达式 二、MATLAB 仿真1、涅菲尔积分①、MATLAB 源码②、仿真结果 2、LFM①、MATLAB 源码②、仿真结果1) 典型 LFM 波形&#xff0c;实部2) 典型 LFM 波形&#xff0c;虚部3) LFM 波形的典型谱 前言 线性调频…

2.前端调试(控制台使用)

消息堆叠 如果一条消息连续重复&#xff0c;而不是在新行上输出每一个消息实例&#xff0c;控制台将“堆叠”消息并在左侧外边距显示一个数字。此数字表示该消息已重复的次数。 如果您倾向于为每一个日志使用一个独特的行条目&#xff0c;请在 DevTools 设置中启用 Show times…

LeetCode 2258. 逃离火灾:BFS

【LetMeFly】2258.逃离火灾 力扣题目链接&#xff1a;https://leetcode.cn/problems/escape-the-spreading-fire/ 给你一个下标从 0 开始大小为 m x n 的二维整数数组 grid &#xff0c;它表示一个网格图。每个格子为下面 3 个值之一&#xff1a; 0 表示草地。1 表示着火的格…

打印流详解

概述 作用&#xff1a;打印流可以实现方便、高效的打印数据到文件中去。 高效体现在用到了缓冲流&#xff1a; public PrintStream(OutputStream out, boolean autoFlush, Charset charset) {super(out);this.autoFlush autoFlush;this.charOut new OutputStreamWriter(thi…

西门子S7-1200PLC混合通信编程(ModbusTcp和UDP通信)

S7-1200PLC的MODBUS-TCP通信 西门子PLC ModbusTcp通信访问网关后从站(SCL语言轮询状态机)-CSDN博客文章浏览阅读305次。西门子PLC的ModbusTcp通信在专栏已有很多文章介绍,所不同的是每个项目的通信需求都略有不同,今天我们以访问网关后的三个从站数据来举例,给出轮询的推荐…

【带头学C++】----- 三、指针章 ---- 3.10 函数指针(补充基础知识)

1.函数指针 1.1 函数的返回值类型为指针类型 将函数内部的合法地址通过返回值 返回给函数外部使用 注意:函数不要返回普通局部变量的地址 分析&#xff1a; 在这段代码中&#xff0c;函数getAddr()返回一个指向局部变量data地址&#xff08;作用域是函数内部&#xff09;的指…

单链表(5)

判空函数 *一进函数先断言 获取数据结点的个数函数 如图&#xff0c;p->nextNULL就跳出的话&#xff0c;当前p->data就没算上。 现在来测试一下 同样在空表时也调用一下 还有这样写的&#xff0c;出来的结果也是一样的&#xff0c;它也算是对的——但是&#xff0c;这是…

数据结构与算法—冒泡排序快速排序

目录 一、交换排序 二、冒泡排序 时间复杂度 三、快速排序 1、三种一次划分操作 Hoare法 挖洞法 前后指针法 三种方法总结&#xff1a; 2、改进划分效率 3、递归实现快速排序 4、非递归实现快速排序 栈的函数&#xff1a; 非递归排序函数&#xff1a; 5、时…