当前位置: 首页 > 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——获取文件目…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)

题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

职坐标物联网全栈开发全流程解析

物联网全栈开发涵盖从物理设备到上层应用的完整技术链路&#xff0c;其核心流程可归纳为四大模块&#xff1a;感知层数据采集、网络层协议交互、平台层资源管理及应用层功能实现。每个模块的技术选型与实现方式直接影响系统性能与扩展性&#xff0c;例如传感器选型需平衡精度与…...

02-性能方案设计

需求分析与测试设计 根据具体的性能测试需求&#xff0c;确定测试类型&#xff0c;以及压测的模块(web/mysql/redis/系统整体)前期要与相关人员充分沟通&#xff0c;初步确定压测方案及具体的性能指标QA完成性能测试设计后&#xff0c;需产出测试方案文档发送邮件到项目组&…...