Linux x86_64 dump_stack()函數基于FP棧回溯

文章目錄

  • 前言
  • 一、dump_stack函數使用
  • 二、dump_stack函數源碼解析
    • 2.1 show_stack
    • 2.2 show_stack_log_lvl
    • 2.3 show_trace_log_lvl
    • 2.4 dump_trace
    • 2.5 print_context_stack
  • 參考資料

前言

Linux x86_64
centos7
Linux:3.10.0

一、dump_stack函數使用

dump_stack函數用于打印當前任務的信息以及其堆棧跟蹤,能夠用來回溯打印調用棧信息。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>void noinline func_c(void)
{dump_stack();	
}void noinline func_b(void)
{func_c();	
}void noinline func_a(void)
{func_b();
}//內核模塊初始化函數
static int __init lkm_init(void)
{func_a();return 0;
}//內核模塊退出函數
static void __exit lkm_exit(void)
{printk("Goodbye\n");
}module_init(lkm_init);
module_exit(lkm_exit);MODULE_LICENSE("GPL");

這里加了noinline修飾,否則會被優化成 inline 函數。

[1109990.858938] Call Trace:
[1109990.858952]  [<ffffffff8e781340>] dump_stack+0x19/0x1b
[1109990.858960]  [<ffffffffc0a3700e>] func_c+0xe/0x10 [helloworld]
[1109990.858968]  [<ffffffffc0a3701e>] func_b+0xe/0x10 [helloworld]
[1109990.858974]  [<ffffffffc0a3702e>] func_a+0xe/0x10 [helloworld]
[1109990.858981]  [<ffffffffc0153009>] lkm_init+0x9/0x1000 [helloworld]
[1109990.858990]  [<ffffffff8e00210a>] do_one_initcall+0xba/0x240
[1109990.858999]  [<ffffffff8e11e45a>] load_module+0x271a/0x2bb0
[1109990.859007]  [<ffffffff8e3b4290>] ? ddebug_proc_write+0x100/0x100
[1109990.859016]  [<ffffffff8e119fe3>] ? copy_module_from_fd.isra.44+0x53/0x150
[1109990.859024]  [<ffffffff8e11ead6>] SyS_finit_module+0xa6/0xd0
[1109990.859033]  [<ffffffff8e793f92>] system_call_fastpath+0x25/0x2a

Linux dump_stack 函數原理:
棧幀如下如圖所示:callee的RBP寄存器的值保存caller的RBP寄存器地址,可以看作每個棧幀用單鏈表連接。

// linux-3.10/arch/x86/include/asm/stacktrace.h/* The form of the top of the frame on the stack */
struct stack_frame {struct stack_frame *next_frame;unsigned long return_address;
};

在這里插入圖片描述

幀指針起到了歷史上的作用。幀指針是一個寄存器,它始終包含著上一個堆棧指針的值。在 x86_64 架構中,通常使用的寄存器是 RBP。

由于幀指針寄存器的存在,堆棧現在成為了一個“堆棧幀”的鏈表,我們可以一直沿著鏈表向前遍歷到開頭。在任何時刻,我們只需查看當前幀指針寄存器的值,就可以獲得先前的 RSP 值。由于先前的 RSP 值恰好是存儲先前幀指針的位置,因此這就是一系列指針沿著堆棧向上爬行的過程。

通過遍歷堆棧幀鏈表,我們可以逐個獲取每個函數的返回地址、參數和局部變量等信息。這樣,我們就可以按順序打印每個函數的名稱,實現堆棧跟蹤。

幀指針寄存器的存在使得堆棧幀之間形成了鏈式結構,使得在堆棧跟蹤過程中可以方便地從當前幀指針寄存器獲取前一個堆棧幀的位置。通過這種方式,我們可以沿著堆棧鏈表一直向上遍歷,獲取所有函數的信息。

幀指針寄存器的使用使得堆棧跟蹤變得更加直觀和可靠,因為它提供了一種可靠的方式來遍歷堆棧幀鏈表。但是需要注意的是,某些情況下,編譯器可能會對幀指針進行優化或省略,因此在特定的編譯器優化設置下,幀指針可能不可用或不準確。

