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

Linux Arm64修改页表项属性

文章目录

  • 前言
  • 一、获取pte
    • 1.1 pgd_offset
    • 1.2 pud_offset
    • 1.3 pmd_offset
    • 1.4 pte_offset_kernel
  • 二、修改pte属性
    • 2.1 set/clear_pte_bit
    • 2.2 pte_wrprotect
    • 2.3 pte_mkwrite
    • 2.4 pte_mkclean
    • 2.5 pte_mkdirty
  • 三、set_pte_at
  • 四、__flush_tlb_kernel_pgtable
  • 五、demo
  • 参考资料

前言

在这篇文章中演示了通过 update_mapping_prot 函数 来 hook 系统调用:
Linux ARM64 hook系统调用
接下来我们直接来修改对应系统调用的页表属性来hook 系统调用。

一、获取pte

pte_t *ptep_from_virt(uintptr_t addr) {pgd_t *pgdp;pud_t *pudp;pmd_t *pmdp;pte_t *ptep;struct mm_struct *mm = kallsyms_lookup_name_("init_mm");//获取内核全局页目录pgdp = pgd_offset(mm, addr);if (pgd_none(READ_ONCE(*pgdp))) {printk(KERN_INFO "failed pgdp");return 0;}pudp = pud_offset(pgdp, addr);if (pud_none(READ_ONCE(*pudp))) {printk(KERN_INFO "failed pudp");return 0;}pmdp = pmd_offset(pudp, addr);if (pmd_none(READ_ONCE(*pmdp))) {printk(KERN_INFO "failed pmdp");return 0;}ptep = pte_offset_kernel(pmdp, addr);if (!pte_valid(READ_ONCE(*ptep))) {printk(KERN_INFO "failed pte");return 0;}return ptep;
}

1.1 pgd_offset

pgd_offset用于在页表目录(page-table-directory)中查找条目(entry)

/* to find an entry in a page-table-directory */
#define pgd_index(addr)		(((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))#define pgd_offset_raw(pgd, addr)	((pgd) + pgd_index(addr))#define pgd_offset(mm, addr)	(pgd_offset_raw((mm)->pgd, (addr)))

pgd_index(addr) 宏用于计算给定地址 addr 在页表目录中的索引。它通过将地址右移 PGDIR_SHIFT 位,然后与 (PTRS_PER_PGD - 1) 进行按位与运算来计算索引值。

pgd_offset_raw(pgd, addr) 宏用于计算给定页表目录 pgd 和地址 addr 对应的页表目录项(pgd entry)的指针。它通过将 pgd 指针与 pgd_index(addr) 计算得到的索引相加来获得目录项的地址。

pgd_offset(mm, addr) 宏用于在给定的内存管理结构 mm 中计算地址 addr 对应的页表目录项的指针。它通过调用 pgd_offset_raw 宏,并传递 mm->pgd(页全局目录指针)和地址 addr 来获得目录项的指针。

如果是给定内核虚拟地址 addr 在页表目录中的索引等价于:

/** a shortcut which implies the use of the kernel's pgd, instead* of a process's*/
#define pgd_offset_k(address) pgd_offset(&init_mm, (address))

1.2 pud_offset

pud_offset用于在一级页表(first-level page table)中查找条目(entry)

/* Find an entry in the frst-level page table. */
#define pud_index(addr)		(((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))#define pud_offset_phys(dir, addr)	(pgd_page_paddr(READ_ONCE(*(dir))) + pud_index(addr) * sizeof(pud_t))#define pud_offset(dir, addr)		((pud_t *)__va(pud_offset_phys((dir), (addr))))

pud_index(addr) 宏用于计算给定地址 addr 在一级页表中的索引。它通过将地址右移 PUD_SHIFT 位,然后与 (PTRS_PER_PUD - 1) 进行按位与运算来计算索引值。

pud_offset_phys(dir, addr) 宏用于计算给定一级页表指针 dir 和地址 addr 对应的页上一级目录项(pud entry)的物理地址。它通过先读取 dir 指针指向的页全局目录项(pgd entry),然后获取其物理地址,再加上 pud_index(addr) 乘以 sizeof(pud_t) 的偏移量来计算目录项的物理地址。

