Linux .eh_frame section以及libunwind

news2024/11/17 7:23:27

文章目录

  • 前言
  • 一、LSB
  • 二、The .eh_frame section
    • 2.1 简介
    • 2.2 The Common Information Entry Format
      • 2.1.1 Augmentation String Format
    • 2.3 The Frame Description Entry Format
  • 三、The .eh_frame_hdr section
  • 四、libunwind
  • 五、基于Frame Pointer和基于unwind 形式的栈回溯比较
  • 参考资料

前言

基于FP的栈回溯请参考:
Linux x86_64 基于FP栈回溯
Linux ARM64 基于FP栈回溯

基于FP栈回溯需要一个专门寄存器RBP来保存frame poniter。
gcc优化选项 -O 默认使用-fomit-frame-pointer编译标志进行优化,省略帧指针。将寄存器RBP作为一个通用的寄存器来使用。

-fomit-frame-pointer 是GCC编译器的一个编译选项。当启用该选项时,它告诉编译器在不需要基指针的函数中省略基指针。通过省略基指针,编译器避免了保存、设置和恢复基指针的指令,从而使生成的代码更小、更快。

省略基指针还提供了一个额外的通用寄存器可供使用,这对于具有有限通用寄存器数量的架构(如x86)非常有用。

这样就不能基于FP来进行栈回溯了,Linux通过.eh_frame节可以来进行栈回溯,.eh_frame节通常由编译器(如GCC)在编译可执行文件或共享库时生成。调试器(如GDB)能够读取并解析这些节,以提供强大的调试功能。

在x86_64体系架构上,大多数软件在编译的时采用了gcc的默认选项,而gcc的默认选项不启用函数帧指针FP,而是把RBP寄存器作为一个通用的寄存器,以及无法进行FP进行栈回溯,因此对于用户空间程序,通常使用.eh_frame section 来进行栈回溯。

.eh_frame段中存储着跟函数入栈相关的关键数据。
当函数执行入栈指令后,在该段会保存跟入栈指令一一对应的编码数据,
根据这些编码数据,就能计算出当前函数栈大小和cpu的哪些寄存器入栈了,在栈中什么位置。

无论是否有-g选项,gcc默认都会生成.eh_frame和.eh_frame_hdr section。

一、LSB

Linux Standard Base(LSB)定义了编译应用程序的系统接口和支持安装脚本的最小环境。其目的是为符合LSB的大规模应用程序提供统一的行业标准环境。

LSB规范由两个基本部分组成:一个通用部分,描述了在LSB的所有实现中保持不变的接口部分;以及一个特定于体系结构的部分,描述了根据处理器体系结构而变化的接口部分。通用部分和特定于体系结构的部分共同为具有相同硬件体系结构的系统上的编译应用程序提供了完整的接口规范。

LSB包含一组应用程序接口(API)和应用程序二进制接口(ABI)。API可以出现在可移植应用程序的源代码中,而该应用程序的编译二进制文件可以使用更大的一组ABIs。符合规范的实现提供了这里列出的所有ABIs。编译系统可以通过替换(例如通过宏定义)某些API,将其调用转换为一个或多个底层二进制接口的调用,并根据需要插入对二进制接口的调用。

LSB是由Linux Foundation组织架构下的多个Linux发行版共同参与的项目,旨在标准化软件系统结构,包括文件系统层次结构(Filesystem Hierarchy Standard)。LSB基于POSIX规范、Single UNIX Specification(SUS)和其他几个开放标准,但在某些领域进行了扩展。

根据LSB:
LSB的目标是开发和推广一组开放标准,增加Linux发行版之间的兼容性,并使软件应用程序能够在任何符合标准的系统上运行,即使是以二进制形式。此外,LSB还将协调努力,吸引软件供应商为Linux操作系统移植和编写产品。

二、The .eh_frame section

2.1 简介

在Linux系统中,.eh_frame节是一种特殊的节(section),用于存储程序的调试信息和堆栈回溯相关的信息。
这个节通常在可执行文件或共享库中存在,以支持运行时的调试和异常处理。

当程序在Linux系统中进行异常处理和堆栈展开时,会使用到.eh_frame节。.eh_frame节是基于DWARF(Debugging With Attributed Record Formats)调试格式的一部分。

.eh_frame节的主要作用是提供运行时支持,用于正确展开函数调用堆栈。它存储了一系列编码的调用帧信息,这些信息在异常处理或进行堆栈回溯时起到关键作用。

