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

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...

Linux信号保存与处理机制详解

Linux信号的保存与处理涉及多个关键机制&#xff0c;以下是详细的总结&#xff1a; 1. 信号的保存 进程描述符&#xff08;task_struct&#xff09;&#xff1a;每个进程的PCB中包含信号相关信息。 pending信号集&#xff1a;记录已到达但未处理的信号&#xff08;未决信号&a…...

Java高级 |【实验八】springboot 使用Websocket

隶属文章&#xff1a;Java高级 | &#xff08;二十二&#xff09;Java常用类库-CSDN博客 系列文章&#xff1a;Java高级 | 【实验一】Springboot安装及测试 |最新-CSDN博客 Java高级 | 【实验二】Springboot 控制器类相关注解知识-CSDN博客 Java高级 | 【实验三】Springboot 静…...

基于规则的自然语言处理

基于规则的自然语言处理 规则方法形态还原&#xff08;针对英语、德语、法语等&#xff09;中文分词切分歧义分词方法歧义字段消歧方法分词带来的问题 词性标注命名实体分类机器翻译规则方法的问题 规则方法 以规则形式表示语言知识&#xff0c;强调人对语言知识的理性整理&am…...

vue3 vite.config.js 引入bem.scss文件报错

[sass] Can’t find stylesheet to import. ╷ 1 │ use “/bem.scss” as *; │ ^^^^^^^^^^^^^^^^^^^^^^ ╵ src\App.vue 1:1 root stylesheet 分析 我们遇到了一个在Vue3项目中使用Vite时&#xff0c;在vite.config.js中引入bem.scss文件报错的问题。错误信息指出在App.vue…...

uniapp实现的简约美观的星级评分组件

采用 uniapp 实现的一款简约美观的星级评分模板&#xff0c;提供丝滑动画效果&#xff0c;用户可根据自身需求进行自定义修改、扩展&#xff0c;纯CSS、HTML实现&#xff0c;支持web、H5、微信小程序&#xff08;其他小程序请自行测试&#xff09; 可到插件市场下载尝试&#x…...