pud_offset(dir, addr) 宏用于在给定的一级页表指针 dir 中计算地址 addr 对应的页上一级目录项的指针。它通过调用 pud_offset_phys 宏,并传递 dir 和地址 addr 来获得目录项的物理地址,并使用 __va 宏将其转换为虚拟地址,然后将其强制转换为 pud_t 类型的指针。

1.3 pmd_offset

pmd_offset用于在二级页表(second-level page table)中查找条目(entry)

/* Find an entry in the second-level page table. */
#define pmd_index(addr)		(((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))#define pmd_offset_phys(dir, addr)	(pud_page_paddr(READ_ONCE(*(dir))) + pmd_index(addr) * sizeof(pmd_t))#define pmd_offset(dir, addr)		((pmd_t *)__va(pmd_offset_phys((dir), (addr))))

pmd_index(addr) 宏用于计算给定地址 addr 在二级页表中的索引。它通过将地址右移 PMD_SHIFT 位,然后与 (PTRS_PER_PMD - 1) 进行按位与运算来计算索引值。

pmd_offset_phys(dir, addr) 宏用于计算给定二级页表指针 dir 和地址 addr 对应的页中一级目录项(pmd entry)的物理地址。它通过先读取 dir 指针指向的页上一级目录项(pud entry),然后获取其物理地址,再加上 pmd_index(addr) 乘以 sizeof(pmd_t) 的偏移量来计算目录项的物理地址。

pmd_offset(dir, addr) 宏用于在给定的二级页表指针 dir 中计算地址 addr 对应的页中一级目录项的指针。它通过调用 pmd_offset_phys 宏,并传递 dir 和地址 addr 来获得目录项的物理地址,并使用 __va 宏将其转换为虚拟地址,然后将其强制转换为 pmd_t 类型的指针。

1.4 pte_offset_kernel

pte_offset_kernel用于在三级页表(third-level page table)中查找条目(entry)

/* Find an entry in the third-level page table. */
#define pte_index(addr)		(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))#define pte_offset_phys(dir,addr)	(pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))#define pte_offset_kernel(dir,addr)	((pte_t *)__va(pte_offset_phys((dir), (addr))))

pte_index(addr) 宏用于计算给定地址 addr 在三级页表中的索引。它通过将地址右移 PAGE_SHIFT 位,然后与 (PTRS_PER_PTE - 1) 进行按位与运算来计算索引值。

pte_offset_phys(dir, addr) 宏用于计算给定三级页表指针 dir 和地址 addr 对应的物理页表项(pte entry)的物理地址。它通过先读取 dir 指针指向的页中一级目录项(pmd entry),然后获取其物理地址,再加上 pte_index(addr) 乘以 sizeof(pte_t) 的偏移量来计算页表项的物理地址。

pte_offset_kernel(dir, addr) 宏用于在给定的三级页表指针 dir 中计算地址 addr 对应的物理页表项的指针。它通过调用 pte_offset_phys 宏,并传递 dir 和地址 addr 来获得页表项的物理地址,并使用 __va 宏将其转换为虚拟地址,然后将其强制转换为 pte_t 类型的指针。

有一个等价的宏:

#define pte_offset_map(dir,addr)	pte_offset_kernel((dir), (addr))

二、修改pte属性

2.1 set/clear_pte_bit

(1)硬件页表项描述符

