Linux x86平臺獲取sys_call_table

文章目錄

  • 前言
  • 一、根據call *sys_call_table來獲取
  • 二、使用dump_stack
  • 三、根據MSR_LSTAR寄存器
  • 四、使用sys_close
  • 參考資料

前言

Linux 3.10.0 – x86_64

最簡單獲取sys_call_table符號的方法:

# cat /proc/kallsyms | grep sys_call_table
ffffffff816beee0 R sys_call_table

內核模塊使用 kallsyms_lookup_name(推薦) 或者 kprobe:

 unsigned long * __sys_call_table = (unsigned long *)kallsyms_lookup_name("sys_call_table");

下面舉例說明一下x86_64平臺下其他獲取sys_call_table的方案。

一、根據call *sys_call_table來獲取

(1)

// linux-3.10/arch/x86/kernel/entry_64.S/** Register setup:* rax  system call number* rdi  arg0* rcx  return address for syscall/sysret, C arg3* rsi  arg1* rdx  arg2* r10  arg3 	(--> moved to rcx for C)* r8   arg4* r9   arg5* r11  eflags for syscall/sysret, temporary for C* r12-r15,rbp,rbx saved by C code, not touched.** Interrupts are off on entry.* Only called from user space.** XXX	if we had a free scratch register we could save the RSP into the stack frame*      and report it properly in ps. Unfortunately we haven't.** When user can change the frames always force IRET. That is because* it deals with uncanonical addresses better. SYSRET has trouble* with them due to bugs in both AMD and Intel CPUs.*/ENTRY(system_call)
......
system_call_fastpath:......movq %r10,%rcxcall *sys_call_table(,%rax,8)  # XXX:	 rip relativemovq %rax,RAX-ARGOFFSET(%rsp)......

在進入system_call時,中斷被禁用。system_call僅從用戶空間調用。系統調用通過指令syscall來執行。

在x86_64架構的用戶空間下進行系統調用時,常用的寄存器如下:

rax:系統調用號(syscall number)放置在rax寄存器中,用于指定要調用的特定系統調用。
rdi:第一個參數(arg0)。在系統調用期間,用戶提供的第一個參數通常存儲在rdi寄存器中。
rsi:第二個參數(arg1)。用戶提供的第二個參數通常存儲在rsi寄存器中。
rdx:第三個參數(arg2)。用戶提供的第三個參數通常存儲在rdx寄存器中。
r10、r8、r9:第四、五、六個參數(arg3、arg4、arg5)。用戶提供的第四、五、六個參數通常存儲在r10、r8和r9寄存器中。

rcx:系統調用的返回地址。在進行系統調用時,將返回地址存儲在rcx寄存器中,以便在系統調用完成后返回到正確的位置。
r11:用于存儲eflags寄存器的值。在進行系統調用前,將當前eflags寄存器的值保存在r11寄存器中,以便在系統調用完成后恢復它。

用戶空間函數調用:

rax:返回值  參數傳遞:rdi,rsi,rdx,rcx,r8,r9

用戶空間系統調用:

rax:系統調用號  參數傳遞:rdi,rsi,rdx,r10、r8、r9  (rcx -> r10)

(2)
在x86_64架構中,調用sys_call_table的機器碼操作是通過間接調用(indirect call)指令來實現的。具體的操作碼是ff 14 c5,其表示的匯編指令是callq *%rax。

這條指令的作用是從rax寄存器中獲取一個指針地址,然后跳轉到該地址執行代碼。在這種情況下,我們假設rax寄存器中存儲了sys_call_table的地址,以便通過間接調用來調用相應的系統調用函數。

我們通過crash調試便可以獲取到sys_call_table的地址:
在這里插入圖片描述
由上面的匯編代碼:

system_call-->system_call_fastpath-->call *sys_call_table(,%rax,8)

我們反匯編system_call_fastpath:

crash> dis system_call_fastpath
0xffffffff816b4fb3 <system_call_fastpath>:      cmp    $0x146,%rax
0xffffffff816b4fb9 <system_call_fastpath+6>:    ja     0xffffffff816b5081 <badsys>
0xffffffff816b4fbf <system_call_fastpath+12>:   mov    %r10,%rcx
0xffffffff816b4fc2 <system_call_fastpath+15>:   callq  *-0x7e941120(,%rax,8)
0xffffffff816b4fc9 <system_call_fastpath+22>:   mov    %rax,0x20(%rsp)

可以看到在地址0xffffffff816b4fc2,調用call *sys_call_table(,%rax,8),然后讀取內存地址0xffffffff816b4fc2的值:

crash> rd -64 0xffffffff816b4fc2
ffffffff816b4fc2:  48816beee0c514ff                    .....k.H

call的操作碼是0xff14c5,后面就是sys_call_table的地址0x816beee0
因此sys_call_table的地址是0xffffffff816beee0。

備注:第三節使用了rdmsr來獲取 system_call 符號的值。

(3)
我們也可以借助vmlinux來objdump來獲取其地址:

# ./extract-vmlinux vmlinuz-3.10.0-693.el7.x86_64 > vmlinux
# objdump -d vmlinux > vmlinux.txt
# vim vmlinux.txt

我們根據 ff 14 c5 指令碼來搜索,上一條指令且是movq %r10,%rcx:

	movq %r10,%rcxcall *sys_call_table(,%rax,8)  # XXX:	 rip relative
ffffffff816b4fbf:       4c 89 d1                mov    %r10,%rcx
ffffffff816b4fc2:       ff 14 c5 e0 ee 6b 81    callq  *-0x7e941120(,%rax,8)

可以看到 ff 14 c5 機器碼后面的地址就是sys_call_table的地址0x816beee0(x86_64是小端機器)。
因此sys_call_table的地址是0xffffffff816beee0。

二、使用dump_stack

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>static int __init lkm_init(void)
{dump_stack();return 0;
}static void __exit lkm_exit(void)
{}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
[ 7666.386756] Call Trace:
[ 7666.386761]  [<ffffffff816a3d91>] dump_stack+0x19/0x1b
[ 7666.386762]  [<ffffffffc01f3009>] lkm_init+0x9/0x1000 [sys_call_table]
[ 7666.386764]  [<ffffffff810020e8>] do_one_initcall+0xb8/0x230
[ 7666.386766]  [<ffffffff81100734>] load_module+0x1f64/0x29e0
[ 7666.386769]  [<ffffffff8134bbf0>] ? ddebug_proc_write+0xf0/0xf0
[ 7666.386770]  [<ffffffff810fcdd3>] ? copy_module_from_fd.isra.42+0x53/0x150
[ 7666.386772]  [<ffffffff81101366>] SyS_finit_module+0xa6/0xd0
[ 7666.386774]  [<ffffffff816b4fc9>] system_call_fastpath+0x16/0x1b

我們就可以看到 system_call_fastpath+0x16 的地址是0xffffffff816b4fc9,因此system_call_fastpath的地址是:

0xffffffff816b4fc9 - 0x16 = 0xffffffff816b4fb3

我們可以從/proc/kallsyms驗證:

# cat /proc/kallsyms | grep system_call_fastpath
ffffffff816b4fb3 t system_call_fastpath
system_call_fastpath:
#if __SYSCALL_MASK == ~0cmpq $__NR_syscall_max,%rax
#elseandl $__SYSCALL_MASK,%eaxcmpl $__NR_syscall_max,%eax
#endifja badsysmovq %r10,%rcxcall *sys_call_table(,%rax,8)  # XXX:	 rip relative

這里我們簡單點,從上面的crash可以看到:

crash> dis system_call_fastpath
0xffffffff816b4fb3 <system_call_fastpath>:      cmp    $0x146,%rax
0xffffffff816b4fb9 <system_call_fastpath+6>:    ja     0xffffffff816b5081 <badsys>
0xffffffff816b4fbf <system_call_fastpath+12>:   mov    %r10,%rcx
0xffffffff816b4fc2 <system_call_fastpath+15>:   callq  *-0x7e941120(,%rax,8)

