Linux x86_64平台指令替换函数 text_poke_smp/bp
文章目录
- 前言
- 一、text_poke_early
- 1.1 text_poke_early简介
- 1.2 用途
- 二、text_poke_smp
- 2.1 简介
- 2.1.1 text_poke_smp函数
- 2.2.2 stop_machine_text_poke简介
- 2.2.3 text_poke函数
- 2.2 用途
- 三、text_poke_smp 内核hook
前言
Linux x86_64平台指令替换函数有两种类型:
(1)在内核启动阶段指令替换函数:text_poke_early
(2)在内核运行阶段指令替换函数:text_poke_smp/bp
在3.12.0版本以后,内核运行阶段指令替换函数由text_poke_smp改为text_poke_bp。
这里我以centos7 3.10.0为例主要介绍text_poke_smp函数。
一、text_poke_early
1.1 text_poke_early简介
// linux-3.10/arch/x86/kernel/alternative.c/*** text_poke_early - Update instructions on a live kernel at boot time* @addr: address to modify* @opcode: source of the copy* @len: length to copy** When you use this code to patch more than one byte of an instruction* you need to make sure that other CPUs cannot execute this code in parallel.* Also no thread must be currently preempted in the middle of these* instructions. And on the local CPU you need to be protected again NMI or MCE* handlers seeing an inconsistent instruction while you patch.*/
void *__init_or_module text_poke_early(void *addr, const void *opcode,size_t len)
{unsigned long flags;local_irq_save(flags);memcpy(addr, opcode, len);sync_core();local_irq_restore(flags);/* Could also do a CLFLUSH here to speed up CPU recovery; butthat causes hangs on some VIA CPUs. */return addr;
}
这段代码实现了在系统引导时(boot time)修改内核指令的函数text_poke_early()。它用于在引导过程中更新内核指令,以便在早期阶段进行修补。
函数的参数包括:
addr:要修改的指令的地址。
opcode:要复制的指令的源地址。
len:要复制的指令长度。
函数的实现步骤如下:
(1)使用local_irq_save()保存当前CPU的中断状态,并禁用中断。
(2)使用memcpy()将源指令(opcode)的内容复制到目标地址(addr)。
(3)调用sync_core()进行同步,以确保指令修改生效。
(4)使用local_irq_restore()恢复之前保存的中断状态,重新启用中断。
1.2 用途
主要用于以下几个地方:
(1)alternatives 指令替换:具体可以参考:Linux x86_64架构 动态替换 altinstructions
内核启动时进行alternatives 指令替换。
/* Replace instructions with better alternatives for this CPU type.This runs before SMP is initialized to avoid SMP problems withself modifying code. This implies that asymmetric systems whereAPs have less capabilities than the boot processor are not handled.Tough. Make sure you disable such features by hand. */void __init_or_module apply_alternatives(struct alt_instr *start,struct alt_instr *end)
{struct alt_instr *a;u8 *instr, *replacement;u8 insnbuf[MAX_PATCH_LEN];DPRINTK("%s: alt table %p -> %p\n", __func__, start, end);/** The scan order should be from start to end. A later scanned* alternative code can overwrite a previous scanned alternative code.* Some kernel functions (e.g. memcpy, memset, etc) use this order to* patch code.** So be careful if you want to change the scan order to any other* order.*/for (a = start; a < end; a++) {instr = (u8 *)&a->instr_offset + a->instr_offset;replacement = (u8 *)&a->repl_offset + a->repl_offset;BUG_ON(a->replacementlen > a->instrlen);BUG_ON(a->instrlen > sizeof(insnbuf));BUG_ON(a->cpuid >= (NCAPINTS + NBUGINTS) * 32);if (!boot_cpu_has(a->cpuid))continue;memcpy(insnbuf, replacement, a->replacementlen);/* 0xe8 is a relative jump; fix the offset. */if (*insnbuf == 0xe8 && a->replacementlen == 5)*(s32 *)(insnbuf + 1) += replacement - instr;add_nops(insnbuf + a->replacementlen,a->instrlen - a->replacementlen);text_poke_early(instr, insnbuf, a->instrlen);}
}
(2)Linux jump label机制:具体可参考:Linux Static Keys和jump label机制
内核启动时修改jump label。
// linux-3.10/init/main.c
start_kernel()-->jump_label_init()-->arch_jump_label_transform_static()
__init_or_module void arch_jump_label_transform_static(struct jump_entry *entry,enum jump_label_type type)
{__jump_label_transform(entry, type, text_poke_early);
}
static void __jump_label_transform(struct jump_entry *entry,enum jump_label_type type,void *(*poker)(void *, const void *, size_t))
{union jump_code_union code;if (type == JUMP_LABEL_ENABLE) {code.jump = 0xe9;code.offset = entry->target -(entry->code + JUMP_LABEL_NOP_SIZE);} elsememcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);(*poker)((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE);
}
(3)Linux Static calls机制:Linux Static calls机制
// linux-5.15/arch/x86/kernel/static_call.cstatic void __ref __static_call_transform(void *insn, enum insn_type type, void *func)
{const void *emulate = NULL;int size = CALL_INSN_SIZE;const void *code;switch (type) {case CALL:code = text_gen_insn(CALL_INSN_OPCODE, insn, func);if (func == &__static_call_return0) {emulate = code;code = &xor5rax;}break;case NOP:code = x86_nops[5];break;case JMP:code = text_gen_insn(JMP32_INSN_OPCODE, insn, func);break;case RET:code = text_gen_insn(RET_INSN_OPCODE, insn, func);size = RET_INSN_SIZE;break;}if (memcmp(insn, code, size) == 0)return;if (unlikely(system_state == SYSTEM_BOOTING))return text_poke_early(insn, code, size);text_poke_bp(insn, code, size, emulate);
}
(4)ftrace
二、text_poke_smp
2.1 简介
2.1.1 text_poke_smp函数
/*** text_poke_smp - Update instructions on a live kernel on SMP* @addr: address to modify* @opcode: source of the copy* @len: length to copy** Modify multi-byte instruction by using stop_machine() on SMP. This allows* user to poke/set multi-byte text on SMP. Only non-NMI/MCE code modifying* should be allowed, since stop_machine() does _not_ protect code against* NMI and MCE.** Note: Must be called under get_online_cpus() and text_mutex.*/
void *__kprobes text_poke_smp(void *addr, const void *opcode, size_t len)
{struct text_poke_params tpp;struct text_poke_param p;p.addr = addr;p.opcode = opcode;p.len = len;tpp.params = &p;tpp.nparams = 1;atomic_set(&stop_machine_first, 1);wrote_text = 0;/* Use __stop_machine() because the caller already got online_cpus. */__stop_machine(stop_machine_text_poke, (void *)&tpp, cpu_online_mask);return addr;
}
这段代码实现了在SMP(对称多处理器)系统上更新内核中的指令的函数text_poke_smp()。它使用stop_machine()函数在SMP系统上修改多字节指令。这允许用户在SMP系统上修改多字节的内核指令。但需要注意的是,由于stop_machine()函数无法保护代码免受NMI(非屏蔽中断)和MCE(机器检查异常)的影响,因此只应允许非NMI和MCE的代码进行修改。
函数的参数包括:
addr:要修改的指令的地址。
opcode:要复制的指令的源地址。
len:要复制的指令长度。
主要调用stop_machine_text_poke函数。
2.2.2 stop_machine_text_poke简介
/** Cross-modifying kernel text with stop_machine().* This code originally comes from immediate value.*/
static atomic_t stop_machine_first;
static int wrote_text;struct text_poke_params {struct text_poke_param *params;int nparams;
};static int __kprobes stop_machine_text_poke(void *data)
{struct text_poke_params *tpp = data;struct text_poke_param *p;int i;if (atomic_xchg(&stop_machine_first, 0)) {for (i = 0; i < tpp->nparams; i++) {p = &tpp->params[i];text_poke(p->addr, p->opcode, p->len);}smp_wmb(); /* Make sure other cpus see that this has run */wrote_text = 1;} else {while (!wrote_text)cpu_relax();smp_mb(); /* Load wrote_text before following execution */}for (i = 0; i < tpp->nparams; i++) {p = &tpp->params[i];flush_icache_range((unsigned long)p->addr,(unsigned long)p->addr + p->len);}/** Intel Archiecture Software Developer's Manual section 7.1.3 specifies* that a core serializing instruction such as "cpuid" should be* executed on _each_ core before the new instruction is made visible.*/sync_core();return 0;
}
这段代码实现了使用stop_machine()函数进行内核文本交叉修改的功能。它通过stop_machine_text_poke()函数实现了在停机状态下修改内核文本的操作。
代码中使用了以下全局变量:
atomic_t类型的stop_machine_first:用于标记是否为第一次执行stop_machine_text_poke()函数。
int类型的wrote_text:表示是否已经修改了文本。
结构体text_poke_params定义了传递给stop_machine_text_poke()函数的参数:
params:指向text_poke_param结构体数组的指针,用于存储要修改的参数。
nparams:参数数组的长度。
stop_machine_text_poke()函数的实现步骤如下:
(1)通过传递的参数data获取text_poke_params结构体指针tpp。
(2)如果atomic_xchg()函数将stop_machine_first的值交换为0,表示当前是第一次执行stop_machine_text_poke()函数,则执行以下操作:
a:遍历tpp->params数组,依次获取text_poke_param结构体指针p。
b:调用text_poke()函数,将p->addr地址处的指令替换为p->opcode指令,长度为p->len。
c: 使用smp_wmb()确保其他CPU能够看到此次修改。
d:将wrote_text设置为1,表示已经修改了文本。
(3)否则,即不是第一次执行stop_machine_text_poke()函数,则进入循环,直到wrote_text变为非0。
(4)使用smp_mb()在执行后加载wrote_text之前进行内存屏障操作。
(5)遍历tpp->params数组,依次获取text_poke_param结构体指针p。
(6)调用flush_icache_range()函数,刷新从p->addr到(p->addr + p->len)范围内的指令缓存。
(7)使用sync_core()函数进行核心同步,确保新指令在每个核心上都可见。
2.2.3 text_poke函数
/*** text_poke - Update instructions on a live kernel* @addr: address to modify* @opcode: source of the copy* @len: length to copy** Only atomic text poke/set should be allowed when not doing early patching.* It means the size must be writable atomically and the address must be aligned* in a way that permits an atomic write. It also makes sure we fit on a single* page.** Note: Must be called under text_mutex.*/
void *__kprobes text_poke(void *addr, const void *opcode, size_t len)
{unsigned long flags;char *vaddr;struct page *pages[2];int i;if (!core_kernel_text((unsigned long)addr)) {pages[0] = vmalloc_to_page(addr);pages[1] = vmalloc_to_page(addr + PAGE_SIZE);} else {pages[0] = virt_to_page(addr);WARN_ON(!PageReserved(pages[0]));pages[1] = virt_to_page(addr + PAGE_SIZE);}BUG_ON(!pages[0]);local_irq_save(flags);set_fixmap(FIX_TEXT_POKE0, page_to_phys(pages[0]));if (pages[1])set_fixmap(FIX_TEXT_POKE1, page_to_phys(pages[1]));vaddr = (char *)fix_to_virt(FIX_TEXT_POKE0);memcpy(&vaddr[(unsigned long)addr & ~PAGE_MASK], opcode, len);clear_fixmap(FIX_TEXT_POKE0);if (pages[1])clear_fixmap(FIX_TEXT_POKE1);local_flush_tlb();sync_core();/* Could also do a CLFLUSH here to speed up CPU recovery; butthat causes hangs on some VIA CPUs. */for (i = 0; i < len; i++)BUG_ON(((char *)addr)[i] != ((char *)opcode)[i]);local_irq_restore(flags);return addr;
}
这段代码实现了在实时内核上更新指令的函数text_poke()。它用于在实时内核中修改指定地址的指令。
函数的参数包括:
addr:要修改的指令的地址。
opcode:要复制的指令的源地址。
len:要复制的指令的长度。
函数的实现步骤如下:
(1)根据地址addr的特性,判断是否位于核心内核文本区域。如果不是核心内核文本区域,使用vmalloc_to_page()将地址转换为对应的内存页,并存储在pages数组中的第一个位置和第二个位置。
(2)如果地址位于核心内核文本区域,使用virt_to_page()将地址转换为对应的内存页,并存储在pages数组中的第一个位置。同时,使用WARN_ON()检查该页是否为保留页。
(3)使用BUG_ON()检查pages[0]是否为空。
(4)使用local_irq_save()保存当前中断状态,并禁用中断。
(5)使用set_fixmap()将pages[0]与FIX_TEXT_POKE0进行映射,将其物理地址设置为页表项的值。
(6)如果pages[1]不为空,使用set_fixmap()将pages[1]与FIX_TEXT_POKE1进行映射,将其物理地址设置为页表项的值。
(7)将fix_to_virt()的结果赋给vaddr,即获取FIX_TEXT_POKE0映射的虚拟地址。
(8)使用memcpy()将opcode的内容复制到vaddr和addr在页内的相对偏移位置上,长度为len。
(9)使用clear_fixmap()清除FIX_TEXT_POKE0的映射。
(10)如果pages[1]不为空,使用clear_fixmap()清除FIX_TEXT_POKE1的映射。
(11)使用local_flush_tlb()刷新TLB(转换后备缓冲)以确保新的页表项生效。
(12)使用sync_core()进行核心同步,确保新指令在每个核心上都可见。
(13)使用一个循环遍历检查修改后的指令是否和原始指令一致。如果不一致,使用BUG_ON()触发错误。
(14)使用local_irq_restore()恢复之前保存的中断状态。
(15)返回被修改的地址。
该函数用于在实时内核中修改指令。它将指定地址的指令替换为新的指令,并进行了一系列的同步操作和错误检查,以确保修改的指令能够正确执行。需要注意的是,在调用text_poke()函数之前,必须在text_mutex的保护下进行,以确保互斥访问。
2.2 用途
(1)Linux jump label机制:具体可参考:Linux Static Keys和jump label机制
内核运行时修改jump label。
static_key_slow_inc/dec-->jump_label_update()-->__jump_label_update()-->arch_jump_label_transform()
void arch_jump_label_transform(struct jump_entry *entry,enum jump_label_type type)
{get_online_cpus();mutex_lock(&text_mutex);__jump_label_transform(entry, type, text_poke_smp);mutex_unlock(&text_mutex);put_online_cpus();
}
(2)kprobe机制:优化kprobe,使用jmp指令替换int3指令。
/** Replace breakpoints (int3) with relative jumps.* Caller must call with locking kprobe_mutex and text_mutex.*/
void __kprobes arch_optimize_kprobes(struct list_head *oplist)
{struct optimized_kprobe *op, *tmp;int c = 0;list_for_each_entry_safe(op, tmp, oplist, list) {WARN_ON(kprobe_disabled(&op->kp));/* Setup param */setup_optimize_kprobe(&jump_poke_params[c],jump_poke_bufs[c].buf, op);list_del_init(&op->list);if (++c >= MAX_OPTIMIZE_PROBES)break;}/** text_poke_smp doesn't support NMI/MCE code modifying.* However, since kprobes itself also doesn't support NMI/MCE* code probing, it's not a problem.*/text_poke_smp_batch(jump_poke_params, c);
}
(3)Linux Static calls机制:Linux Static calls机制
// linux-5.15/arch/x86/kernel/static_call.cstatic void __ref __static_call_transform(void *insn, enum insn_type type, void *func)
{const void *emulate = NULL;int size = CALL_INSN_SIZE;const void *code;switch (type) {case CALL:code = text_gen_insn(CALL_INSN_OPCODE, insn, func);if (func == &__static_call_return0) {emulate = code;code = &xor5rax;}break;case NOP:code = x86_nops[5];break;case JMP:code = text_gen_insn(JMP32_INSN_OPCODE, insn, func);break;case RET:code = text_gen_insn(RET_INSN_OPCODE, insn, func);size = RET_INSN_SIZE;break;}if (memcmp(insn, code, size) == 0)return;if (unlikely(system_state == SYSTEM_BOOTING))return text_poke_early(insn, code, size);text_poke_bp(insn, code, size, emulate);
}
(4)ftrace
三、text_poke_smp 内核hook
text_poke_smp 可以在内核运行时修改内存指令,因此可以用来进行内核hook。
例子请参考:
https://blog.csdn.net/bin_linux96/article/details/105776231
https://blog.csdn.net/dog250/article/details/105254739
https://blog.csdn.net/dog250/article/details/105787199
https://blog.csdn.net/dog250/article/details/84201114
https://blog.csdn.net/qq_21792169/article/details/84583275
https://richardweiyang-2.gitbook.io/kernel-exploring/00-index/04-ftrace_internal
相关文章:

Linux x86_64平台指令替换函数 text_poke_smp/bp
文章目录 前言一、text_poke_early1.1 text_poke_early简介1.2 用途 二、text_poke_smp2.1 简介2.1.1 text_poke_smp函数2.2.2 stop_machine_text_poke简介2.2.3 text_poke函数 2.2 用途 三、text_poke_smp 内核hook 前言 Linux x86_64平台指令替换函数有两种类型:…...

海南云亿商务咨询有限公司口碑怎么样?
在数字化浪潮席卷全球的今天,电商行业正以前所未有的速度发展。抖音作为短视频领域的佼佼者,其电商功能更是为众多品牌和企业打开了全新的销售渠道。海南云亿商务咨询有限公司,作为抖音电商服务领域的佼佼者,正以其专业的服务和创…...

航空数据管控系统-②项目分析与设计:任务2:使用Git或SVN管理项目(可选任务,只介绍Git安装)
任务描述 1、安装Git 2、注册GitHub 3、配置本地库 4、配置远程库 5、使用Git管理项目 任务指导 分为以下几个部分完成: 学会Git的安装,帐号注册本地存储库的管理自己创建一个项目,项目名称为自己的名字,上传到代码仓库ÿ…...

【面试题】串联探针和旁挂探针有什么区别?
在网络安全领域中,串联探针和旁挂探针(通常也被称为旁路探针)是两种不同部署方式的监控设备,它们各自具有独特的特性和应用场景。以下是它们之间的主要区别: 部署方式 串联探针:串联探针一般通过网关或者…...