// arch/arm64/include/asm/pgtable-hwdef.h/** Level 3 descriptor (PTE).*/
#define PTE_VALID		(_AT(pteval_t, 1) << 0)
#define PTE_TYPE_MASK		(_AT(pteval_t, 3) << 0)
#define PTE_TYPE_PAGE		(_AT(pteval_t, 3) << 0)
#define PTE_TABLE_BIT		(_AT(pteval_t, 1) << 1)
#define PTE_USER		(_AT(pteval_t, 1) << 6)		/* AP[1] */
#define PTE_RDONLY		(_AT(pteval_t, 1) << 7)		/* AP[2] */
#define PTE_SHARED		(_AT(pteval_t, 3) << 8)		/* SH[1:0], inner shareable */
#define PTE_AF			(_AT(pteval_t, 1) << 10)	/* Access Flag */
#define PTE_NG			(_AT(pteval_t, 1) << 11)	/* nG */
#define PTE_DBM			(_AT(pteval_t, 1) << 51)	/* Dirty Bit Management */
#define PTE_CONT		(_AT(pteval_t, 1) << 52)	/* Contiguous range */
#define PTE_PXN			(_AT(pteval_t, 1) << 53)	/* Privileged XN */
#define PTE_UXN			(_AT(pteval_t, 1) << 54)	/* User XN */
#define PTE_HYP_XN		(_AT(pteval_t, 1) << 54)	/* HYP XN */

(2)bit55-58是硬件预留给软件使用:

// arch/arm64/include/asm/pgtable-prot.h/** Software defined PTE bits definition.*/
#define PTE_WRITE		(PTE_DBM)		 /* same as DBM (51) */
#define PTE_DIRTY		(_AT(pteval_t, 1) << 55)
#define PTE_SPECIAL		(_AT(pteval_t, 1) << 56)
#define PTE_DEVMAP		(_AT(pteval_t, 1) << 57)
#define PTE_PROT_NONE		(_AT(pteval_t, 1) << 58) /* only when !PTE_VALID */

(3)

typedef u64 pteval_t;typedef struct { pteval_t pgprot; } pgprot_t;#define pgprot_val(x)	((x).pgprot)
static inline pte_t clear_pte_bit(pte_t pte, pgprot_t prot)
{pte_val(pte) &= ~pgprot_val(prot);return pte;
}static inline pte_t set_pte_bit(pte_t pte, pgprot_t prot)
{pte_val(pte) |= pgprot_val(prot);return pte;
}

两个内联函数,用于清除和设置页表项(page table entry)中的位:

clear_pte_bit 函数用于清除页表项 pte 中通过 pgprot_t 类型的 prot 参数指定的位。它通过对 pte 的值执行按位取反操作,然后与 prot 的值执行按位与操作,最后将结果返回。

set_pte_bit 函数用于设置页表项 pte 中通过 pgprot_t 类型的 prot 参数指定的位。它通过对 pte 的值执行按位或操作,使用 prot 的值作为掩码,将指定位设置为 1,最后将结果返回。

2.2 pte_wrprotect

static inline pte_t pte_wrprotect(pte_t pte)
{pte = clear_pte_bit(pte, __pgprot(PTE_WRITE));pte = set_pte_bit(pte, __pgprot(PTE_RDONLY));return pte;
}

用于修改页表项的权限,将其设置为只读状态,以实现对页面的写保护。通过调用 clear_pte_bit 和 set_pte_bit 函数,可以方便地进行位操作,从而修改页表项的属性。

2.3 pte_mkwrite

static inline pte_t pte_mkwrite(pte_t pte)
{pte = set_pte_bit(pte, __pgprot(PTE_WRITE));pte = clear_pte_bit(pte, __pgprot(PTE_RDONLY));return pte;
}

用于修改页表项的权限,将其设置为可写状态,以实现对页面的写入操作。通过调用 set_pte_bit 和 clear_pte_bit 函数,可以方便地进行位操作,从而修改页表项的属性。

2.4 pte_mkclean

static inline pte_t pte_mkclean(pte_t pte)
{pte = clear_pte_bit(pte, __pgprot(PTE_DIRTY));pte = set_pte_bit(pte, __pgprot(PTE_RDONLY));return pte;
}

用于修改页表项的属性,将其标记为干净并设置为只读状态。通过调用 clear_pte_bit 和 set_pte_bit 函数,可以方便地进行位操作,从而修改页表项的属性。这样可以实现对页面的写保护,并将其标记为干净,以便在需要时进行页面回写操作。