在异常发生或需要进行堆栈回溯时,运行时系统会利用.eh_frame节中的信息来展开堆栈。它会遵循编码的CFI(Call Frame Information)指令序列,逐层遍历堆栈帧,获取返回地址,并找到对应的异常处理程序或回溯信息。

# readelf -S a.out
共有 30 个节头,从偏移量 0x1930 开始:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  ......
  [16] .eh_frame_hdr     PROGBITS         00000000004005c0  000005c0
       000000000000003c  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000400600  00000600
       0000000000000114  0000000000000000   A       0     0     8
  ......
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
A (alloc)

.eh_frame带有SHF_ALLOC flag(标志一个section是否应为内存中镜像的一部分)。

.eh_frame节包含了用于栈回溯和异常处理的数据结构,其中包括编码了调用帧信息、异常处理表和其他相关数据的指令序列。这些信息用于在程序运行时进行堆栈展开(stack unwinding),即在异常发生时回溯函数调用堆栈以查找异常处理程序。这些结构可以在程序运行时被调试器或其他工具使用。它们提供了关于函数调用链、寄存器状态和局部变量等信息的详细描述,以便进行调试和错误诊断。

当程序中包含异常处理机制(如C++异常)或使用与堆栈相关的特性(如backtrace函数)时,编译器会生成和使用.eh_frame节。这些信息允许运行时系统在异常处理期间正确地展开函数调用堆栈,并将控制权传递给适当的异常处理程序。

尽管.eh_frame增加了可执行文件的大小,但它提供了重要的运行时支持和调试功能。然而,对于某些嵌入式系统或特定的应用程序,可能需要最小化可执行文件的大小,并且不需要异常处理和调试功能。在这种情况下,可以使用编译器选项(如-fno-asynchronous-unwind-tables)来禁用.eh_frame的生成,以减少可执行文件的大小。

.eh_frame中的数据结构通常使用一种称为DWARF(Debugging With Arbitrary Record Formats)的格式进行编码。
DWARF是一种调试信息格式,广泛用于Linux系统和其他类Unix系统中。它定义了一组规范,用于描述程序的调试信息,包括函数、类型、变量、源代码映射等。

通过解析.eh_frame节中的DWARF数据,调试器可以还原函数调用堆栈,获取函数的参数和局部变量值,以及跟踪函数调用的路径。这对于调试复杂的程序、分析错误和优化代码非常有帮助。

.eh_frame节应包含一个或多个调用帧信息(CFI - Call Frame Information)记录。存在的记录数量应由节头中包含的节大小确定。每个CFI记录包含一个通用信息条目(CIE - Common Information Entry)记录,后面跟着一个或多个帧描述条目(FDE - Frame Description Entry)记录。CIE和FDE都应对齐到地址单元大小的边界。

Call Frame Information Format:

----------------------------------
Common Information Entry Record
----------------------------------
Frame Description Entry Record(s)
----------------------------------

如下图所示:
在这里插入图片描述

2.2 The Common Information Entry Format

Common Information Entry Format:

LengthRequired
Extended LengthOptional
CIE IDRequired
VersionRequired
Augmentation StringRequired
Code Alignment FactorRequired
Data Alignment FactorRequired
Return Address RegisterRequired
Augmentation Data LengthOptional
Augmentation DataOptional
Initial InstructionsRequired
Padding
// libunwind/include/dwarf.h

typedef struct dwarf_cie_info
  {
    unw_word_t cie_instr_start; /* start addr. of CIE "initial_instructions" */
    unw_word_t cie_instr_end;   /* end addr. of CIE "initial_instructions" */
    unw_word_t fde_instr_start; /* start addr. of FDE "instructions" */
    unw_word_t fde_instr_end;   /* end addr. of FDE "instructions" */
    unw_word_t code_align;      /* code-alignment factor */
    unw_word_t data_align;      /* data-alignment factor */
    unw_word_t ret_addr_column; /* column of return-address register */
    unw_word_t handler;         /* address of personality-routine */
    uint16_t abi;
    uint16_t tag;
    uint8_t fde_encoding;
    uint8_t lsda_encoding;
    unsigned int sized_augmentation : 1;
    unsigned int have_abi_marker : 1;
    unsigned int signal_frame : 1;
  }