centos 7 配置了CONFIG_FRAME_POINTER選項:

# cat /boot/config-3.10.0-1160.el7.x86_64 | grep CONFIG_FRAME_POINTER
CONFIG_FRAME_POINTER=y

(1)基于Frame Pointer - fp寄存器的棧回溯:
優點:棧回溯比較快,理解簡單。相對較簡單:基于Frame Pointer寄存器的棧回溯通常比解析unwind節更簡單直接。
缺點:gcc添加了優化選項 -O 就會省略掉省略基指針。這樣就不能都通過這種形式進行棧回溯了。
-fomit-frame-pointer編譯標志進行優化:避免將%rbp用作棧幀指針,把FP當作一個通用寄存器,這樣就提供了一個額外的通用寄存器,提高程序運行效率。

(1)func_c RBP寄存器的值存放了父函數func_b的RBP寄存器的地址。其返回地址 = func_cRBP寄存器地址+8。
(2)對函數func_b的RBP寄存器的地址取值獲取func_b RBP寄存器的值,func_b RBP寄存器的值存放了父函數func_a的RBP寄存器的地址。其返回地址 = func_bRBP寄存器地址+8。
(3)對函數func_a的RBP寄存器的地址取值獲取func_a RBP寄存器的值,func_a RBP寄存器的值存放了父函數lkm_init的RBP寄存器的地址。其返回地址 = func_aRBP寄存器地址+8。
這樣一步步回溯就可以獲取整個調用棧。

在 x86_64 架構中 rbp 指向當前棧幀的起始位置,這個位置保存著舊的 rbp的值。我們可以看到在舊的 rbp 保存的位置上方保存著返回地址(rbp + 8)。這個返回地址是調用者函數中 call 指令的下一條指令的地址,子函數執行完成后會返回,舊的 rbp 首先出棧并賦值給 rbp 寄存器,同時返回地址也要出棧并賦值給 pc。

上面的過程可以遞歸的用于多層函數調用上。

我們可以將 dump_stack 函數的棧幀看做 Current frame,當前 pc 的值保存的是 dump_stack 中的某條指令的地址,內核先根據這個地址查詢 符號表 獲取到 dump_stack 函數的名稱與當前指令先相對于 dump_stack 函數起始位置的偏移量,然后通過訪問 rbp 寄存器指向的舊 rbp 的值來獲取到調用 dump_stack 函數的棧幀指針的值,有了這個值就可以不斷的回溯上方的棧幀,一個棧幀就是一個調用層次。

同時返回地址的位置就在舊的 rbp 存儲位置的上方,根據這樣的特點 dump_stack 也就能回溯不同調用層次中返回地址的值。根據返回地址就可以獲取到返回地址的上一條調用語句的地址,對該地址進行尋址,獲取到指令的編碼,就能夠獲取到調用函數的入口地址。這里可以使用如下公式:

call 指令調用函數的地址 = call 指令碼后面的偏移量 + 返回地址

這之后使用入口地址查詢 System-map 獲取到函數的名稱,同時計算出返回地址相對于函數入口的偏移量就準備好了打印的內容,調用打印函數打印信息,每個棧幀用單鏈表連接,然后繼續重復這一過程直到找不到一個合法的棧幀為止。

二、dump_stack函數源碼解析

centos 7 配置了CONFIG_FRAME_POINTER選項:

# cat /boot/config-3.10.0-1160.el7.x86_64 | grep CONFIG_FRAME_POINTER
CONFIG_FRAME_POINTER=y
// linux-3.10/lib/dump_stack.c/*** dump_stack - dump the current task information and its stack trace** Architectures can override this implementation by implementing its own.*/
void dump_stack(void)
{dump_stack_print_info(KERN_DEFAULT);show_stack(NULL, NULL);
}
EXPORT_SYMBOL(dump_stack);
dump_stack()-->show_stack()-->show_stack_log_lvl()-->show_trace_log_lvl()-->dump_trace()-->print_context_stack()