2.5 pte_mkdirty

/** The following only work if pte_present(). Undefined behaviour otherwise.*/
#define pte_write(pte)		(!!(pte_val(pte) & PTE_WRITE))

宏 pte_write(pte),用于检查页表项 pte 是否允许写入操作。
宏展开后的逻辑如下:
使用 pte_val(pte) 获取页表项 pte 的值。
执行位与操作 pte_val(pte) & PTE_WRITE,其中 PTE_WRITE 是一个位掩码,用于表示写权限的位。
使用双重取反 !! 将结果转换为布尔值,将非零值转换为 true,将零值转换为 false。

static inline pte_t pte_mkdirty(pte_t pte)
{pte = set_pte_bit(pte, __pgprot(PTE_DIRTY));if (pte_write(pte))pte = clear_pte_bit(pte, __pgprot(PTE_RDONLY));return pte;
}

用于修改页表项的属性,将其标记为脏并根据需要取消只读状态。通过调用 set_pte_bit 和 clear_pte_bit 函数,可以方便地进行位操作,从而修改页表项的属性。这样可以在页面发生写入操作时标记页面为脏,并根据需要设置为可写状态。

三、set_pte_at

static inline void set_pte(pte_t *ptep, pte_t pte)
{WRITE_ONCE(*ptep, pte);/** Only if the new pte is valid and kernel, otherwise TLB maintenance* or update_mmu_cache() have the necessary barriers.*/if (pte_valid_not_user(pte)) {dsb(ishst);isb();}
}

set_pte,用于在指定地址处设置页表项(pte),该地址由页表项指针(ptep)表示。
函数的实现如下:
(1)使用 WRITE_ONCE 宏将 pte 的值写入 ptep 指向的内存位置。WRITE_ONCE 宏通常用于确保写操作不被编译器进行优化或重新排序,从而确保写操作仅执行一次。

(2)代码接着检查新的 pte 是否有效并且属于内核(而非用户级别的条目)。这通过使用 pte_valid_not_user 宏进行检查。如果条件为真,表示新的 pte 是有效的且与内核相关,则执行以下操作:
使用 dsb(ishst) 指令确保在 dsb 指令之前的所有内存访问完成后,再启动 dsb 指令之后的任何内存访问。这提供了内存访问顺序的保证。
使用 isb() 指令确保指令流同步,使得对页表项的任何更改立即生效。

static inline void set_pte_at(struct mm_struct *mm, unsigned long addr,pte_t *ptep, pte_t pte)
{if (pte_present(pte) && pte_user_exec(pte) && !pte_special(pte))__sync_icache_dcache(pte);__check_racy_pte_update(mm, ptep, pte);set_pte(ptep, pte);
}

内联函数 set_pte_at,用于在指定的地址处设置页表项,用于把pte页表项写入硬件页表。

函数的实现如下:
(1)首先,通过一系列条件判断语句检查新的页表项 pte 的属性:
使用 pte_present(pte) 判断页表项是否存在(present)。
使用 pte_user_exec(pte) 判断页表项是否允许用户执行(user exec)。
使用 pte_special(pte) 判断页表项是否是特殊类型(special)。

如果以上条件都满足,则调用 __sync_icache_dcache 函数,执行一些与页表项相关的同步操作。
(2)接着,调用 __check_racy_pte_update 函数,用于检查潜在的竞态条件(race condition)并处理页表项的更新。
(3)最后,调用 set_pte 函数,将新的页表项 pte 设置到指定地址处的页表项指针 ptep 上。

用于设置页表项,并处理一些与页表项更新相关的操作。它执行了一系列检查和同步操作,以确保页表项的正确性和一致性。

四、__flush_tlb_kernel_pgtable

/** Used to invalidate the TLB (walk caches) corresponding to intermediate page* table levels (pgd/pud/pmd).*/
static inline void __flush_tlb_kernel_pgtable(unsigned long kaddr)
{unsigned long addr = __TLBI_VADDR(kaddr, 0);dsb(ishst);__tlbi(vaae1is, addr);dsb(ish);isb();
}