LeetCode42(接雨水)[三种解法:理解动态规划,双指针,单调栈]
接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 这是一道困难题,难度确实有点层次.我们先来朴素思想走一波. 要求能接多少雨水,我们可以具化到每个硅谷,每个硅谷能存多少雨水,那么答案就是每个…...

STM32-ADC+DMA
本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. ADC模拟-数字转换器1.1 ADC模拟-数字转换器1.2 逐次逼近型ADC1.3 ADC框图1.4 ADC基本结构1.5 输入通道1.6 规则组的转换模式1.6.1 单次转换,非扫描模式1.6.2 连续转换,非扫描模式1.6.3 单次…...

代码随想录算法训练营第六十二天 | 108. 冗余连接、109. 冗余连接II、复习
108. 冗余连接 题目链接:https://kamacoder.com/problempage.php?pid1181 文档讲解:https://www.programmercarl.com/kamacoder/0108.%E5%86%97%E4%BD%99%E8%BF… 思路 从前向后遍历每一条边(因为优先让前面的边连上)࿰…...

昇思MindSpore学习笔记6-01LLM原理和实践--FCN图像语义分割
摘要: 记录MindSpore AI框架使用FCN全卷积网络理解图像进行图像语议分割的过程、步骤和方法。包括环境准备、下载数据集、数据集加载和预处理、构建网络、训练准备、模型训练、模型评估、模型推理等。 一、概念 1.语义分割 图像语义分割 semantic segmentation …...