2.1 show_stack

// linux-3.10/arch/x86/include/asm/stacktrace.h#define STACKSLOTS_PER_LINE 4
#define get_bp(bp) asm("movq %%rbp, %0" : "=r" (bp) :)#ifdef CONFIG_FRAME_POINTER
static inline unsigned long
stack_frame(struct task_struct *task, struct pt_regs *regs)
{unsigned long bp;if (regs)return regs->bp;if (task == current) {/* Grab bp right from our regs */get_bp(bp);return bp;}/* bp is the last reg pushed by switch_to */return *(unsigned long *)task->thread.sp;
}

get_bp(bp)是一個宏定義,使用匯編語句獲取當前函數的基址寄存器(rbp)的值,并將其保存在bp變量中。

stack_frame是一個內聯函數,用于獲取給定任務的棧幀指針。

(1)如果傳入的regs參數非空,說明已經提供了寄存器上下文(pt_regs結構),則直接返回其中的基址寄存器(bp)的值。

(2)如果給定的任務結構體指針與當前任務相同(current表示當前任務),則直接使用get_bp宏獲取當前函數的基址寄存器的值(rbp),并將其作為棧幀指針返回。

(3)如果以上條件都不滿足,則假設bp是由switch_to函數推入的最后一個寄存器,從給定任務的線程結構體中獲取棧指針(sp)所指向的地址,并將其解釋為unsigned long類型的指針,以獲取棧幀指針。

void show_stack(struct task_struct *task, unsigned long *sp)
{unsigned long bp = 0;unsigned long stack;/** Stack frames below this one aren't interesting.  Don't show them* if we're printing for %current.*/if (!sp && (!task || task == current)) {sp = &stack;bp = stack_frame(current, NULL);}show_stack_log_lvl(task, NULL, sp, bp, "");
}

該函數用于打印給定任務的堆棧跟蹤信息。

2.2 show_stack_log_lvl

// linux-3.10/arch/x86/kernel/dumpstack_64.cvoid
show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,unsigned long *sp, unsigned long bp, char *log_lvl)
{unsigned long *irq_stack_end;unsigned long *irq_stack;unsigned long *stack;int cpu;int i;preempt_disable();cpu = smp_processor_id();irq_stack_end	= (unsigned long *)(per_cpu(irq_stack_ptr, cpu));irq_stack	= (unsigned long *)(per_cpu(irq_stack_ptr, cpu) - IRQ_STACK_SIZE);/** Debugging aid: "show_stack(NULL, NULL);" prints the* back trace for this cpu:*/if (sp == NULL) {if (task)sp = (unsigned long *)task->thread.sp;elsesp = (unsigned long *)&sp;}stack = sp;for (i = 0; i < kstack_depth_to_print; i++) {if (stack >= irq_stack && stack <= irq_stack_end) {if (stack == irq_stack_end) {stack = (unsigned long *) (irq_stack_end[-1]);pr_cont(" <EOI> ");}} else {if (((long) stack & (THREAD_SIZE-1)) == 0)break;}if (i && ((i % STACKSLOTS_PER_LINE) == 0))pr_cont("\n");pr_cont(" %016lx", *stack++);touch_nmi_watchdog();}preempt_enable();pr_cont("\n");show_trace_log_lvl(task, regs, sp, bp, log_lvl);
}

show_stack_log_lvl函數用于打印給定任務的堆棧跟蹤信息,并在日志級別上進行控制。

函數首先定義了一些局部變量,包括irq_stack_end、irq_stack、stack、cpu和i。

然后,禁用搶占(preempt_disable)并獲取當前處理器的 ID(smp_processor_id)。

irq_stack_end表示中斷堆棧的結束地址,irq_stack表示中斷堆棧的起始地址(通過per_cpu宏和irq_stack_ptr變量計算得到)。

接下來,通過一系列條件判斷,確定要打印的堆棧跟蹤信息。

如果給定的sp參數為空,表示需要打印當前任務的堆棧跟蹤信息。根據是否提供了任務結構體指針(task),確定要使用的棧指針(sp)。如果提供了任務結構體指針,則使用任務的線程結構體中的棧指針;否則,使用當前函數的棧指針。