dwarf_cie_info_t;

(1)Length
一个4字节的无符号值,表示CIE结构的长度(以字节为单位),不包括Length字段本身。如果Length字段的值为0xffffffff,则长度包含在Extended Length字段中。如果Length字段的值为0,则此CIE应被视为终止符,并且处理将结束。

(2)Extended Length
这个8字节的无符号值表示CIE结构的字节长度,不包括长度字段和扩展长度字段本身。除非长度字段包含值0xffffffff,否则该字段不存在。

(3)CIE ID
这个4字节的无符号值用于区分CIE(Common Information Entry)记录和FDE(Frame Description Entry)记录。该值应始终为0,表示该记录是一个CIE。

static inline int
is_cie_id (unw_word_t val, int is_debug_frame)
{
  /* The CIE ID is normally 0xffffffff (for 32-bit ELF) or
     0xffffffffffffffff (for 64-bit ELF).  However, .eh_frame
     uses 0.  */
  if (is_debug_frame)
      return (val == (uint32_t)(-1) || val == (uint64_t)(-1));
  else
    return (val == 0);
}

(4)Version
这个1字节的值用于标识帧信息结构的版本号。该值应为1。

  /* Read the return-address column either as a u8 or as a uleb128.  */
  if (version == 1)
    {
      if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0)
        return ret;
      dci->ret_addr_column = ch;
    }

(5)Augmentation String
这个值是一个以NUL(空字符)结尾的字符串,用于标识与该CIE或与该CIE关联的FDE的增强信息。如果字符串长度为零,则表示没有增强数据存在。增强字符串是区分大小写的,并且应按照下面的描述进行解释。

  /* read and parse the augmentation string: */
  memset (augstr, 0, sizeof (augstr));
  for (i = 0;;)
    {
      if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0)
        return ret;

      if (!ch)
        break;  /* end of augmentation string */

      if (i < sizeof (augstr) - 1)
        augstr[i++] = ch;
    }

(6)Code Alignment Factor
这个值是一个无符号的LEB128编码值,它被从与该CIE或其FDE关联的所有"advance location"指令中分解出来。该值应与"advance location"指令的增量参数相乘,以获得新的位置值。

(7)Data Alignment Factor
这个值是一个带符号的LEB128编码值,它被从与该CIE或其FDE关联的所有偏移指令中分解出来。该值应与偏移指令的寄存器偏移参数相乘,以获得新的偏移值。

  if ((ret = dwarf_read_uleb128 (as, a, &addr, &dci->code_align, arg)) < 0
      || (ret = dwarf_read_sleb128 (as, a, &addr, &dci->data_align, arg)) < 0)
    return ret;

(8)Augmentation Length
这个值是一个无符号的LEB128编码值,用于表示增强数据的字节长度。只有当增强字符串中包含字符’z’时,该字段才存在。

(9)Augmentation Data
这是一个数据块,其内容由增强字符串中的内容定义,具体描述如下。只有当增强字符串中包含字符’z’时,该字段才存在。该数据块的大小由增强长度(Augmentation Length)指定。

(9)Initial Instructions
这是初始的调用帧指令集。指令的数量由CIE记录中剩余的空间确定。

(10)Padding
这些额外的字节用于将CIE结构对齐到地址单元大小边界。

2.1.1 Augmentation String Format

增强字符串指示了一些可选字段的存在以及如何解释这些字段。该字符串区分大小写。CIE中增强字符串中的每个字符的解释如下:

‘z’:
字符串的第一个字符可以是’z’。如果存在,则增强数据字段也必须存在。增强数据的内容将根据增强字符串中的其他字符进行解释。

‘L’:
字符串的第一个字符是’z’时,可以在任何位置上出现’L’。如果存在,它表示CIE的增强数据中存在一个参数,并且FDE的增强数据中也存在相应的参数。CIE的增强数据中的参数是1字节,表示用于FDE的增强数据中的参数的指针编码,该参数是指向特定语言数据区(LSDA)的地址。LSDA指针的大小由使用的指针编码指定。

‘P’:
字符串的第一个字符是’z’时,可以在任何位置上出现’P’。如果存在,它表示CIE的增强数据中存在两个参数。第一个参数是1字节,表示用于第二个参数的指针编码,该参数是指向人格例程处理程序的地址。人格例程用于处理特定语言和供应商的任务。系统解旋库接口通过指向人格例程的指针访问特定语言的异常处理语义。个性例程没有ABI-specific的名称。个性例程指针的大小由使用的指针编码指定。

