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.mzph.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博客专家、阿里云博客专家、华为云享专家。一路…