接下來,通過循環遍歷堆棧,打印堆棧上的地址。在遍歷過程中,通過一系列條件判斷確定是否處于中斷堆棧范圍內,并在特定情況下打印(End of Interrupt)標記。如果堆棧地址與線程棧的大小(THREAD_SIZE)對齊,則表示已經遍歷到了棧的底部,循環結束。

在每次打印堆棧地址后,調用touch_nmi_watchdog函數,用于觸發非屏蔽中斷(NMI)看門狗,以確保系統不會因為長時間占用CPU而被認為是死鎖。

最后,啟用搶占(preempt_enable),打印換行符,然后調用show_trace_log_lvl函數,將任務結構體指針、寄存器上下文、棧指針、棧幀指針和日志級別作為參數傳遞,繼續打印堆棧跟蹤信息。

2.3 show_trace_log_lvl

// linux-3.10/arch/x86/kernel/dumpstack.cvoid
show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,unsigned long *stack, unsigned long bp, char *log_lvl)
{printk("%sCall Trace:\n", log_lvl);dump_trace(task, regs, stack, bp, &print_trace_ops, log_lvl);
}
Call Trace:
[1119269.645012]  [<ffffffff8e781340>] dump_stack+0x19/0x1b
[1119269.645021]  [<ffffffffc0a5000e>] func_c+0xe/0x10 [helloworld]
[1119269.645028]  [<ffffffffc0a5001e>] func_b+0xe/0x10 [helloworld]
[1119269.645034]  [<ffffffffc0a5002e>] func_a+0xe/0x10 [helloworld]
[1119269.645041]  [<ffffffffc0153009>] lkm_init+0x9/0x1000 [helloworld]
[1119269.645049]  [<ffffffff8e00210a>] do_one_initcall+0xba/0x240
[1119269.645059]  [<ffffffff8e11e45a>] load_module+0x271a/0x2bb0
[1119269.645066]  [<ffffffff8e3b4290>] ? ddebug_proc_write+0x100/0x100
[1119269.645075]  [<ffffffff8e119fe3>] ? copy_module_from_fd.isra.44+0x53/0x150
[1119269.645083]  [<ffffffff8e11ead6>] SyS_finit_module+0xa6/0xd0
[1119269.645093]  [<ffffffff8e793f92>] system_call_fastpath+0x25/0x2a

2.4 dump_trace

(1)

// linux-3.10/arch/x86/kernel/dumpstack_64.c/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/void dump_trace(struct task_struct *task, struct pt_regs *regs,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data)
{const unsigned cpu = get_cpu();unsigned long *irq_stack_end =(unsigned long *)per_cpu(irq_stack_ptr, cpu);unsigned used = 0;struct thread_info *tinfo;int graph = 0;unsigned long dummy;if (!task)task = current;if (!stack) {if (regs)stack = (unsigned long *)regs->sp;else if (task != current)stack = (unsigned long *)task->thread.sp;elsestack = &dummy;}if (!bp)bp = stack_frame(task, regs);/** Print function call entries in all stacks, starting at the* current stack address. If the stacks consist of nested* exceptions*/tinfo = task_thread_info(task);for (;;) {char *id;unsigned long *estack_end;estack_end = in_exception_stack(cpu, (unsigned long)stack,&used, &id);if (estack_end) {if (ops->stack(data, id) < 0)break;bp = ops->walk_stack(tinfo, stack, bp, ops,data, estack_end, &graph);ops->stack(data, "<EOE>");/** We link to the next stack via the* second-to-last pointer (index -2 to end) in the* exception stack:*/stack = (unsigned long *) estack_end[-2];continue;}if (irq_stack_end) {unsigned long *irq_stack;irq_stack = irq_stack_end -(IRQ_STACK_SIZE - 64) / sizeof(*irq_stack);if (in_irq_stack(stack, irq_stack, irq_stack_end)) {if (ops->stack(data, "IRQ") < 0)break;bp = ops->walk_stack(tinfo, stack, bp,ops, data, irq_stack_end, &graph);/** We link to the next stack (which would be* the process stack normally) the last* pointer (index -1 to end) in the IRQ stack:*/stack = (unsigned long *) (irq_stack_end[-1]);irq_stack_end = NULL;ops->stack(data, "EOI");continue;}}break;}/** This handles the process stack:*/bp = ops->walk_stack(tinfo, stack, bp, ops, data, NULL, &graph);put_cpu();
}
EXPORT_SYMBOL(dump_trace);