call *sys_call_table 在 system_call_fastpath 的 0xf(15)處。

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>static int __init lkm_init(void)
{int i;unsigned char *ptr;ptr = (unsigned char *)(0xffffffff816b4fc9 - 0x16 + 0xf);for (i = 0; i < 8; i ++) {//printk("%02x ", (unsigned char)ptr[i]);printk("%02x ", (unsigned char)*(ptr + i));}return 0;
}static void __exit lkm_exit(void)
{}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
(unsigned char)ptr[i]) = (unsigned char)*(ptr + i))
# insmod sys_call_table.ko
[# dmesg -c
[ 8416.858466] ff 14 c5 e0 ee 6b 81 48

可以看到 ff 14 c5 機器碼后面的地址就是sys_call_table的地址0x816beee0(x86_64是小端機器)。
因此sys_call_table的地址是0xffffffff816beee0。

或者直接搜索:

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>static int __init lkm_init(void)
{int i, j;unsigned char *ptr;ptr = (unsigned char *)(0xffffffff816b4fc9 - 0x16 + 0xf);for (i = 0; i < 20; i ++) {if( ((unsigned char)ptr[i] == 0xff) && ((unsigned char)ptr[i + 1] == 0x14) && ((unsigned char)ptr[i +2] == 0xc5) ){printk("0x%x ", *(unsigned int *)(ptr+i+3));break;}} return 0;
}static void __exit lkm_exit(void)
{}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
# insmod sys_call_table.ko
# dmesg -c
[  280.502659] 0x816beee0

因此sys_call_table的地址是0xffffffff816beee0。

三、根據MSR_LSTAR寄存器

我們來看一下內核的啟動過程:

start_kernel()-->trap_init()-->cpu_init()-->syscall_init()
#define MSR_STAR		0xc0000081 /* legacy mode SYSCALL target */
#define MSR_LSTAR		0xc0000082 /* long mode SYSCALL target */
#define MSR_CSTAR		0xc0000083 /* compat mode SYSCALL target */

| Register

AddressArchitectural MSR Name / Bit Fields (Former MSR Name)MSR/Bit Description
C000_0081HIA32_STARSystem Call Target Address (R/W)
C000_0082HIA32_LSTARIA-32e Mode System Call Target Address (R/W)
Target RIP for the called procedure when SYSCALL is executed in 64-bit mode.
C000_0083HIA32_CSTARIA-32e Mode System Call Target Address (R/W)
Not used, as the SYSCALL instruction is not recognized in compatibility mode.

我們主要看MSR_LSTAR寄存器:
IA32_LSTAR 是 IA-32e 模式下的系統調用目標地址寄存器,用于存儲在 64 位模式下執行 SYSCALL 指令時被調用過程的目標 RIP(指令指針)。

當在 64 位模式下執行 SYSCALL 指令時,系統將從 IA32_LSTAR 寄存器中加載 RIP 的值,以跳轉至相應的系統調用處理程序。

為了使內核接收傳入的系統調用,它必須通過將其地址寫入IA32_LSTAR MSR寄存器 來注冊將在發生系統調用時執行的代碼的地址。

/* May not be marked __init: used by software suspend */
void syscall_init(void)
{/** LSTAR and STAR live in a bit strange symbiosis.* They both write to the same internal register. STAR allows to* set CS/DS but only a 32bit target. LSTAR sets the 64bit rip.*/wrmsrl(MSR_STAR,  ((u64)__USER32_CS)<<48  | ((u64)__KERNEL_CS)<<32);wrmsrl(MSR_LSTAR, system_call);wrmsrl(MSR_CSTAR, ignore_sysret);....../* Flags to clear on syscall */wrmsrl(MSR_SYSCALL_MASK,X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF|X86_EFLAGS_IOPL|X86_EFLAGS_AC);
}

這行代碼把system_call入口地址存入到MSR_LSTAR寄存器。syscall指令會把該地址加載到到%rip寄存器,從該地址開始執行。

syscall指令:
在這里插入圖片描述
SYSCALL 指令用于在特權級別 0(內核模式)下調用操作系統的系統調用處理程序。它通過從 IA32_LSTAR MSR 寄存器加載 RIP(同時將 SYSCALL 指令后面的指令地址保存在 RCX 中)來實現這一功能。IA32_LSTAR MSR 寄存器的值是一個規范地址(canonical address),通過 WRMSR 指令確保其始終包含一個規范地址。

SYSCALL 指令:RIP = IA32_LSTAR MSR 寄存器 = system_call

過程如下:
在這里插入圖片描述

特別說明一下,因為初始化時,掩碼中包含中斷標志位X86_EFLAGS_IF,所以syscall指令執行時,中斷是禁止的。

我們可以看到在syscall_init中將 system_call 的地址寫入了 MSR_LSTAR 寄存器:

wrmsrl(MSR_LSTAR, system_call);

那么我們讀取MSR_LSTAR寄存器就可以獲取到system_call的地址,進入獲取到sys_call_table的地址。

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>static int __init lkm_init(void)
{unsigned long msr_lstar;rdmsrl(MSR_LSTAR, msr_lstar);printk("MSR_LSTAR = %lx\n", msr_lstar);return 0;
}static void __exit lkm_exit(void)
{}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
]# cat /proc/kallsyms | grep '\<system_call\>'
ffffffff816b4f50 T system_call
# insmod get_msr.ko
# dmesg -c
[ 1474.209016] MSR_LSTAR = ffffffff816b4f50