内联函数 __flush_tlb_kernel_pgtable,用于刷新与中间页表级别(pgd/pud/pmd)对应的TLB(转换后备缓冲器)。

函数的实现如下:
(1)首先,根据内核地址 kaddr 使用 __TLBI_VADDR 宏计算出相应的虚拟地址 addr。

(2)使用 dsb(ishst) 指令确保在 dsb 指令之前的所有内存访问完成后,再启动 dsb 指令之后的任何内存访问。这提供了内存访问顺序的保证。

(3)调用 __tlbi 函数,使用 vaae1is 选项刷新与给定虚拟地址 addr 相关联的TLB项。这个操作将使TLB中的映射失效,需要重新进行地址转换。

(4)使用 dsb(ish) 指令确保在 dsb 指令之前的所有内存访问完成后,再启动 dsb 指令之后的任何内存访问。这提供了内存访问顺序的保证。

(5)使用 isb() 指令确保指令流同步,使得对TLB的任何更改立即生效。

这些指令(dsb、__tlbi、isb)用于刷新内核页表的TLB项,确保新的页表映射在地址转换时得到正确处理。TLB是用于加速虚拟地址到物理地址转换的高速缓存,当页表发生变化时,需要刷新TLB以保证正确的地址映射。该函数主要用于内核页表的维护和更新,以确保内存访问的一致性和正确性。

五、demo

接下来根据上面的知识来实现arm64平台下系统调用的hook:

#include <linux/init.h>
#include <linux/mm.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kallsyms.h> 
#include <linux/syscalls.h>
#include <asm/unistd.h>
#include <asm/ptrace.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>static pte_t *ptep;
static struct mm_struct *mm;static unsigned long *__sys_call_table;
static unsigned long mkdir_sys_call_addr;typedef long (*syscall_fn_t)(const struct pt_regs *regs);#ifndef __NR_mkdirat
#define __NR_mkdirat 34
#endif//用于保存原始的 mkdir 系统调用
static syscall_fn_t orig_mkdir;asmlinkage long mkdir_hook(const struct pt_regs *regs)
{printk("hook mkdir sys_call\n");//return orig_mkdir(regs);return 0;
}static void set_pte_write(void)
{pte_t pte;pte = READ_ONCE(*ptep);//清除pte的可读属性位//设置pte的可写属性位pte = pte_mkwrite(pte);//把pte页表项写入硬件页表钟set_pte_at(mm, mkdir_sys_call_addr, ptep, pte);//页表更新 和 TLB 刷新之间保持正确的映射关系//为了保持一致性,必须确保页表的更新和 TLB 的刷新是同步的__flush_tlb_kernel_pgtable(mkdir_sys_call_addr);}static void set_pte_rdonly(void)
{pte_t pte;pte = READ_ONCE(*ptep);//清除pte的可写属性位//设置pte的可读属性位pte = pte_wrprotect(pte);set_pte_at(mm, mkdir_sys_call_addr, ptep, pte);__flush_tlb_kernel_pgtable(mkdir_sys_call_addr);}//内核模块初始化函数
static int __init lkm_init(void)
{pgd_t *pgdp;pud_t *pudp;pmd_t *pmdp;/* can be directly found in kernel memory */mm = (struct mm_struct *)kallsyms_lookup_name("init_mm");if(mm == NULL)return -1;__sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");if (!__sys_call_table)return -1;mkdir_sys_call_addr = (unsigned long)(__sys_call_table + __NR_mkdirat);pgdp = pgd_offset(mm, mkdir_sys_call_addr);if (pgd_none(READ_ONCE(*pgdp))) {printk(KERN_INFO "failed pgdp");return 0;}pudp = pud_offset(pgdp, mkdir_sys_call_addr);if (pud_none(READ_ONCE(*pudp))) {printk(KERN_INFO "failed pudp");return 0;}pmdp = pmd_offset(pudp, mkdir_sys_call_addr);if (pmd_none(READ_ONCE(*pmdp))) {printk(KERN_INFO "failed pmdp");return 0;}ptep = pte_offset_kernel(pmdp, mkdir_sys_call_addr);if (!pte_valid(READ_ONCE(*ptep))) {printk(KERN_INFO "failed pte");return 0;}//保存原始的系统调用:mkdirorig_mkdir = (syscall_fn_t)__sys_call_table[__NR_mkdirat];set_pte_write();__sys_call_table[__NR_mkdirat] = (unsigned long)mkdir_hook;set_pte_rdonly();printk("lkm_init\n");return 0;
}//内核模块退出函数
static void __exit lkm_exit(void)
{set_pte_write();__sys_call_table[__NR_mkdirat] = (unsigned long)orig_mkdir;set_pte_rdonly();printk("lkm_exit\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");

参考资料

Linux 5.4.18

相关文章:

Linux Arm64修改页表项属性

文章目录 前言一、获取pte1.1 pgd_offset1.2 pud_offset1.3 pmd_offset1.4 pte_offset_kernel 二、修改pte属性2.1 set/clear_pte_bit2.2 pte_wrprotect2.3 pte_mkwrite2.4 pte_mkclean2.5 pte_mkdirty 三、set_pte_at四、__flush_tlb_kernel_pgtable五、demo参考资料 前言 在…...

elasticsearch14-高亮

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…...

HUAWEI华为MateBook X Pro 2021款 i7 集显(MACHD-WFE9Q)原装出厂Win10系统20H2

华为笔记本电脑原厂系统自带指纹驱动、显卡驱动、声卡驱动、网卡驱动等所有驱动、出厂主题壁纸、系统属性华为专属LOGO标志、Office办公软件、华为电脑管家等预装程序 链接&#xff1a;https://pan.baidu.com/s/1oeSM0ciwyyRIKms5tR4SNA?pwdo2gq 提取码&#xff1a;o2gq...

21天学会C++:Day9----初识类与对象

CSDN的uu们&#xff0c;大家好。这里是C入门的第九讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;C专题 目录 1. 面向过程与面向对象 2. 类的定义 3. 类中的访问限定符 3.1 访问限定符的…...

【深度学习】 Python 和 NumPy 系列教程(十七):Matplotlib详解:2、3d绘图类型(3)3D条形图(3D Bar Plot)

目录 一、前言 二、实验环境 三、Matplotlib详解 1、2d绘图类型 2、3d绘图类型 0. 设置中文字体 1. 线框图 2. 3D散点图 3. 3D条形图&#xff08;3D Bar Plot&#xff09; 一、前言 Python是一种高级编程语言&#xff0c;由Guido van Rossum于1991年创建。它以简洁、易读…...

基于Spring Boot+vue的酒店管理系统

文章目录 项目介绍主要功能截图:前台后台部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于Spring Boot+vue的酒店管理…...

Python 通过threading模块实现多线程

视频版教程 Python3零基础7天入门实战视频教程 我们可以使用threading模块的Thread类的构造器来创建线程 def _ init _(self, groupNone, targetNone, nameNone, args(), kwargsNone, *, daemonNone): 上面的构造器涉及如下几个参数。 group:指定该线程所属的线程组。目前该…...

用一个RecyclerView实现二级评论

先上个效果图&#xff08;没有UI&#xff0c;将就看吧&#xff09;&#xff0c;写代码的整个过程花了4个小时左右&#xff0c;相比当初自己开发需求已经快了很多了哈。 给产品估个两天时间&#xff0c;摸一天半的鱼不过分吧&#xff08;手动斜眼&#xff09; 需求拆分 这种大家…...

音视频 SDL简介

一、SDL简介 SDL&#xff08;Simple DirectMedia Layer&#xff09;是一套开放源代码的跨平台多媒体开发库&#xff0c;使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数&#xff0c;让开发者只要用相同或是相似的代码就可以开发出跨多个平台&#xff08;Linux、Win…...

7.前端·新建子模块与开发(自动生成)

文章目录 学习地址视频笔记自动代码生成模式开发增删改查功能调试功能权限分配 脚本实现权限分配 学习地址 https://www.bilibili.com/video/BV13g411Y7GS/?p15&spm_id_frompageDriver&vd_sourceed09a620bf87401694f763818a31c91e 视频笔记 自动代码生成模式开发 …...

Linux 创建目录

语法&#xff1a;mkdir xxx Linux路径 在当前目录下创建文件夹 在/目录下创建文件夹 如果想要一次性创建多个层级的目录&#xff0c;如下图 会报错&#xff0c;因为上级目录test并不存在&#xff0c;所以无法创建test目录 可以通过-p选项&#xff0c;将一整个链条都创建完成…...

【DIY小记】修复Win10启动出现蓝屏0xc0000185错误的一些方法

近些日子想到自己尘封已久的笔记本电脑没有开机了&#xff0c;很多软件驱动之类的没有更新&#xff0c;就打算把电脑开起来做一轮批量升级。但开电脑的时候很久没有进入Win10桌面&#xff0c;等了很长一段时间蓝屏提示0xc0000185错误&#xff0c;说系统需要恢复。经历了一番折腾…...

Linux 下的 10 个 PDF 软件

本文[1]是我们正在进行的有关 Linux 顶级工具系列的延续&#xff0c;在本系列中&#xff0c;我们将向您介绍最著名的 Linux 系统开源工具。 随着互联网上越来越多地使用可移植文档格式 (PDF) 文件来获取在线书籍和其他相关文档&#xff0c;拥有 PDF 查看器/阅读器对于桌面 Linu…...

浅谈redis分布式锁

浅谈redis分布式锁 分布式锁介绍 分布式锁&#xff0c;顾名思义&#xff0c;分布式系统中的锁&#xff0c;当多个进程不在同一个系统中时&#xff0c;用分布式锁控制各个进程对共享资源的访问&#xff0c;通过互斥来保持一致性。 使用场景&#xff1a;电商中某商品的秒杀活动…...

【Python保姆级教程】List容器

文章目录 前言一、列表是什么二、列表的定义2.1 有初始值2.2 空列表使用方括号创建空列表使用list()函数创建空列表 三、list列表常用操作3.1 添加元素3.2 删除元素3.3 修改元素3.4 列表长度 四、遍历操作4.1 使用for循环4.2 使用while循环和索引 总结 前言 Python是一种广泛使…...

微服务保护-授权规则

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…...

v-if失效原因

一般v-if失效都是和绑定变量有关&#xff0c;我所知道的一般有两种 1.绑定的变量为String类型或者其他类型 就是返回的变量类型与所需要的布尔类型不匹配。 <template><div><div id"container" ref"container" v-iftype></div>&l…...

Chrome 基于 Wappalyzer 查看网站所用的前端技术栈

1. 找到谷歌商店 https://chrome.google.com/webstore/search/wappalyzer?utm_sourceext_app_menu 2. 搜索 Wappalyzer 3. 添加至Chrome 4. 使用 插件 比如打开 https://www.bilibili.com/ 就可以看到其所以用的前端技术栈了...

python的装饰器

作用:在不改变原来函数的代码情况下,进行修改,或者增加函数的功能装饰器本质上就是一个闭包雏形:def wrapper(fn): wrapper: 装饰器 , fn: 目标函数def inner():# 在目标函数执行前的一些动作fn()# 在目标函数执行后的一些动作return inner #千万别加(),这里是返回一…...

P2P协议的传输艺术

TP 采用两个 TCP 连接来传输一个文件。 控制连接&#xff1a;服务器以被动的方式&#xff0c;打开众所周知用于 FTP 的端口 21&#xff0c;客户端则主动发起连接。该连接将命令从客户端传给服务器&#xff0c;并传回服务器的应答。常用的命令有&#xff1a;list——获取文件目…...

Unity资源提取原理与uTinyRipper实战指南

1. 为什么你第一次打开uTinyRipper时会“卡在加载界面”——这不是软件坏了&#xff0c;是Unity资源结构在对你说话 “零基础入门&#xff1a;uTinyRipper Unity资产提取完全指南”这个标题里藏着一个被绝大多数新手忽略的关键前提&#xff1a; uTinyRipper不是万能解包器&…...

2026年AI论文平台盘点:12款神器助你高效完成选题大纲、撰稿和降重

随着 AI 技术的持续突破&#xff0c;2026 年的论文写作工具市场已迈入“智能化、精细化、合规化”的新阶段。从本科生的课程论文到研究生的学位论文&#xff0c;再到科研人员的期刊投稿&#xff0c;AI 工具正以前所未有的专业度覆盖各类学术场景。无论是选题构思、文献检索、初…...

Go Web中间件机制深度剖析与实战

Go Web中间件机制深度剖析与实战 引言 中间件&#xff08;Middleware&#xff09;是Web开发中的核心概念&#xff0c;它在请求处理链路中扮演着至关重要的角色。本文将深入探讨Go语言中中间件的实现机制&#xff0c;并通过实战案例展示如何构建可复用的中间件系统。 一、中间件…...

嵌入式Linux驱动移植:基于MAX31865与PT100的高精度温度采集方案

1. 项目概述与核心思路最近在做一个工业边缘计算网关的项目&#xff0c;需要高精度地监测几个关键节点的温度&#xff0c;精度要求至少达到0.5℃。市面上常见的DS18B20这类数字温度传感器&#xff0c;在精度和抗干扰能力上有点力不从心。于是&#xff0c;我把目光投向了铂电阻温…...

端侧AI与嵌入式系统融合:从模型轻量化到5G通信的产业化落地

1. 从展会看趋势&#xff1a;端侧AI与嵌入式系统的深度融合最近在德国纽伦堡举办的国际嵌入式展览会&#xff0c;可以说是全球嵌入式技术发展的风向标。作为从业者&#xff0c;我每年都会关注这个展会&#xff0c;因为它总能揭示未来几年工业和技术应用的核心走向。今年&#x…...

关于我尝试写博客这档事

一、起因 在学习过数据结构后&#xff0c;希望更改目前记笔记的形式&#xff0c;于是想到整理成文章&#xff0c;通过开源方式锻炼表达力与技术理解力&#xff0c;希望复习与拓展所学习过的知识&#xff0c;使用费曼学习法学习 二、自我介绍 1.基本信息 博主名为Doubletful(Dou…...

NotebookLM显著性判断失效真相:92%用户忽略的3个统计学前提及实时校验脚本

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;NotebookLM显著性判断失效的典型现象与影响评估 NotebookLM 在处理多源异构文档时&#xff0c;其内置的“显著性判断”模块&#xff08;Significance Scorer&#xff09;常因语义稀疏、上下文截断或引用锚点偏…...

多智能体路由:从场景定义到Agent解析的工程实践

大家好&#xff0c;我是程序员小策。 场景&#xff1a;你正在做一个 AI 面试系统。产品经理说&#xff1a;“我们不光要一个通用聊天机器人&#xff0c;还要一个能自动出题、能给用户答案打分、还能分析用户表情神态的面试官。” 你一拍脑袋&#xff1a;行&#xff0c;不就是…...

CLIPDraw手绘生成:用文本控制矢量线条的AI绘画新范式

1. 项目概述&#xff1a;当文字真的能“画”出你心里的那幅画“Text-to-Drawing Synthesis With Artistic Control”——这个标题乍看像一句学术论文的副标题&#xff0c;但拆开来看&#xff0c;它直指一个正在快速落地的创作现实&#xff1a;用一句话描述&#xff0c;就能生成…...

单物体最优抓取轨迹生成

基于 3D 位姿规划直线平滑抓取轨迹&#xff0c;包含趋近 - 抓取 - 复位三段最优运动路径&#xff0c;适配机械臂点位运动核心规划逻辑基准位&#xff1a;机械臂初始安全待机点趋近段&#xff1a;直线匀速靠近物体上方预备抓取点抓取段&#xff1a;垂直下落至物体抓取中心位姿抬…...