dump_trace函數用于在給定任務的堆棧上進行跟蹤,并通過提供的回調函數執行相應的操作。

函數首先定義了一些局部變量,包括cpu、irq_stack_end、used、tinfo和graph,以及一個dummy變量。

然后,根據情況,確定要跟蹤的任務和堆棧的起始地址。如果沒有給定任務,則默認使用當前任務。如果沒有給定堆棧地址,則根據情況選擇使用寄存器上下文的棧指針、任務的線程結構體中的棧指針,或者一個臨時變量作為棧指針。

接下來,如果沒有給定基指針(bp),則通過調用stack_frame函數計算基指針。

在一個無限循環中,函數根據堆棧的類型進行處理。首先,通過調用in_exception_stack函數檢查堆棧是否屬于異常堆棧(如雙重故障、NMI、堆棧故障、調試、MCE等),并獲取異常堆棧的結束地址(estack_end)以及用于標識堆棧的字符串(id)。

如果堆棧屬于異常堆棧(接上文)

如果堆棧屬于異常堆棧,將調用回調函數ops->stack(data, id)打印堆棧標識符,并通過調用ops->walk_stack函數執行堆棧的遍歷操作。然后,再次調用ops->stack(data, “”)打印異常堆棧的結束標識符。之后,通過異常堆棧的倒數第二個指針(索引為-2)獲取下一個堆棧的起始地址,并繼續下一輪循環。

如果堆棧不屬于異常堆棧,將檢查是否存在中斷堆棧(IRQ stack)。如果存在中斷堆棧,將通過調用in_irq_stack函數判斷當前堆棧是否屬于中斷堆棧,并獲取中斷堆棧的起始地址。如果當前堆棧屬于中斷堆棧,則與異常堆棧類似,調用回調函數打印中斷標識符,并通過ops->walk_stack函數執行中斷堆棧的遍歷操作。然后,通過中斷堆棧的最后一個指針(索引為-1)獲取下一個堆棧的起始地址,并繼續下一輪循環。

如果既不是異常堆棧也不是中斷堆棧,表示已經遍歷完所有堆棧,退出循環。

最后,通過調用ops->walk_stack函數處理進程堆棧,并完成整個跟蹤過程。最后,調用put_cpu()釋放當前CPU的引用計數。

該函數使用了一些其他函數和數據結構,例如task_thread_info函數用于獲取線程信息,stack_frame函數用于計算基指針,in_exception_stack和in_irq_stack函數用于判斷堆棧類型。回調函數ops->stack用于打印堆棧標識符,回調函數ops->walk_stack用于執行堆棧的遍歷操作。

(2)

