Linux arm64 set_memory_ro/rw函数
文章目录
- 一、函数简介
- 1.1 简介
- 1.2 change_memory_common
- 1.3 __change_memory_common
- 二、apply_to_page_range函数
- 2.1 apply_to_page_range
- 2.2 apply_to_p4d_range
- 2.3 apply_to_pud_range
- 2.4 apply_to_pmd_range
- 2.5 apply_to_pte_range
- 三、hook系统调用
- 参考资料
一、函数简介
1.1 简介
// linux-5.4.18/arch/arm64/mm/pageattr.cint set_memory_ro(unsigned long addr, int numpages)
{return change_memory_common(addr, numpages,__pgprot(PTE_RDONLY),__pgprot(PTE_WRITE));
}int set_memory_rw(unsigned long addr, int numpages)
{return change_memory_common(addr, numpages,__pgprot(PTE_WRITE),__pgprot(PTE_RDONLY));
}
arm64架构下 set_memory_ro 和 set_memory_rw 用来更改内核内存区域的可读可写属性,只用于更改由 vmalloc 和 vmap分配的内存区间。 两者都是调用 change_memory_common 函数。
1.2 change_memory_common
static int change_memory_common(unsigned long addr, int numpages,pgprot_t set_mask, pgprot_t clear_mask)
{unsigned long start = addr;//计算要更改内存地址的大小unsigned long size = PAGE_SIZE*numpages;unsigned long end = start + size;struct vm_struct *area;int i;//检查addr是否未对齐到页面边界。如果未对齐,则将start地址调整为前一个页面边界,并相应地更新endif (!PAGE_ALIGNED(addr)) {start &= PAGE_MASK;end = start + size;WARN_ON_ONCE(1);}/** Kernel VA mappings are always live, and splitting live section* mappings into page mappings may cause TLB conflicts. This means* we have to ensure that changing the permission bits of the range* we are operating on does not result in such splitting.** Let's restrict ourselves to mappings created by vmalloc (or vmap).* Those are guaranteed to consist entirely of page mappings, and* splitting is never needed.** So check whether the [addr, addr + size) interval is entirely* covered by precisely one VM area that has the VM_ALLOC flag set.*///来查找覆盖指定地址范围的虚拟内存区域area,area由vmalloc or vmap 分配area = find_vm_area((void *)addr);//如果找不到这样的区域,或者范围的结束地址超过了区域的边界,或者该区域没有设置VM_ALLOC标志,函数将返回错误代码-EINVALif (!area ||end > (unsigned long)area->addr + area->size ||!(area->flags & VM_ALLOC))return -EINVAL;if (!numpages)return 0;/** If we are manipulating read-only permissions, apply the same* change to the linear mapping of the pages that back this VM area.*///我手中机器配置没有rodata_full标志if (rodata_full && (pgprot_val(set_mask) == PTE_RDONLY ||pgprot_val(clear_mask) == PTE_RDONLY)) {for (i = 0; i < area->nr_pages; i++) {__change_memory_common((u64)page_address(area->pages[i]),PAGE_SIZE, set_mask, clear_mask);}}/** Get rid of potentially aliasing lazily unmapped vm areas that may* have permissions set that deviate from the ones we are setting here.*///以删除任何可能存在的与当前函数中设置的权限不同的潜在别名延迟取消映射的VM区域vm_unmap_aliases();//调用__change_memory_common,传入调整后的start地址、大小和set_mask、clear_mask,以对指定的内存范围应用权限更改return __change_memory_common(start, size, set_mask, clear_mask);
}
change_memory_common函数是一个用于更改指定内存页面范围权限。
函数change_memory_common接受四个参数:
addr:内存范围的起始地址。
numpages:内存范围中的页面数量。
set_mask:要设置的保护掩码,表示要设置的权限位。
clear_mask:要清除的保护掩码,表示要清除的权限位。
函数首先根据地址addr计算起始地址start和结束地址end,并计算需要更改权限的内存大小size。如果起始地址addr不是页对齐的,会将start调整为页对齐,并重新计算end。同时,会发出一个警告(WARN_ON_ONCE(1))。
接下来,函数检查要操作的内存范围是否完全位于一个由vmalloc(或vmap)创建的虚拟内存区域中。函数调用find_vm_area来查找覆盖指定地址范围的虚拟内存区域。如果找不到这样的区域,或者范围的结束地址超过了区域的边界,或者该区域没有设置VM_ALLOC标志,函数将返回错误代码-EINVAL。这个检查确保指定范围完全被一个具有VM_ALLOC标志的VM区域覆盖,该标志表示该区域完全由页面映射组成,不需要进行分割。
如果numpages为0,表示没有需要更改权限的页,直接返回0。
如果设置了rodata_full标志,并且set_mask或clear_mask指示了只读权限(PTE_RDONLY),函数将遍历VM区域的页面,并调用__change_memory_common,将相同的权限更改应用于页面的线性映射。这一步确保相应的线性映射权限与VM区域权限保持一致。
我手中机器配置没有rodata_full标志:
# CONFIG_RODATA_FULL_DEFAULT_ENABLED is not set
bool rodata_full __ro_after_init = IS_ENABLED(CONFIG_RODATA_FULL_DEFAULT_ENABLED);
在对指定的内存范围应用权限更改之前,函数调用vm_unmap_aliases,以删除任何可能存在的与当前函数中设置的权限不同的潜在别名延迟取消映射的VM区域。
最后,函数调用__change_memory_common,传入调整后的start地址、大小和set_mask、clear_mask,以对指定的内存范围应用权限更改。
1.3 __change_memory_common
/** This function assumes that the range is mapped with PAGE_SIZE pages.*/
static int __change_memory_common(unsigned long start, unsigned long size,pgprot_t set_mask, pgprot_t clear_mask)
{//page_change_data结构体用于存储设置和清除的保护掩码struct page_change_data data;int ret;data.set_mask = set_mask;data.clear_mask = clear_mask;ret = apply_to_page_range(&init_mm, start, size, change_page_range,&data);flush_tlb_kernel_range(start, start + size);return ret;
}
__change_memory_common的函数,用于更改指定内存页面范围的内存权限。
函数__change_memory_common接受四个参数:
start:内存范围的起始地址。
size:内存范围的大小(以字节为单位)。
set_mask:要设置的保护掩码,表示要设置的权限位。
clear_mask:要清除的保护掩码,表示要清除的权限位。
函数调用apply_to_page_range,对init_mm(内核初始化的内存描述符)中指定的内存范围应用给定的函数change_page_range。change_page_range函数被用于处理页范围内的每个页面,并根据data中的保护掩码设置和清除页面的权限。apply_to_page_range函数返回一个整数值,表示操作的结果。
struct page_change_data {pgprot_t set_mask;pgprot_t clear_mask;
};static int change_page_range(pte_t *ptep, unsigned long addr, void *data)
{struct page_change_data *cdata = data;pte_t pte = READ_ONCE(*ptep);pte = clear_pte_bit(pte, cdata->clear_mask);pte = set_pte_bit(pte, cdata->set_mask);set_pte(ptep, pte);return 0;
}
名为change_page_range的函数,作为apply_to_page_range函数的参数,函数使用clear_pte_bit宏和set_pte_bit宏分别对pte进行清除和设置操作,根据cdata中的保护掩码指定的权限位。这些宏用于修改页表项的权限位,以实现更改页面权限的目的。
函数调用flush_tlb_kernel_range来刷新内核页表中指定范围的TLB(转换查找缓冲器)。TLB是用于加速虚拟地址到物理地址转换的硬件缓存。刷新TLB确保最新的内存权限设置生效。
二、apply_to_page_range函数
2.1 apply_to_page_range
// linux-5.4.18/mm/memory.c/** Scan a region of virtual memory, filling in page tables as necessary* and calling a provided function on each leaf page table.*/
int apply_to_page_range(struct mm_struct *mm, unsigned long addr,unsigned long size, pte_fn_t fn, void *data)
{pgd_t *pgd;unsigned long next;unsigned long end = addr + size;int err;if (WARN_ON(addr >= end))return -EINVAL;pgd = pgd_offset(mm, addr);do {next = pgd_addr_end(addr, end);err = apply_to_p4d_range(mm, pgd, addr, next, fn, data);if (err)break;} while (pgd++, addr = next, addr != end);return err;
}
EXPORT_SYMBOL_GPL(apply_to_page_range);
pply_to_page_range的函数,用于扫描虚拟内存的一个区域,并在需要时填充页表,同时在每个叶子页表上调用提供的函数。在这里我们提供的函数是change_page_range,修改page属性。
函数apply_to_page_range接受五个参数:
mm:指向mm_struct结构体的指针,表示进程的内存描述符。 -- 这里传递的是 init_mm
addr:内存范围的起始地址。
size:内存范围的大小(以字节为单位)。
fn:指向函数的指针,该函数将在每个叶子页表上调用。 -- 这里传递的是change_page_range
data:传递给fn函数的数据。
(1)通过调用pgd_offset函数,根据给定的进程内存描述符mm和起始地址addr获取页全局目录项(PGD)的指针,并将其存储在pgd中。
struct mm_struct init_mm = {.pgd = swapper_pg_dir,
};
这里页全局目录项(PGD)的指针就是指内核页表页全局目录项swapper_pg_dir。
(2)使用一个循环来遍历地址范围内的每个页全局目录项。在每次迭代中,通过调用pgd_addr_end函数计算下一个要处理的地址next,根据当前的addr和end。
/** When walking page tables, get the address of the next boundary,* or the end address of the range if that comes earlier. Although no* vma end wraps to 0, rounded up __boundary may wrap to 0 throughout.*/#define pgd_addr_end(addr, end) \
({ unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK; \(__boundary - 1 < (end) - 1)? __boundary: (end); \
})
该宏用于计算页全局目录项(PGD)的下一个边界地址。它接受两个参数,addr表示当前地址,end表示范围的结束地址。宏的工作如下:
首先,它将当前地址addr加上页全局目录的大小(PGDIR_SIZE),然后与页全局目录的掩码(PGDIR_MASK)进行按位与操作,得到一个边界地址__boundary。
接下来,宏检查(__boundary - 1 < (end) - 1)是否为真。这里通过使用减一操作来避免了可能发生的边界溢出问题。
如果条件为真,则返回__boundary,否则返回end。
(4)然后,调用apply_to_p4d_range函数,传递进程内存描述符mm、当前的页全局目录项pgd、当前地址addr、下一个地址next、提供的函数fn和数据data。该函数的作用是在页全局目录项范围内递归地处理页中间目录项(P4D)。
2.2 apply_to_p4d_range
static int apply_to_p4d_range(struct mm_struct *mm, pgd_t *pgd,unsigned long addr, unsigned long end,pte_fn_t fn, void *data)
{p4d_t *p4d;unsigned long next;int err;p4d = p4d_alloc(mm, pgd, addr);if (!p4d)return -ENOMEM;do {next = p4d_addr_end(addr, end);err = apply_to_pud_range(mm, p4d, addr, next, fn, data);if (err)break;} while (p4d++, addr = next, addr != end);return err;
}
arm64最大为四级页表,p4d一般只有x86_64架构下才有,这里不予讨论。
2.3 apply_to_pud_range
static int apply_to_pud_range(struct mm_struct *mm, p4d_t *p4d,unsigned long addr, unsigned long end,pte_fn_t fn, void *data)
{pud_t *pud;unsigned long next;int err;pud = pud_alloc(mm, p4d, addr);if (!pud)return -ENOMEM;do {next = pud_addr_end(addr, end);err = apply_to_pmd_range(mm, pud, addr, next, fn, data);if (err)break;} while (pud++, addr = next, addr != end);return err;
}
函数的执行流程如下:
首先,通过调用pud_alloc函数为给定的P4D分配内存空间,并将返回的PUD指针赋值给pud变量。
进入一个循环,该循环将在PUD范围内进行迭代处理。
在循环的每一次迭代中,首先调用pud_addr_end宏来计算下一个边界地址next。这个宏将根据当前地址addr和范围结束地址end计算出下一个PMD(Page Middle Directory)的边界地址。
然后,调用apply_to_pmd_range函数来对PMD范围内的页面应用函数fn。该函数将处理mm、pud、addr和next之间的页面,并使用fn函数进行处理。
如果没有出现错误,继续进行下一次迭代。在每次迭代中,通过递增pud指针、更新addr为next,并检查addr是否等于end来确定是否继续循环。
当addr等于end时,表示已经处理完整个范围。
这段代码的作用是在给定的PUD范围内对页面应用函数进行处理,通过逐个PMD范围进行迭代,并在每个PMD范围内调用给定的函数进行处理。
2.4 apply_to_pmd_range
static int apply_to_pmd_range(struct mm_struct *mm, pud_t *pud,unsigned long addr, unsigned long end,pte_fn_t fn, void *data)
{pmd_t *pmd;unsigned long next;int err;BUG_ON(pud_huge(*pud));pmd = pmd_alloc(mm, pud, addr);if (!pmd)return -ENOMEM;do {next = pmd_addr_end(addr, end);err = apply_to_pte_range(mm, pmd, addr, next, fn, data);if (err)break;} while (pmd++, addr = next, addr != end);return err;
}
函数的执行流程如下:
首先,使用BUG_ON宏检查给定的PUD是否是巨页(huge page)。如果是巨页,会引发一个bug检查(bug-on),表示代码中出现了不应该出现的情况。
调用pmd_alloc函数为给定的PUD分配一个PMD,并将返回的PMD指针赋值给pmd变量。如果内存分配失败,函数将返回错误码-ENOMEM。
进入一个循环,该循环将在PMD范围内进行迭代处理。
在循环的每一次迭代中,首先调用pmd_addr_end宏来计算下一个边界地址next。这个宏将根据当前地址addr和范围结束地址end计算出下一个PTE(Page Table Entry)的边界地址。
然后,调用apply_to_pte_range函数来对PTE范围内的页面应用函数fn。该函数将处理mm、pmd、addr和next之间的页面,并使用fn函数进行处理。如果在处理过程中出现错误,将返回一个非零的错误码err。
如果err不为零,表示在处理过程中出现了错误,函数将跳出循环。
如果没有出现错误,继续进行下一次迭代。在每次迭代中,通过递增pmd指针、更新addr为next,并检查addr是否等于end来确定是否继续循环。
当addr等于end时,表示已经处理完整个范围,函数将返回err。
这段代码的作用是在给定的PMD范围内对页面应用函数进行处理,通过逐个PTE范围进行迭代,并在每个PTE范围内调用给定的函数进行处理。
2.5 apply_to_pte_range
static int apply_to_pte_range(struct mm_struct *mm, pmd_t *pmd,unsigned long addr, unsigned long end,pte_fn_t fn, void *data)
{pte_t *pte;int err;spinlock_t *uninitialized_var(ptl);pte = (mm == &init_mm) ?pte_alloc_kernel(pmd, addr) :pte_alloc_map_lock(mm, pmd, addr, &ptl);if (!pte)return -ENOMEM;BUG_ON(pmd_huge(*pmd));arch_enter_lazy_mmu_mode();do {err = fn(pte++, addr, data);if (err)break;} while (addr += PAGE_SIZE, addr != end);arch_leave_lazy_mmu_mode();if (mm != &init_mm)pte_unmap_unlock(pte-1, ptl);return err;
}
函数的执行流程如下:
根据mm是否等于&init_mm,选择不同的方式分配PTE。如果mm等于&init_mm,表示当前是内核线程,使用pte_alloc_kernel函数为给定的PMD和地址分配一个PTE;否则,使用pte_alloc_map_lock函数为给定的进程mm、PMD和地址分配一个PTE,并将分配过程中获取的自旋锁地址保存在ptl中。如果无法分配PTE,则返回错误码-ENOMEM。
使用BUG_ON宏检查给定的PMD是否是巨页(huge page)。如果是巨页,会引发一个bug检查(bug-on),表示代码中出现了不应该出现的情况。
调用arch_enter_lazy_mmu_mode函数,进入延迟MMU模式。这个函数是架构相关的,用于在某些架构上进入延迟更新MMU页表的模式。
进入一个循环,该循环将在PTE范围内进行迭代处理。
在循环的每一次迭代中,首先调用给定的函数fn来处理当前的PTE,传递pte、addr和data作为参数。如果在处理过程中出现错误,将返回一个非零的错误码err。
如果err不为零,表示在处理过程中出现了错误,函数将跳出循环。
如果没有出现错误,继续进行下一次迭代。在每次迭代中,通过递增pte指针、更新addr为addr + PAGE_SIZE,并检查addr是否等于end来确定是否继续循环。
完成PTE范围的处理后,调用arch_leave_lazy_mmu_mode函数,离开延迟MMU模式。
如果mm不等于&init_mm,表示当前是用户进程,调用pte_unmap_unlock函数来解除之前映射的PTE页框,并释放自旋锁。这个函数用于解除映射并解锁页表。
三、hook系统调用
set_memory_ro/rw函数只用于更改由 vmalloc 和 vmap分配的内存区间,而系统调用表不是由vmalloc 或者vmap分配的,系统调用表位于内核的只读数据区,在arm64架构中,在Linux 4.6 中 将内核镜像移到vmalloc的区域了,虽然不是由vmalloc 或者vmap分配的,但是是在vmalloc区间,如下所示:
// linux-5.4.18/arch/arm64/mm/mmu.cstatic void __init map_kernel_segment(pgd_t *pgdp, void *va_start, void *va_end,pgprot_t prot, struct vm_struct *vma,int flags, unsigned long vm_flags)
{phys_addr_t pa_start = __pa_symbol(va_start);unsigned long size = va_end - va_start;BUG_ON(!PAGE_ALIGNED(pa_start));BUG_ON(!PAGE_ALIGNED(size));__create_pgd_mapping(pgdp, pa_start, (unsigned long)va_start, size, prot,early_pgtable_alloc, flags);if (!(vm_flags & VM_NO_GUARD))size += PAGE_SIZE;vma->addr = va_start;vma->phys_addr = pa_start;vma->size = size;vma->flags = VM_MAP | vm_flags;vma->caller = __builtin_return_address(0);//添加到vmalloc区间vm_area_add_early(vma);
}
而set_memory_ro/rw函数在调用change_memory_common函数时,只是判断内核地址是否在 vmap_area – kernel virtual area 区间,然后检查是否有VM_ALLOC标志,因此我们还是可以通过set_memory_ro/rw函数来更改系统调用表的页表属性。
static int change_memory_common(unsigned long addr, int numpages,pgprot_t set_mask, pgprot_t clear_mask)
{struct vm_struct *area;...../** Let's restrict ourselves to mappings created by vmalloc (or vmap).* Those are guaranteed to consist entirely of page mappings, and* splitting is never needed.** So check whether the [addr, addr + size) interval is entirely* covered by precisely one VM area that has the VM_ALLOC flag set.*/area = find_vm_area((void *)addr);if (!area ||end > (unsigned long)area->addr + area->size ||!(area->flags & VM_ALLOC))return -EINVAL;....../** Get rid of potentially aliasing lazily unmapped vm areas that may* have permissions set that deviate from the ones we are setting here.*/vm_unmap_aliases();return __change_memory_common(start, size, set_mask, clear_mask);
}
这样我们可以自己给该地址的vmap_area区间加上VM_ALLOC标志即可,内核镜像是在kernel virtual area 区间的,如下所示:
area = my_find_vm_area((void *)addr);if(!area){printk("no find vm area\n");return -1;}area->flags |= VM_ALLOC;
完整的hook代码如下:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
#include <asm/unistd.h>
#include <asm/ptrace.h> int (*my_set_memory_ro)(unsigned long addr, int numpages);
int (*my_set_memory_rw)(unsigned long addr, int numpages);struct vm_struct *(*my_find_vm_area)(const void *addr);static unsigned long *__sys_call_table;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 unsigned long addr;static int __init lkm_init(void)
{struct vm_struct *area;my_set_memory_ro = (void *)kallsyms_lookup_name("set_memory_ro");my_set_memory_rw = (void *)kallsyms_lookup_name("set_memory_rw");my_find_vm_area = (void *)kallsyms_lookup_name("find_vm_area");__sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");printk("__sys_call_table = %lx\n", __sys_call_table);//保存原始的系统调用:mkdirorig_mkdir = (syscall_fn_t)__sys_call_table[__NR_mkdirat];addr = (unsigned long)(__sys_call_table + __NR_mkdirat);addr &= PAGE_MASK;area = my_find_vm_area((void *)addr);if(!area){printk("no find vm area\n");return -1;}area->flags |= VM_ALLOC;printk("area->addr = %p, area->size = %lx\n", area->addr, area->size); //hook 系统调用表表项:sys_call_table[__NR_mkdirat]my_set_memory_rw(addr, 1);__sys_call_table[__NR_mkdirat] = (unsigned long)mkdir_hook;my_set_memory_ro(addr, 1);printk("lkm_init\n");return 0;
}static void __exit lkm_exit(void)
{//模块卸载时恢复原来的mkdir系统调用my_set_memory_rw(addr, 1);__sys_call_table[__NR_mkdirat] = (unsigned long)orig_mkdir;my_set_memory_ro(addr, 1);printk("lkm_exit\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
参考资料
Linux 5.4.18
相关文章:
Linux arm64 set_memory_ro/rw函数
文章目录 一、函数简介1.1 简介1.2 change_memory_common1.3 __change_memory_common 二、apply_to_page_range函数2.1 apply_to_page_range2.2 apply_to_p4d_range2.3 apply_to_pud_range2.4 apply_to_pmd_range2.5 apply_to_pte_range 三、hook系统调用参考资料 一、函数简介…...

安达发|APS排单软件中甘特图的应用
近几年来,企业对生产效率和管理水平的要求越来越高。为了提高生产效率,降低生产成本,许多企业开始引入先进的生产计划与调度系统(APS),实现生产过程的自动化、智能化管理。APS排产软件是一种能够根据企业的…...

快速上手Linux基础开发工具
目录 软件包管理器 概念理解 用法示例 - 以yum为例 vim 模式的切换 常用操作 插件和配置 gcc/g gdb make / makefile 软件包管理器 概念理解 在Linux下安装软件的话,一个比较原始的办法是下载程序的源代码,然后进行编译,进而得到…...

【开发工具】idea 的全局搜索快捷键(Ctrl+shift+F)失效
文章目录 前言1. 取消 输入法的快捷键(推荐使用)2.更改 idea的快捷键3. 热键占用总结 前言 当你发现在idea 中看到用于全局搜索的快捷键就是 CtrlshiftF,可是怎么按都不管用的时候,你就不要再执着于自己的操作继续狂点电脑按键了…...

港联证券:“火箭蛋”来袭 蛋价涨势能否延续?
上个交易周(9月11日至15日),鸡蛋期货商场呈现了意想不到的涨势。9月15日,鸡蛋期货多个合约大涨,其中2310合约涨超5.6%,主力合约2311盘中两度触及涨停,最终收涨6%。业内人士以为,鸡蛋…...

Vue3_vite
使用Vue-cli创建 使用vite创建 Composition API 组合API setup 1.Vue3中的一个新的配置项,值为一个函数 2.可以将组件中所用到的数据,方法等配置在setup中. 3.setup函数的两种返回值 3.1若返回一个对象,则对象中的属性,方法,在模板中均可以直接使用. 3.2若返回一个渲染函数…...

python-字符串去掉空格的常见方法
python提供了去掉字符串空格的方法,可以满足大部分需求。 但在实际应用中,还需要灵活借助python其他方法,来实现字符串空格的删除。 比如,去掉字符串的全部空格、字符串连续空格保留一个等,都需要结合其他的方法来实现…...

如何写出一个成熟的线上线下结合的营销方案?
分享一下咱们案例库里策划的一个线上线下结合的活动的案例。 这个活动是为了推广一个新品牌,增加品牌知名度和用户粘性。 你可以根据以下几个要点来进行活动策划: 1、目标: 让目标用户了解并喜欢新品牌,激发用户参与和分享&am…...

Vc - Qt - “扩张“的窗口
该示例演示了一个"扩张的窗口",主窗口的布局为水平布局,内置两个子窗口,采用定时器设置左边窗口的宽度,达到控制"扩张"的目的。 #include <QApplication> #include <QWidget> #include <QHBox…...

vue学习-02vue入门之组件
删除Vue-cli预设 在用户根目录下(C:\Users\你的用户名)这个地址里有一个.vuerc 文件,修改或删除配置 组件 Props(组件之间的数据传递) Prop 的大小写 (camelCase vs kebab-case)不敏感Prop 类型: String Number Boolean Array Object Date Function Symbol传递静态或动态 Pr…...

解决Pycharm使用Conda激活环境失败的问题
Q:公司电脑终端使用powershell来激活conda环境时报错? 同时手动打开powershell报"profile.ps1” 无法被加载的错误 A: 1,手动打开powershell,设置管理员打开 2,打开powershell 打开 PowerShell 终端,并输入以下命令:Get-ExecutionPo…...

SpringSecurity 核心组件
文章目录 SpringSecurity 结构组件:SecurityContextHolder组件:Authentication组件:UserDetailsService组件:GrantedAuthority组件总结 SpringSecurity 结构 在SpringSecurity中的jar分为4个,作用分别为 jar作用spri…...

【Vue】快速入门和生命周期
目录 前言 一、vue的介绍 1. Vue.js是什么? 2. 库和框架的区别 3.基本概念和用法: 二、MVVM的介绍 1. 什么是MVVM? 2. MVVM的组成部分 3. MVVM的工作流程 4. MVVM的优势 5. MVVM的应用场景 三、vue实例 1.模板语法: …...
JVM架构和内存管理优化
Java虚拟机(JVM)是Java编程语言的核心组件,负责执行Java字节码并提供运行时环境,使得Java程序可以在不同的平台上运行。了解JVM的工作原理和内存管理对于优化代码性能和理解Java的内存管理和垃圾收集机制非常重要。在本文中&#…...

C语言——贪吃蛇小游戏
目录 一、ncurse 1.1 为什么需要用ncurse: 1.2 ncurse的输入输出: 1.2.1 如何使用ncurse: 1.2.2 编译ncurse的程序: 1.2.3 测试输入一个按键ncurse的响应速度: 1.3 ncurse上下左右键获取: 1.3.1 如…...

PHP8中获取并删除数组中第一个元素-PHP8知识详解
我在上一节关于数组的教程,讲的是在php8中获取并删除数组中最后一个元素,今天分享的是相反的:PHP8中获取并删除数组中第一个元素。 回顾一下昨天的知识,array_pop()函数将返回数组的最后一个元素,今天学习的是使用arr…...

EtherCAT 总线型 4 轴电机控制卡解决方案
技术特点 支持标准 100M/s 带宽全双工 EtherCAT 总线网络接口及 CoE 通信协议一 进一出(RJ45 接口),支持多组动态 PDO 分组和对象字典的自动映射,支持站 号 ID 的自动设置与保存,支持 SDO 的电机参数设置与…...

Upload-labs十六和十七关
目录 第十六关第十七关 第十六关 直接上传php文件判断限制方式: 同第十五关白名单限制 第十六关源码: 代码逻辑判断了后缀名、content-type,以及利用imagecreatefromgif判断是否为gif图片,最后再做了一次二次渲染 第71行检测…...

软件包的管理
概念 在早期Linux系统中,要想在Linux系统中安装软件只能采取编译源码包的方式进行安装,所以早期安装软件是一件非常困难、耗费耐心的事情,而且大多数服务程序仅提供源代码,还需要运维人员编译后自行解决软件之间的依赖关系。所以…...

常见入门级进销存系统合集
进销存系统是企业管理中不可或缺的一环,它们可以帮助企业有效管理库存、销售和采购等关键业务。然而,对于初创企业和小型企业来说,选择一个合适的进销存系统可能是一项挑战。在这篇文章中,我们将探讨入门级和资深级进销存系统之间…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...