当前位置: 首页 > news >正文

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平台指令替换函数有两种类型&#xff1a;…...

海南云亿商务咨询有限公司口碑怎么样?

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

航空数据管控系统-②项目分析与设计:任务2:使用Git或SVN管理项目(可选任务,只介绍Git安装)

任务描述 1、安装Git 2、注册GitHub 3、配置本地库 4、配置远程库 5、使用Git管理项目 任务指导 分为以下几个部分完成&#xff1a; 学会Git的安装&#xff0c;帐号注册本地存储库的管理自己创建一个项目&#xff0c;项目名称为自己的名字&#xff0c;上传到代码仓库&#xff…...

【面试题】串联探针和旁挂探针有什么区别?

在网络安全领域中&#xff0c;串联探针和旁挂探针&#xff08;通常也被称为旁路探针&#xff09;是两种不同部署方式的监控设备&#xff0c;它们各自具有独特的特性和应用场景。以下是它们之间的主要区别&#xff1a; 部署方式 串联探针&#xff1a;串联探针一般通过网关或者…...

LeetCode42(接雨水)[三种解法:理解动态规划,双指针,单调栈]

接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 这是一道困难题,难度确实有点层次.我们先来朴素思想走一波. 要求能接多少雨水,我们可以具化到每个硅谷,每个硅谷能存多少雨水,那么答案就是每个…...

STM32-ADC+DMA

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

代码随想录算法训练营第六十二天 | 108. 冗余连接、109. 冗余连接II、复习

108. 冗余连接 题目链接&#xff1a;https://kamacoder.com/problempage.php?pid1181 文档讲解&#xff1a;https://www.programmercarl.com/kamacoder/0108.%E5%86%97%E4%BD%99%E8%BF… 思路 从前向后遍历每一条边&#xff08;因为优先让前面的边连上&#xff09;&#xff0…...

昇思MindSpore学习笔记6-01LLM原理和实践--FCN图像语义分割

摘要&#xff1a; 记录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日晚间&#xff0c;美股开盘后&#xff0c;美国生物制药公司Morphic股价一度暴升超75%。音讯面上&#xff0c;生物医药巨子礼来公司官宣&#xff0c;将以57美元/股的价格现金收买Morphic&#xff0c;较上星期五的收盘价溢价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 编程语言入门案例教程&#xff0c;内容包括 Mojo 的基本概念、变量、控制结构、函数等方面&#xff1a; Mojo 的基本概念 1.什么是 Mojo&#xff1f;&#xff1a;Mojo 是一种函数式编程语言&#xff0c;用于开发小型应用程序、脚本和工具。 2.Mojo 的特点&#x…...

如何在window执行mkfile

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

Nginx 是一个非常流行的 Web 服务器和反向代理服务器

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

mysql怎么调整缓冲区大小

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

计算机组成原理学习笔记(一)

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

Vue3 对跳转 同一路由传入不同参数的页面分别进行缓存

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

LinearLayout的测量流程

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

数据无忧:Ubuntu 系统迁移备份全指南

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

中国IDC圈探访北京•光子1号金融算力中心

今天&#xff0c;“AI”、“大模型”是最炙手可热的话题&#xff0c;全球有海量人群在工作生活中使用大模型&#xff0c;大模型产品涉及多模态&#xff0c;应用范围已涵盖电商、传媒、金融、短视频、制造等众多行业。 而回看2003年的互联网记忆&#xff0c; “上网”“在线”是…...

如何免费快速备份你的QQ空间记忆:GetQzonehistory完整指南

如何免费快速备份你的QQ空间记忆&#xff1a;GetQzonehistory完整指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾经担心过QQ空间里的那些珍贵回忆会随着时间流逝而消失&am…...

AI绘画模型训练完全指南:3大核心优势与零代码实践

AI绘画模型训练完全指南&#xff1a;3大核心优势与零代码实践 【免费下载链接】sd-trainer 项目地址: https://gitcode.com/gh_mirrors/sd/sd-trainer Stable Diffusion训练技术已成为AI绘画领域的核心能力&#xff0c;但传统训练流程复杂、配置繁琐&#xff0c;让许多…...

KingbaseES V008R006C008B0014物理备份实战:sys_rman从配置到自动化的完整避坑指南

KingbaseES物理备份实战&#xff1a;从sys_rman配置到自动化运维的深度解析 凌晨三点&#xff0c;数据库告警铃声突然响起——某核心业务系统的KingbaseES实例因磁盘故障导致数据丢失。此时&#xff0c;一个配置得当的sys_rman物理备份系统将成为最后的救命稻草。不同于简单的操…...

基于YOLOv8深度学习的花卉识别检测系统(YOLOv8+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 随着计算机视觉技术的快速发展&#xff0c;基于深度学习的图像识别技术在植物分类与识别领域展现出巨大的应用潜力。本系统基于先进的YOLOv8目标检测算法&#xff0c;构建了一个高效准确的花卉识别检测系统&#xff0c;能够实现对13种不同花卉的实时检测与识别。…...

飞腾FT2000/4外部中断开发避坑指南:如何高效处理16个中断信号

飞腾FT2000/4外部中断开发避坑指南&#xff1a;如何高效处理16个中断信号 在嵌入式系统开发中&#xff0c;中断处理机制的设计往往直接决定了系统的实时性和可靠性。飞腾FT2000/4处理器作为国产高性能芯片的代表&#xff0c;其外部中断功能在实际应用中展现出独特优势&#xff…...

TFT LCD屏幕硬件解析:从XPT2046触摸屏到背光控制的完整指南

TFT LCD屏幕硬件解析&#xff1a;从XPT2046触摸屏到背光控制的完整指南 在工业控制面板和医疗设备显示屏等专业领域&#xff0c;TFT LCD屏幕凭借其高精度显示和可靠触控性能成为首选方案。不同于消费级产品的通用设计&#xff0c;专业场景下的屏幕需要工程师深入理解从触摸采样…...

SDXL-Turbo实战教程:从A futuristic car到motorcycle的删改逻辑教学

SDXL-Turbo实战教程&#xff1a;从A futuristic car到motorcycle的删改逻辑教学 获取更多AI镜像 想探索更多AI镜像和应用场景&#xff1f;访问 CSDN星图镜像广场&#xff0c;提供丰富的预置镜像&#xff0c;覆盖大模型推理、图像生成、视频生成、模型微调等多个领域&#xff0c…...

终极指南:Google Maps Python客户端错误处理与异常类型完全解析

终极指南&#xff1a;Google Maps Python客户端错误处理与异常类型完全解析 【免费下载链接】google-maps-services-python Python client library for Google Maps API Web Services 项目地址: https://gitcode.com/gh_mirrors/go/google-maps-services-python 在Pytho…...

如何快速实现Tale博客系统国际化:多语言博客搭建完整指南

如何快速实现Tale博客系统国际化&#xff1a;多语言博客搭建完整指南 【免费下载链接】tale &#x1f984; Best beautiful java blog, worth a try 项目地址: https://gitcode.com/gh_mirrors/ta/tale Tale博客系统是一款优雅的Java博客程序&#xff0c;提供了强大的内…...

RMBG-1.4开源模型解析:AI净界如何实现SOTA级Alpha通道生成

RMBG-1.4开源模型解析&#xff1a;AI净界如何实现SOTA级Alpha通道生成 你有没有遇到过这样的烦恼&#xff1f;想给产品换个背景&#xff0c;结果抠出来的图边缘全是锯齿&#xff1b;想给自己做一张透明背景的证件照&#xff0c;头发丝却和背景糊在一起&#xff1b;或者想用AI生…...