// linux-3.10/arch/x86/include/asm/stacktrace.h/* Generic stack tracer with callbacks */struct stacktrace_ops {void (*address)(void *data, unsigned long address, int reliable);/* On negative return stop dumping */int (*stack)(void *data, char *name);walk_stack_t	walk_stack;
};
/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/static inline int valid_stack_ptr(struct thread_info *tinfo,void *p, unsigned int size, void *end)
{void *t = tinfo;if (end) {if (p < end && p >= (end-THREAD_SIZE))return 1;elsereturn 0;}return p > t && p < t + THREAD_SIZE - size;
}unsigned long
print_context_stack(struct thread_info *tinfo,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data,unsigned long *end, int *graph)
{struct stack_frame *frame = (struct stack_frame *)bp;while (valid_stack_ptr(tinfo, stack, sizeof(*stack), end)) {unsigned long addr;addr = *stack;if (__kernel_text_address(addr)) {if ((unsigned long) stack == bp + sizeof(long)) {ops->address(data, addr, 1);frame = frame->next_frame;bp = (unsigned long) frame;} else {ops->address(data, addr, 0);}print_ftrace_graph_addr(addr, data, ops, tinfo, graph);}stack++;}return bp;
}
EXPORT_SYMBOL_GPL(print_context_stack);static int print_trace_stack(void *data, char *name)
{printk("%s <%s> ", (char *)data, name);return 0;
}void printk_address(unsigned long address, int reliable)
{pr_cont(" [<%p>] %s%pB\n",(void *)address, reliable ? "" : "? ", (void *)address);
}/** Print one address/symbol entries per line.*/
static void print_trace_address(void *data, unsigned long addr, int reliable)
{touch_nmi_watchdog();printk(data);printk_address(addr, reliable);
}static const struct stacktrace_ops print_trace_ops = {.stack			= print_trace_stack,.address		= print_trace_address,.walk_stack		= print_context_stack,
};

2.5 print_context_stack

/* The form of the top of the frame on the stack */
struct stack_frame {struct stack_frame *next_frame;unsigned long return_address;
};
/** x86-64 can have up to three kernel stacks:* process stack* interrupt stack* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack*/static inline int valid_stack_ptr(struct thread_info *tinfo,void *p, unsigned int size, void *end)
{void *t = tinfo;if (end) {if (p < end && p >= (end-THREAD_SIZE))return 1;elsereturn 0;}return p > t && p < t + THREAD_SIZE - size;
}unsigned long
print_context_stack(struct thread_info *tinfo,unsigned long *stack, unsigned long bp,const struct stacktrace_ops *ops, void *data,unsigned long *end, int *graph)
{struct stack_frame *frame = (struct stack_frame *)bp;while (valid_stack_ptr(tinfo, stack, sizeof(*stack), end)) {unsigned long addr;addr = *stack;if (__kernel_text_address(addr)) {if ((unsigned long) stack == bp + sizeof(long)) {ops->address(data, addr, 1);frame = frame->next_frame;bp = (unsigned long) frame;} else {ops->address(data, addr, 0);}print_ftrace_graph_addr(addr, data, ops, tinfo, graph);}stack++;}return bp;
}
EXPORT_SYMBOL_GPL(print_context_stack);

print_context_stack函數用于在給定線程的堆棧上打印函數調用的地址,并通過提供的回調函數執行相應的操作。

函數首先定義了局部變量frame,它是一個指向struct stack_frame類型的指針,用于表示幀結構。

然后,使用一個循環遍歷堆棧中的每個地址。在每次循環迭代中,函數檢查堆棧指針是否有效,并獲取當前堆棧指針處的地址。

如果地址屬于內核文本空間(通過__kernel_text_address函數判斷),則進行以下操作:

  1. 如果當前堆棧指針等于基指針加上一個long大小,表示該地址是當前函數調用的返回地址。在這種情況下,將調用回調函數ops->address(data, addr, 1)打印地址,并更新幀結構和基指針,使其指向上一幀的基指針。
  2. 如果當前堆棧指針不等于基指針加上一個long大小,表示該地址是普通的函數調用地址。在這種情況下,將調用回調函數ops->address(data, addr, 0)打印地址。
  3. 最后,調用print_ftrace_graph_addr函數打印與地址相關的ftrace圖形信息。

在每次循環迭代后,將堆棧指針指向下一個地址。

最后,函數返回更新后的基指針。

參考資料

Linux 3.10.0

https://blogs.oracle.com/linux/post/unwinding-stack-frame-pointers-and-orc
https://blog.csdn.net/Longyu_wlz/article/details/103327538

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

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

相關文章

Unity開發中導彈路徑散射的原理與實現

Unity開發中導彈路徑散射的原理與實現 前言邏輯原理代碼實現導彈自身腳本外部控制腳本 應用效果結語 前言 前面我們學習了導彈的追蹤的效果&#xff0c;但是在動畫或游戲中&#xff0c;我們經常可以看到導彈發射后的彈道是不規則的&#xff0c;扭扭曲曲的飛行&#xff0c;然后擊…

