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

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...