【FFMPEG基础(一)】解码源码
学习分享 main函数decodetorgb32.h 文件decodetorgb32 .cpp文件 main函数 #include <QApplication> #include "decodetorgb32.h" int main(int argc, char *argv[]) {QApplication a(argc, argv);DecodeToRGB32 toRGB32;int restoRGB32.openVideo("../fi…...

第二证券股市资讯:深夜!突然暴涨75%!
一则重磅收买引发医药圈轰动。 北京时间7月8日晚间,美股开盘后,美国生物制药公司Morphic股价一度暴升超75%。音讯面上,生物医药巨子礼来公司官宣,将以57美元/股的价格现金收买Morphic,较上星期五的收盘价溢价79%&…...

flutter 使用wechat_assets_picker的权限检测
https://pub.dev/packages/wechat_assets_picker AssetPicker.pickAssets之前进行权限检查 pickImages() async {try {if (PermissionState.authorized ! await AssetPicker.permissionCheck()) {PermissionUtil.showAllPermissions(Permission.storage, 1);return;}final Lis…...

Mojo入门案例教程(上手篇)
以下是 Mojo 编程语言入门案例教程,内容包括 Mojo 的基本概念、变量、控制结构、函数等方面: Mojo 的基本概念 1.什么是 Mojo?:Mojo 是一种函数式编程语言,用于开发小型应用程序、脚本和工具。 2.Mojo 的特点&#x…...