數字生態系統的演進與企業API管理的關鍵之路

數字生態系統的演進與企業API管理的關鍵之路 在數字化時代&#xff0c;企業正經歷著一場轉型的浪潮&#xff0c;而API&#xff08;應用程序編程接口&#xff09;扮演著至關重要的角色。API如同一座橋梁&#xff0c;將組織內部的價值轉化為可市場化的產品&#xff0c;從而增強企…

韓國站群服務器在全球網絡架構中的重要作用?

韓國站群服務器在全球網絡架構中的重要作用? 在全球互聯網的蓬勃發展中&#xff0c;站群服務器作為網絡架構的核心組成部分之一&#xff0c;扮演著至關重要的角色。韓國站群服務器以其卓越的技術實力、優越的地理位置、穩定的網絡基礎設施和強大的安全保障能力&#xff0c;成…

LeetCode 題目 118:楊輝三角

題目描述 給定一個非負整數 numRows&#xff0c;生成楊輝三角的前 numRows 行。在楊輝三角中&#xff0c;每個數是它左上方和右上方的數的和。 楊輝三角解析 在這個詳解中&#xff0c;我們將使用 ASCII 圖形來說明楊輝三角的構建過程&#xff0c;包括逐行添加新的行的過程。…

250 基于matlab的5種時頻分析方法((短時傅里葉變換)STFT

基于matlab的5種時頻分析方法&#xff08;(短時傅里葉變換)STFT,Gabor展開和小波變換,Wigner-Ville&#xff08;WVD&#xff09;,偽Wigner-Ville分布(PWVD),平滑偽Wigner-Ville分布&#xff08;SPWVD&#xff09;,每條程序都有詳細的說明&#xff0c;設置仿真信號進行時頻輸出。…

Parted分區大容量磁盤

創建了新的虛擬磁盤10T , 掛載后分區格式化一.fdisk無法創建大容量的分區 Fileserver:~ # fdisk /dev/sdb Welcome to fdisk (util-linux 2.29.2). Changes will remain in memory only, until you decide to write them. Be careful before using the write command. Device …

使用html和css實現個人簡歷表單的制作

根據下列要求&#xff0c;做出下圖所示的個人簡歷&#xff08;表單&#xff09; 表單要求 Ⅰ、表格整體的邊框為1像素&#xff0c;單元格間距為0&#xff0c;表格中前六列列寬均為100像素&#xff0c;第七列 為200像素&#xff0c;表格整體在頁面上居中顯示&#xff1b; Ⅱ、前…

git提交代碼異常報錯error:bad signature 0x00000000

報錯信息 error:bad signature 0x00000000 異常原因 git 提交過程中異常關機或重啟&#xff0c;造成當前項目工程中的.git/index 文件損壞&#xff0c;無法提交 解決步驟 刪除.git/index文件 rm -f .git/index 重啟git git reset

Java 【數據結構】 哈希(Hash超詳解)HashSetHashMap【神裝】

登神長階 第十神裝 HashSet 第十一神裝 HashMap 目錄 &#x1f454;一.哈希 &#x1f9e5;1.概念 &#x1fa73;2.Object類的hashCode()方法: &#x1f45a;3.String類的哈希碼: &#x1f460;4.注意事項: &#x1f3b7;二.哈希桶 &#x1fa97;1.哈希桶原理 &#x…

Bert基礎(二十二)--Bert實戰:對話機器人

一 、概念簡介 1.1 生成式對話機器人 1.1.1什么是生成式對話機器人? 生成式對話機器人是一種能夠通過自然語言交互來理解和生成響應的人工智能系統。它們能夠進行開放域的對話,即在對話過程中,機器人可以根據用戶的需求和上下文信息,自主地生成新的、連貫的回復,而不僅…

如何使用CertCrunchy從SSL證書中發現和識別潛在的主機名稱

關于CertCrunchy CertCrunchy是一款功能強大的網絡偵查工具&#xff0c;該工具基于純Python開發&#xff0c;廣大研究人員可以利用該工具輕松從SSL證書中發現和識別潛在的主機信息。 支持的在線源 該工具支持從在線源或給定IP地址范圍獲取SSL證書的相關數據&#xff0c;并檢索…

大數據測試

1、前言 大數據測試是對大數據應用程序的測試過程&#xff0c;以確保大數據應用程序的所有功能按預期工作。大數據測試的目標是確保大數據系統在保持性能和安全性的同時&#xff0c;平穩無差錯地運行。 大數據是無法使用傳統計算技術處理的大型數據集的集合。這些數據集的測試涉…

Foxmail使用經驗總結

本篇博客將詳盡講解如何利用Foxmail進行高效的郵件管理&#xff0c;以及一些實用的使用技巧&#xff0c;讓郵件管理變得更為高效和有序。 1. 賬戶設置與管理 多賬戶整合&#xff1a;Foxmail支持多個郵件賬戶同時管理&#xff0c;用戶可以將個人和工作郵箱整合在同一個界面&am…

實戰中使用 QEMU 進行內網穿透

前言 閱讀 https://xz.aliyun.com/t/14052 《使用 QEMU 進行內網穿透&#xff1f;》 https://securelist.com/network-tunneling-with-qemu/111803/ 《Network tunneling with… QEMU?》 我將此項技術應用到實戰中&#xff0c;取得不錯的效果&#xff0c;但是也遇到很多坑&am…

機器學習算法應用——樸素貝葉斯分類器

樸素貝葉斯分類器 樸素貝葉斯分類器&#xff08;Naive Bayes Classifier&#xff09;是一種基于貝葉斯定理和特征條件獨立假設的分類方法。它適用于分類任務&#xff0c;特別是文本分類、垃圾郵件識別等領域。 原理 樸素貝葉斯分類器基于以下兩個主要假設&#xff1a; 特征條…

JS_ES6(1)

作用域鏈&#xff1a; 作用域鏈是底層變量查找的機制&#xff1a;當函數執行時&#xff0c;優先查找當前函數作用域中有無需要用到的變量&#xff0c;如果找不到&#xff0c;逐級查找父級&#xff0c;直到全局 > 嵌套關系形成作用域鏈&#xff0c;同一作用域鏈從小到大查找…

taro3兼容支付寶/微信小程序的自定義拖拽排序組件

描述&#xff1a;列表可以完成拖拽排序 此組件是根據支付寶原生文檔改編成taro-vue3的形式&#xff0c;只保留了拖拽的部分&#xff0c;其他功能都去除了&#xff0c;測試下來可以兼容支付寶和微信小程序。 支付寶原生文檔&#xff1a; https://opendocs.alipay.com/support/…

BGP(border gateway protocol)邊界網關協議初識篇

BGP它是一種路徑矢量協議&#xff0c;用于決定數據包在互聯網中的最佳路徑。 1、工作原理&#xff1a; 自治系統&#xff08;AS&#xff09;間路由: BGP主要用于連接不同自治系統之間的路由器&#xff0c;其中每個自治系統&#xff08;AS&#xff09;代表一組具有共同路由的網…

編譯 fdk-aac

文章目錄 關于 fdk-aac編譯 fdk-aac在 FFMpeg 編譯中啟用 關于 fdk-aac A standalone library of the Fraunhofer FDK AAC code from Android. github &#xff1a; https://github.com/mstorsjo/fdk-aac代碼托管 &#xff1a; https://sourceforge.net/projects/opencore-am…

最新巨量X-Bogus、_signature參數逆向分析與算法還原

文章目錄 1. 寫在前面2. 接口分析3. 斷點分析4. 扣代碼補環境5. 數據解密 【&#x1f3e0;作者主頁】&#xff1a;吳秋霖 【&#x1f4bc;作者介紹】&#xff1a;擅長爬蟲與JS加密逆向分析&#xff01;Python領域優質創作者、CSDN博客專家、阿里云博客專家、華為云享專家。一路…