獲取到了system_call的地址那么解析其后面的字節便可以找到sys_call_table的地址。

完整代碼:

#include <linux/module.h>
#include <linux/kernel.h>void *memmem(const void *haystack, size_t haystack_size, const void *needle, size_t needle_size )
{char *p;for (p = (char *)haystack; p <= ((char *)haystack - needle_size + haystack_size); p++ )if (memcmp(p, needle, needle_size) == 0 )return (void *)p;return NULL;
}unsigned long *find_sys_call_table ( void )
{char **p;unsigned long sct_off = 0;unsigned char code[512];rdmsrl(MSR_LSTAR, sct_off);memcpy(code, (void *)sct_off, sizeof(code));p = (char **)memmem(code, sizeof(code), "\xff\x14\xc5", 3);if (p){unsigned long *sct = *(unsigned long **)((char *)p + 3);// Stupid compiler doesn't want to do bitwise math on pointerssct = (unsigned long *)(((unsigned long)sct & 0xffffffff) | 0xffffffff00000000);return sct;}elsereturn NULL;
}static int __init lkm_init(void)
{unsigned long *sys_call_table = find_sys_call_table();printk("The sys_call_table address is:%lx\n",(unsigned long)sys_call_table);return 0;
}static void __exit lkm_exit(void)
{}
module_init(init_get_sys_call_table);
module_exit(exit_get_sys_call_table);MODULE_LICENSE("GPL");
# cat /proc/kallsyms | grep '\<sys_call_table\>'
ffffffff816beee0 R sys_call_table
# insmod sys_call_table.ko
# dmesg -c
[ 2363.360239] The sys_call_table address is:0xffffffff816beee0

四、使用sys_close

// linux-3.10/fs/open.c/** Careful here! We test whether the file pointer is NULL before* releasing the fd. This ensures that one clone task can't release* an fd while another clone is opening it.*/
SYSCALL_DEFINE1(close, unsigned int, fd)
{int retval = __close_fd(current->files, fd);/* can't restart close syscall because file table entry was cleared */if (unlikely(retval == -ERESTARTSYS ||retval == -ERESTARTNOINTR ||retval == -ERESTARTNOHAND ||retval == -ERESTART_RESTARTBLOCK))retval = -EINTR;return retval;
}
EXPORT_SYMBOL(sys_close);
EXPORT_SYMBOL(sys_close);

系統調用函數(sys_close)在內核中是導出的,因此可以直接獲取其地址。

// linux-3.10/arch/x86/include/asm/page_64_types.h/** Set __PAGE_OFFSET to the most negative possible address +* PGDIR_SIZE*16 (pgd slot 272).  The gap is to allow a space for a* hypervisor to fit.  Choosing 16 slots here is arbitrary, but it's* what Xen requires.*/
#define __PAGE_OFFSET           _AC(0xffff880000000000, UL)
// linux-3.10/arch/x86/include/asm/page_types.h#define PAGE_OFFSET		((unsigned long)__PAGE_OFFSET)

__PAGE_OFFSET是一個常量,用于表示內核虛擬地址空間的起始地址。

PAGE_OFFSET 是內核內存空間的起始地址。 因為 sys_close 是導出函數 (需要指出的是, sys_open 、 sys_read 等并不是導出的), 我們可以直接得到他的地址;因為系統調用號 (也就是 sys_call_table 這個一維數組的索引) 在同一 ABI (x86 跟 x64 不是同一 ABI)上具有高度的后向兼容性, 更重要的是,我們可以直接使用這個索引(代碼中的 __NR_close )!

從內核內存的起始地址開始, 逐一嘗試每一個指針大小的內存:把它當成是 sys_call_table 的地址, 用某個系統調用的編號(也就是索引)訪問數組中的成員, 如果訪問得到的值剛好是是這個系統調用號所對應的系統調用的地址, 那么我們就認為當前嘗試的這塊指針大小的內存就是我們要找的 sys_call_table 的地址。

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>#include <linux/syscalls.h> //__NR_closeunsigned long **get_sys_call_table(void)
{//內核內存空間的起始地址(PAGE_OFFSET)是已知的,因為它是內核的固定位置。unsigned long **entry = (unsigned long **)PAGE_OFFSET;for ( ; (unsigned long)entry < ULONG_MAX; entry += 1) {if (entry[__NR_close] == (unsigned long *)sys_close) {return entry;}}return NULL;
}static int __init lkm_init(void)
{unsigned long **real_sys_call_table = get_sys_call_table();printk("PAGE_OFFSET = %lx\n", (unsigned long)PAGE_OFFSET);printk("sys_call_table = %lx\n", (unsigned long)real_sys_call_table);return 0;
}static void __exit lkm_exit(void)
{}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");
# insmod sys_call_table.ko
# dmesg -c
[ 7405.938954] PAGE_OFFSET = ffff880000000000
[ 7405.938956] sys_call_table = ffff8800016beee0

從內核內存的起始地址(PAGE_OFFSET)開始,逐一嘗試不同大小的內存塊。這是因為sys_call_table通常被存儲在一塊連續的內存區域中。

對于每個嘗試的內存地址,使用__NR_close作為索引來訪問sys_call_table數組中的成員。

檢查返回的值是否與sys_close函數的地址匹配。如果匹配成功,那么當前嘗試的內存地址就被認為是sys_call_table的地址。

參考資料

Linux 3.10.0
https://blog.csdn.net/whatday/article/details/100513427
https://juejin.cn/post/7203681024236355639
https://docs-conquer-the-universe.readthedocs.io/zh-cn/latest/linux_rootkit/sys_call_table.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/711631.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/711631.shtml
英文地址,請注明出處:http://en.pswp.cn/news/711631.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

隨想錄算法訓練營第四十七天|198.打家劫舍、213.打家劫舍II、337.打家劫舍III

198.打家劫舍 public class Solution {public int Rob(int[] nums) {if(nums.Length0){return 0;}if(nums.Length1){return nums[0];}int[] dpnew int[nums.Length1];dp[0]nums[0];dp[1]Math.Max(nums[0],nums[1]);for(int i2;i<nums.Length;i){dp[i]Math.Max(dp[i-2]nums[…

什么是 HTTPS 證書?作用是什么?

HTTPS 證書&#xff0c;即超文本傳輸安全協議證書&#xff08;Hypertext Transfer Protocol Secure&#xff09;&#xff0c;是網站安全的關鍵組成部分。它通過 SSL/TLS 加密協議&#xff0c;確保用戶與網站之間的數據傳輸是加密和安全的。 什么是 HTTPS 證書&#xff1f; HT…

使用Docker -compose啟動自定義jar包

步驟1&#xff1a;編寫docker-compose.yml文件 首先我們需要編寫一個docker-compose.yml文件來定義我們的服務傳到我們的云服務器上 以下是一個示例&#xff1a; version: 3 services:app:build:context: .dockerfile: Dockerfileports:- 8080:8080volumes:- ./app.jar:/app…

可視化圖表:水球圖,展示百分比的神器。

Hi&#xff0c;我是貝格前端工場的老司機&#xff0c;本文分享可視化圖表設計的水球圖設計&#xff0c;歡迎老鐵持續關注我們。 一、水球圖及其作用 水球圖是一種特殊的可視化圖表&#xff0c;它主要用于展示百分比或比例的數據&#xff0c;并以水球的形式進行呈現。水球圖的作…

2023面試題

目錄 題目 1:JVM 整體結構是什么樣的? 8 題目 3:Object 類有哪些方法? 11 題目 4:靜態變量與實例變量區別? 11 題目 5:String 類的常用方法有哪些? 11 題目 6:數組有沒有 length()方法?String 有沒有 length() 12 題目 7:String、StringBuffer、StringBuilder 的區別…

【k8s 訪問控制--認證與鑒權】

1、身份認證與權限 前面我們在操作k8s的所有請求都是通過https的方式進行請求&#xff0c;通過REST協議操作我們的k8s接口&#xff0c;所以在k8s中有一套認證和鑒權的資源。 Kubenetes中提供了良好的多租戶認證管理機制&#xff0c;如RBAC、ServiceAccount還有各種策路等。通…

集合篇之ArrayList

一、源碼如何分析&#xff1f; 1.成員變量 2.構造方法 3.關鍵方法 一些添加的方法。 二、debug看源碼 我們給出下面代碼&#xff1a; public void test01() {ArrayList<Integer> list new ArrayList<>();list.add(1);for (int i 2; i < 10; i) {list.add(i…

H5:段落標簽與換行標簽

目錄 一.前言 二.正文 1.段落標簽 2.換行標簽 三.結語 一.前言 學習前端&#xff0c;從此起飛&#xff0c;愿你堅持&#xff0c;直至等頂。 二.正文 1.段落標簽 <p></p> p為段落標簽&#xff0c;由英文paragraph簡寫而來&#xff0c;用于將一段某一部分文本&am…

算法練習第九天|232.用棧實現隊列、225. 用隊列實現棧

熟悉棧和隊列的方法&#xff1b;熟悉棧和隊列的數據結構&#xff0c;不涉及算法 232.用棧實現隊列 import java.util.Stack; class MyQueue {//負責進棧的Stack<Integer> stackIn;//負責出棧的Stack<Integer> stackOut;public MyQueue() {stackIn new Stack<&…

Rocketmq 入門介紹

從零手寫實現 mq 詳細介紹一下 rocketmq RocketMQ 是由阿里巴巴開發的分布式消息隊列系統&#xff0c;它是一個低延遲、高可靠、高吞吐量的消息中間件。 RocketMQ 最初是作為阿里巴巴的內部項目進行開發的&#xff0c;后來成為了 Apache 軟件基金會下的頂級項目&#xff0c;以…

精讀《React 高階組件》

本期精讀文章是&#xff1a;React Higher Order Components in depth 1 引言 高階組件&#xff08; higher-order component &#xff0c;HOC &#xff09;是 React 中復用組件邏輯的一種進階技巧。它本身并不是 React 的 API&#xff0c;而是一種 React 組件的設計理念&…

【QT+QGIS跨平臺編譯】之五十三:【QGIS_CORE跨平臺編譯】—【qgssqlstatementparser.cpp生成】

文章目錄 一、Bison二、生成來源三、構建過程一、Bison GNU Bison 是一個通用的解析器生成器,它可以將注釋的無上下文語法轉換為使用 LALR (1) 解析表的確定性 LR 或廣義 LR (GLR) 解析器。Bison 還可以生成 IELR (1) 或規范 LR (1) 解析表。一旦您熟練使用 Bison,您可以使用…

transformers文本相似度

在自然語言處理(NLP)中,文本相似度是衡量兩個文本之間語義或結構相似程度的一個重要概念。計算文本相似度的方法多種多樣,適應不同的應用場景和需求。以下是一些常見的文本相似度計算方法: 1、余弦相似度: 通過將文本轉換為向量表示(例如,使用詞袋模型、TF-IDF 或 wor…

2024年個人護理賽道選品風向在哪?這份賽盈分銷選品攻略必看!

2024年還會卷下去嗎&#xff1f;看到一位行業大佬分享的內容深有感觸&#xff1a;堅定做好產品&#xff0c;不做大賣&#xff0c;就不存在卷不卷。 有人出局&#xff0c;也會有人入局&#xff0c;并且深耕領域做大做強。 專注口腔護理的Bitvae入行不到兩年&#xff0c;憑借一款…

C#學習(十四)——垃圾回收、析構與IDisposable

一、何為GC 數據是存儲在內存中的&#xff0c;而內存又分為Stack棧內存和Heap堆內存 Stack棧內存Heap堆內存速度快、效率高結構復雜類型、大小有限制對象只能保存簡單的數據引用數據類型基礎數據類型、值類型- 舉個例子 var c new Customer{id: 123,name: "Jack"…

Java中String類有哪些常用方法?

Java中的String類提供了許多有用的方法&#xff0c;用于處理字符串。以下是一些常用的方法及其簡要描述&#xff1a; 1. **charAt(int index)**&#xff1a;返回指定位置的字符。 2. **length()**&#xff1a;返回字符串的長度。 3. **substring(int beginIndex, int endInd…

微信小程序手勢沖突?不存在的!

原生的應用經常會有頁面嵌套列表&#xff0c;滾動列表能夠改變列表大小&#xff0c;然后還能支持列表內下拉刷新等功能。看了很多的小程序好像都沒有這個功能&#xff0c;難道這個算是原生獨享的嗎&#xff0c;難道是由于手勢沖突無法實現嗎&#xff0c;冷靜的思考了一下&#…

Google驗證碼,掃描綁定,SpringBoot+ vue

文章目錄 后端1.使用Google工具類這個 類的 verifyTest 方法可以判斷掃描綁定之后的app上面驗證碼的準確性。這個類通過g_user,g_code(就是谷歌驗證器的secret,這個你已經插入到數據庫 中)來生成相關二維碼。2.用工具類自帶的g_user,g_code來生成二維碼2.1通過請求來生成相關二…

你知道vector底層是如何實現的嗎?

你知道vector底層是如何實現的嗎&#xff1f; vector底層使用動態數組來存儲元素對象&#xff0c;同時使用size和capacity記錄當前元素的數量和當前動態數組的容量。如果持續的push_back(emplace_back)元素&#xff0c;當size大于capacity時&#xff0c;需要開辟一塊更大的動態…

【InternLM 實戰營筆記】XTuner 大模型單卡低成本微調實戰

XTuner概述 一個大語言模型微調工具箱。由 MMRazor 和 MMDeploy 聯合開發。 支持的開源LLM (2023.11.01) InternLM Llama&#xff0c;Llama2 ChatGLM2&#xff0c;ChatGLM3 Qwen Baichuan&#xff0c;Baichuan2 Zephyr 特色 傻瓜化&#xff1a; 以 配置文件 的形式封裝了大…