‘R’:
字符串的第一个字符是’z’时,可以在任何位置上出现’R’。如果存在,则增强数据中应包含一个1字节的参数,该参数表示FDE中使用的地址指针的指针编码。

  i = 0;
  if (augstr[0] == 'z')
    {
      dci->sized_augmentation = 1;
      if ((ret = dwarf_read_uleb128 (as, a, &addr, &aug_size, arg)) < 0)
        return ret;
      i++;
    }

  for (; i < sizeof (augstr) && augstr[i]; ++i)
    switch (augstr[i])
      {
      case 'L':
        /* read the LSDA pointer-encoding format.  */
        if ((ret = dwarf_readu8 (as, a, &addr, &ch, arg)) < 0)
          return ret;
        dci->lsda_encoding = ch;
        break;

      case 'R':
        /* read the FDE pointer-encoding format.  */
        if ((ret = dwarf_readu8 (as, a, &addr, &fde_encoding, arg)) < 0)
          return ret;
        break;

      case 'P':
        /* read the personality-routine pointer-encoding format.  */
        if ((ret = dwarf_readu8 (as, a, &addr, &handler_encoding, arg)) < 0)
          return ret;
        if ((ret = dwarf_read_encoded_pointer (as, a, &addr, handler_encoding,
                                               pi, &dci->handler, arg)) < 0)
          return ret;
        break;

      case 'S':
        /* This is a signal frame. */
        dci->signal_frame = 1;

        /* Temporarily set it to one so dwarf_parse_fde() knows that
           it should fetch the actual ABI/TAG pair from the FDE.  */
        dci->have_abi_marker = 1;
        break;

      default:
        Debug (1, "Unexpected augmentation string `%s'\n", augstr);
        if (dci->sized_augmentation)
          /* If we have the size of the augmentation body, we can skip
             over the parts that we don't understand, so we're OK. */
          goto done;
        else
          return -UNW_EINVAL;
      }

2.3 The Frame Description Entry Format

Frame Description Entry Format:

LengthRequired
Extended LengthOptional
CIE PointerRequired
PC BeginRequired
PC RangeRequired
Augmentation Data LengthOptional
Augmentation DataOptional
Call Frame InstructionsRequired
Padding

(1)Length
一个4字节的无符号值,表示FDE(Frame Description Entry)结构的长度(以字节为单位),不包括Length字段本身。如果Length字段的值为0xffffffff,则长度包含在Extended Length字段中。如果Length字段的值为0,则该FDE应被视为终止器,并且处理过程应该结束。

(2)Extended Length
一个8字节的无符号值,表示FDE(Frame Description Entry)结构的长度(以字节为单位),不包括Length字段或Extended Length字段本身。除非Length字段的值为0xffffffff,否则该字段不会出现。

(3)CIE Pointer
一个4字节的无符号值,从当前FDE中的CIE指针的偏移量中减去,得到关联CIE的起始偏移量。该值永远不应为0。

(4)PC Begin
一个编码值,表示与该FDE关联的初始位置的地址。编码格式在增强数据(Augmentation Data)中指定。

(5)PC Range
一个绝对值,表示与该FDE关联的指令字节数。

(6)Augmentation Length
一个无符号 LEB128 编码值,表示增强数据的字节长度。只有在关联的CIE中的增强字符串包含字符 ‘z’ 时,该字段才存在。

(7)Augmentation Data
一个数据块,其内容由关联CIE中的增强字符串的内容所定义,如上所述。只有当关联CIE中的增强字符串包含字符 ‘z’ 时,该字段才存在。该数据块的大小由增强长度(Augmentation Length)给出。

(8)Call Frame Instructions
一组调用帧指令(Call Frame Instructions)。

(9)Padding
用于将FDE(Frame Description Entry)结构对齐到一个地址单元大小边界的额外字节。

三、The .eh_frame_hdr section

.eh_frame_hdr 段包含有关 .eh_frame 段的额外信息。该段中包含了指向 .eh_frame 数据起始位置的指针,以及可选的指向 .eh_frame 记录的二进制搜索表。

定位一个pc所在的FDE需要从头扫描.eh_frame,找到合适的FDE(pc是否落在initial_location和address_range表示的区间),所花时间和扫描的CIE和FDE记录数相关。 .eh_frame_hdr包含binary search index table描述(initial_location, FDE address) pairs。