如何在window执行mkfile
1、Windows cmd中出现错误:“‘make‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。”的解决方法_windows_是板栗啊-GitCode 开源社区 2、安装cmder,再通过包管理工具下载make...

Nginx 是一个非常流行的 Web 服务器和反向代理服务器
Nginx 是一个非常流行的 Web 服务器和反向代理服务器,以其高性能、稳定性、丰富的功能集和低资源消耗而闻名。下面是一个简化的 Nginx 使用教程,包括基本的安装、配置和一些常见用途。 安装 Nginx 在 Ubuntu/Debian 上安装: sudo apt upda…...

mysql怎么调整缓冲区大小
MySQL中调整缓冲区大小是数据库性能优化的重要一环。缓冲区大小直接影响了数据库的读写性能和响应速度。以下是一些常见的MySQL缓冲区及其调整方法: 一、InnoDB缓冲池(InnoDB Buffer Pool) InnoDB缓冲池是InnoDB存储引擎用来缓存表数据和索…...

计算机组成原理学习笔记(一)
计算机组成原理 [类型:: [[计算机基础课程]] ] [来源:: [[B站]] ] [主讲人:: [[咸鱼学长]] ] [评价:: ] [知识点:: [[系统软件]] & [[应用软件]] ] [简单解释:: 管理计算机系统的软件; 按照任务需要编写的程序 ] [问题:: ] [知识点:: [[机器字长]] ] [简单…...

Vue3 对跳转 同一路由传入不同参数的页面分别进行缓存
1:使用场景 从列表页跳转至不同的详情页面,对这些详情页面分别进行缓存 2:核心代码 2.1: 配置路由文件 在路由文件里对需要进行缓存的路由对象添加meta 属性 // 需要缓存的详情页面路由 { name: detail, path: /myRouter/detail…...

LinearLayout的测量流程
在日常开发中我们常常使用LinearLayout作为布局Group,本文从其源码实现出发分析测量流程。大家可以带着问题进入下面的分析流程,看看是否能找到答案。 垂直测量 View的测量入口方法是onmeasure方法。LinearLayout的onMeasure方法根据其方向而做不同的处…...

数据无忧:Ubuntu 系统迁移备份全指南
唠唠闲话 最近电脑出现了一些故障,送修期间,不得不在实验室的台式机上重装系统,配环境的过程花费了不少时间。为避免未来处理类似事情时耗费时间,特此整理一些备份策略。 先做以下准备: U盘启动盘,参考 …...

中国IDC圈探访北京•光子1号金融算力中心
今天,“AI”、“大模型”是最炙手可热的话题,全球有海量人群在工作生活中使用大模型,大模型产品涉及多模态,应用范围已涵盖电商、传媒、金融、短视频、制造等众多行业。 而回看2003年的互联网记忆, “上网”“在线”是…...

[Unity入门01] Unity基本操作
参考的傅老师的教程学了一下Unity的基础操作: [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移动:鼠标中键旋转:鼠标右键放大:鼠标滚轮飞行模式:右键WASDQEFocus模式&…...

vivado DELAY_VALUE_XPHY、DIFF_TERM
延迟_值_XPHY PORT对象上的DELAY_VALUE_XPHY属性指定要添加的延迟量 Versal XPHY逻辑接口的输入或输出路径。在的早期阶段 opt_design在重新生成高级I/O向导IP时 DELAY_VALUE_XPHY值将从PORT复制到的XPHY实例上 输入或输出路径。Vivado设计套件中存在DRCs,以确保 DE…...

C++语言相关的常见面试题目(三)
1. List底层实现原理 省流: list底层实现了一个双向循环链表。 每个元素(或节点)包含三个部分:数据域(_M_Storage)、前驱指针(_M_prev)、后继指针(_M_next)。 数据域:存储实际数据。 前驱指针:指向链表中…...

代码随想录-Day53
739. 每日温度 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。 示例 1: …...

Android 如何通过代码实时设置EditTextView光标
背景:换肤框架下,QA进行深色浅色切换说输入框光标颜色没有改变,转UI结果UI说需要修改!!!!! 本来有方法可以设置,但是 设置后未生效。重新进入该页面才生效!&a…...

202488读书笔记|《365日创意文案》——无聊的 到底是这世间, 还是自己?懂得忘却的人才能前进
202488读书笔记|《365日创意文案》——无聊的 到底是这世间, 还是自己?懂得忘却的人才能前进 1月2月3月4月5月6月7月8月9月10月11月12月 《365日创意文案》WRITES PUBLISHING,一些日常,是烟火,也是幸福的印记。 当下也…...

iperf3: error - unable to connect to server: No route to host
1.确认iperf3版本是否统一。 2.确认防火墙是否关闭。 关闭防火墙 : systemctl stop firewalld 查看防火墙状态: systemctl status firewalld 3.重新建起链接...

正则表达式中的贪心匹配
在正则表达式中,?既可以表示数量,0次或1次,等效于 {0,1},也可以跟在其它数量限定符之后,表示非贪心匹配,即匹配时匹配搜索到的尽可能短的字符串。 下面来看一个例子: T…...

线程相关概念及操作
【1】线程的概念 1.线程-->进程会得到一个内存地址,进程是资源分配的基本单位线程才是真正进程里处理数据与逻辑的东西进程---》被分配一定的资源线程---》利用进程资源处理数据与逻辑 【2】进程和线程关系: 进程与进程之间是竞争关系,竞…...

2024最新版若依-RuoYi-Vue3-PostgreSQL前后端分离项目部署手册教程
项目简介: RuoYi-Vue3-PostgreSQL 是一个基于 RuoYi-Vue3 框架并集成 PostgreSQL 数据库的项目。该项目提供了一套高效的前后端分离的开发解决方案,适用于中小型企业快速构建现代化的企业级应用。此项目结合了 RuoYi-Vue-Postgresql 和 RuoYi-Vue3 的优点࿰…...