.eh_frame_hdr Section Format:

EncodingField
unsigned byteversion
unsigned byteeh_frame_ptr_enc
unsigned bytefde_count_enc
unsigned bytetable_enc
encodedeh_frame_ptr
encodedfde_count
binary search table

(1)version
.eh_frame_hdr 格式的版本。该值应为 1。

(2)eh_frame_ptr_enc
eh_frame_ptr字段的编码格式。

(3)fde_count_enc
fde_count 字段的编码格式。DW_EH_PE_omit 的值表示二进制搜索表不存在。

(4)table_enc
二进制搜索表中条目的编码格式。DW_EH_PE_omit 的值表示二进制搜索表不存在。

(5)eh_frame_ptr
指向.eh_frame部分开头的指针的编码值。

(6)fde_count
二进制搜索表中条目数的编码值。

(7)binary search table
一个包含 fde_count 个条目的二进制搜索表。每个表条目包含两个编码值,即初始位置和地址。这些条目按照初始位置的值按升序排序。

四、libunwind

libunwind 是一个可移植且高效的 C API,用于确定 ELF 程序线程的当前调用链,并可以在该调用链的任何点上恢复执行。该 API 支持本地(同一进程)和远程(其他进程)操作。用于显示引发问题的调用链的回溯信息,或用于性能监控和分析。

libunwind的使用比较简单:

#define UNW_LOCAL_ONLY

#include <libunwind.h>
#include <stdio.h>
#include <stdlib.h>

#define panic(...)				\
	{ fprintf (stderr, __VA_ARGS__); exit (-1); }


static void do_backtrace (void)
{
  unw_cursor_t cursor;
  unw_word_t ip, sp;
  unw_context_t uc;
  int ret;


  unw_getcontext (&uc);
  if (unw_init_local (&cursor, &uc) < 0)
    panic ("unw_init_local failed!\n");

  do{
      unw_get_reg (&cursor, UNW_REG_IP, &ip);
      unw_get_reg (&cursor, UNW_REG_SP, &sp);

      char fname[64];
      unw_word_t  offset;
      unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);

	  printf ("(ip=%016lx) (sp=%016lx): (%s+0x%x) [%p]\n", (long) ip, (long) sp, fname, offset, (long) sp);

      ret = unw_step (&cursor);
      if (ret < 0)
        {
            unw_get_reg (&cursor, UNW_REG_IP, &ip);
            panic ("FAILURE: unw_step() returned %d for ip=%lx\n",
                ret, (long) ip);
        }
    }while (ret > 0);
}

void func_c(void)
{
	do_backtrace();	
}

void func_b(void)
{
	func_c();	
}

void func_a(void)
{
	func_b();
}


int main (int argc, char **argv)
{
  func_a ();
  return 0;
}
# gcc 1.c -lunwind
# ./a.out
(ip=0000000000400897) (sp=00007fffc131ce40): (do_backtrace+0x1a) [0x7fffc131ce40]
(ip=00000000004009f0) (sp=00007fffc131d660): (func_c+0x9) [0x7fffc131d660]
(ip=00000000004009fb) (sp=00007fffc131d670): (func_b+0x9) [0x7fffc131d670]
(ip=0000000000400a06) (sp=00007fffc131d680): (func_a+0x9) [0x7fffc131d680]
(ip=0000000000400a1c) (sp=00007fffc131d690): (main+0x14) [0x7fffc131d690]
(ip=00007ff1df9a2555) (sp=00007fffc131d6b0): (__libc_start_main+0xf5) [0x7fffc131d6b0]
(ip=00000000004007b9) (sp=00007fffc131d770): (+0xf5) [0x7fffc131d770]

五、基于Frame Pointer和基于unwind 形式的栈回溯比较

(1)基于Frame Pointer - fp寄存器的栈回溯:
优点:栈回溯比较快,理解简单。相对较简单:基于Frame Pointer寄存器的栈回溯通常比解析unwind节更简单直接。
缺点:gcc添加了优化选项 -O 就会省略掉省略基指针。这样就不能都通过这种形式进行栈回溯了。
-fomit-frame-pointer编译标志进行优化:避免将%rbp用作栈帧指针,把FP当作一个通用寄存器,这样就提供了一个额外的通用寄存器,提高程序运行效率。
通用寄存器用来暂存数据和参与运算。通过load\store指令操作。

如果把fp寄存器当作栈帧寄存器,那就不能参与指令数据运算,CPU寄存器是很宝贵的,多一个寄存器对加快指令数据运算是有积极意义的。

(2)基于unwind 形式的栈回溯:
优点:只是将入栈相关的指令的编码保存到unwind段中,不用把无关的寄存器保存到栈中,也不用浪费fp寄存器。
把FP当作一个通用寄存器,这样就提供了一个额外的通用寄存器,提高程序运行效率。
更准确:unwind节中的调试信息提供了更详细的函数调用和栈帧信息,可以更准确地还原函数调用链和参数传递。
不受优化影响:unwind节通常包含了编译器生成的准确信息,不受编译器优化选项的影响。
提供更多调试功能:unwind节提供了丰富的调试信息,可以用于更深入的调试和错误诊断。

缺点:栈回溯的速度肯定比fp形式栈回溯慢,理解难度要比fp形式大很多。
复杂性:解析和使用unwind节的调试信息可能需要更多的工具和技术知识。

参考资料

https://refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html#EHFRAME
https://github.com/libunwind/libunwind

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1684275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

紫光展锐突破创新终端品类,搭载展锐芯的全球首款二合一5G云电脑正式发布

近日&#xff0c;搭载紫光展锐5G芯片T760的中兴云电脑逍遥系列正式发布&#xff0c;亮点&#xff1a; 全球首款二合一5G云电脑&#xff0c;支持本地/云端双模式&#xff0c;一键切换&#xff0c;用户可同时享有Android平板和Windows云电脑两种形态&#xff1b;支持5G蜂窝网络&…

LLama3 | 一. 本地 Web Demo 部署

前置工作 课程文档&#xff1a;Llama3-Tutorial/docs/hello_world.md at main SmartFlowAI/Llama3-Tutorial GitHub 1.安装vscode 2.安装vscode插件 Remote SSH 3.配置 VSCode 远程连接开发机 ssh连接开发机 进行端口映射 在开发机控制台中点击自定义服务&#xff0c;复…

DeepDriving | CUDA编程-02: 初识CUDA编程

本文来源公众号“DeepDriving”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;CUDA编程-02&#xff1a; 初识CUDA编程 上一篇文章DeepDriving | CUDA编程-01&#xff1a; 搭建CUDA编程环境-CSDN博客介绍了如何搭建CUDA编程环境&a…

阿里云数据库 SelectDB 版全面商业化,开启现代化实时数据仓库的全新篇章

2024 年 5 月 21 日&#xff0c;由阿里云联合飞轮科技共同举办的「阿里云数据库 SelectDB 版商业化产品发布会」于线上召开。阿里巴巴集团副总裁、阿里云数据库产品事业部负责人李飞飞宣布&#xff0c;阿里云数据库 SelectDB 版在中国站及国际站全面发布&#xff0c;正式开启商…

5. JVM面试题汇总

Java全栈面试题汇总目录-CSDN博客 1. 说一下JVM的主要组成部分及其作用? JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 Cl…

Kibanna安装配置

环境&#xff1a;windows10、ES&#xff08;8.13.3&#xff09;、Kibana&#xff08;8.13.3&#xff09;、Logstash&#xff08;8.13.3&#xff09; 1.Kibanna安装配置 Kibanna对ES的数据进行可视化、分析和监控 Download Kibana Free | Get Started Now | ElasticDownload K…

零部件销售|基于SSM+vue的轻型卡车零部件销售平台系统的设计与实现(源码+数据库+文档)

轻型卡车零部件销售平台 目录 基于SSM&#xff0b;vue的轻型卡车零部件销售平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1 系统功能模块 2 管理员功能模块 3 用户后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题…

MavLinK协议

由于在公司需要使用这个&#xff0c;我就写一个文章用于入门级别 简单介绍 MAVSDK是PX4开源团队贡献的基于mavlink通信协议的用于无人机应用开发的SDK&#xff0c;其可以部署在Windows、Linux、Android等多种平台&#xff0c;并且支持多种语言如c/c、python、Java等。 在官网…

5月30日在线研讨会 | 面向智能网联汽车的产教融合解决方案

随着智能网联汽车技术的快速发展&#xff0c;产业对高素质技术技能人才的需求日益增长。为了促进智能网联汽车行业的健康发展&#xff0c;推动教育链、人才链与产业链、创新链的深度融合&#xff0c;经纬恒润推出产教融合相关方案&#xff0c;旨在通过促进教育链与产业链的深度…

XV4001KD汽车级应用的数字输出陀螺传感器

XV4001KD是一款专为汽车导航系统和远程信息处理而设计的数字输出陀螺传感器。采用SPI/I2C串行接口&#xff0c;具有高精度的16位的角速率输出和11位的温度输出功能&#xff0c;能够准确地测量车辆的运动状态和环境温度&#xff0c;为导航系统和信息处理提供可靠的数据支持。以及…

深度学习基础之《TensorFlow框架(18)—卷积神经网络(2)》

一、卷积层 1、卷积层&#xff08;Convolutional Layer&#xff09;介绍 卷积神经网络中每层卷积层由若干卷积单元&#xff08;卷积核&#xff09;组成&#xff0c;每个卷积单元的参数都是通过反向传播算法最佳化得到的 卷积运算的目的是特征提取&#xff0c;第一层卷积层可能…

word页眉线如何置于文字上方

然后 敲黑板&#xff0c;点这里

学硕都考11408的211院校!河北工业大学计算机考研考情分析!

河北工业大学&#xff08;Hebei University of Technology&#xff09;&#xff0c;简称河北工大&#xff0c;坐落于天津市&#xff0c;由河北省人民政府、天津市人民政府与中华人民共和国教育部共建&#xff0c; 隶属于河北省&#xff0c;是国家“双一流”建设高校、国家“211…

Linux磁盘高级操作

RAID RAID存储系统是一种数据存储虚拟化技术&#xff0c;它将多个物理磁盘驱动器组合成一个或多个逻辑单元&#xff0c;以提供数据冗余和/或提高性能。 1. RAID 0 无奇偶校验与冗余&#xff08;磁盘容错&#xff09;的条带存储&#xff08;带区卷/条带卷&#xff09; 由两块…

科技巨头的下一个目标:Web3与物联网融合

引言 随着技术的不断演进和创新&#xff0c;科技巨头们正在将目光聚焦到Web3与物联网的融合领域&#xff0c;这将开启一个全新的数字时代。本文将深入探讨科技巨头们在这一领域的动向&#xff0c;以及融合可能带来的影响和未来发展方向。 Web3与物联网的融合趋势 技术发展的趋…

【手写大跟堆详解】

文章目录 大跟堆介绍大跟堆的结构大跟堆的应用场景大跟堆的代码实现 大跟堆介绍 大根堆&#xff08;Max Heap&#xff09;是一种特殊的二叉树结构&#xff0c;它满足以下两个条件&#xff1a; 1.完全二叉树&#xff1a;大根堆是一棵完全二叉树&#xff0c;即除了最后一层外&am…

Web Server项目实战4-服务器编程基本框架和2种高效的事件处理模式

服务器编程基本框架 虽然服务器程序种类繁多&#xff0c;但其基本框架都一样&#xff0c;不同之处在于逻辑处理 模块功能I/O处理单元处理客户连接&#xff0c;读写网络数据逻辑单元业务进程或线程网络存储单元数据库、文件或缓存请求队列各单元之间的通信方式 I/O 处理单元是…

520主题趣味小游戏玩法线上互动的作用是什么

行业商家借势520气氛&#xff0c;往往能低成本达到预期效果&#xff0c;包括但不限于品牌传播、渠道引流涨粉、用户促活引导等&#xff0c;除了前面推荐的互动玩法外&#xff0c;在【雨科】平台的这几款520趣味小游戏同样值得关注。 1、爱你不止520 这是一款九宫格抽奖活动&am…

【Java面试】三、Redis篇(下)

文章目录 1、抢券场景2、Redis分布式锁3、Redisson实现分布式锁4、Redisson实现的分布式锁是可重入锁5、Redisson实现分布式锁下的主从一致性6、面试 1、抢券场景 正常思路&#xff1a; 代码实现&#xff1a; 比如优惠券数量为1。正常情况下&#xff1a;用户A的请求过来&a…

利用神经网络学习语言(六)——总结与常见面试问题

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 文章列表&#xff1a; 利用神经网络学习语言&#xff08;一&#xff09;——自然语言处理的基本要素利用神经网络学习语言&…