Linux .eh_frame section以及libunwind

文章目录

  • 前言
  • 一、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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络】初识Tcp协议

&#x1f4bb;文章目录 &#x1f4c4;前言Tcp基础概念Tcp 的报文格式三次握手四次挥手 Tcp的滑动窗口机制概念超时重传机制高速重传 TCP传输控制机制流量控制拥堵控制慢启动 Tcp的性能优化机制延迟应答捎带应答 &#x1f4d3;总结 &#x1f4c4;前言 TCP三次握手、四次挥手&…

【qt】QListWidget 组件

QListWidget 组件 一.QListWidget的用途二.界面设计三.QListWidget的添加1.界面添加2.代码添加 四.列表项的设置1.文本2.图标3.复选框4.列表大小 五.字体和图标的设置1.字体&#xff1a;2.图标&#xff1a; 六.设置显示模式1.图标2.列表 七.其他功能实现1.删除2.全选3.反选4.ad…

IO端口编址

统一编址 特点 独立编址 特点 内存地址分配 区别 应用 IO端口地址译码 硬件上的实现 示例1&#xff1a; 示例2&#xff1a; IO指令 软件上的实现 示例

Vue - JavaScript基础学习

一、语言概述 JavaScript 中的类型应该包括这些&#xff1a; 1.数字&#xff08;默认双精度区别于Java&#xff09; console.log(3 / 2); // 1.5,not 1 console.log(Math.floor(3 / 2)); // 10.1 0.2 0.30000000000000004NaN&#xff08;Not a Number&#x…

为什么 buffer 越大传输效率越低

先看 从边际效益递减看 buffer 中挤占带宽 中的两个模型&#xff1a; E1 inflight_prop - inflight_buff&#xff1a; y 2 t x − b x a − x y2tx-\dfrac{bx}{a-x} y2tx−a−xbx​E2 bw / delay&#xff1a; y a x − x 2 b t a − t x y\dfrac{ax-x^2}{bta-tx} ybta−…

OpenMV学习笔记1——IDE安装与起步

目录 一、OpenMV IDE下载 二、OpenMV界面 三、Hello World&#xff01; 四、将代码烧录到OpenMV实现脱机运行 五、插SD卡&#xff08;为什么买的时候没送&#xff1f;&#xff09; 一、OpenMV IDE下载 浏览器搜索OpenMV官网&#xff0c;进入后点击“立即下载”&#xff0…

深度学习基于Tensorflow卷积神经网络VGG16的CT影像识别分类

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着医疗技术的快速发展&#xff0c;CT&#xff08;Computed Tomography&#xff09;影像已成为医生…

面试准备【面试准备】

面试准备【面试准备】 前言面试准备自我介绍&#xff1a;项目介绍&#xff1a; 论坛项目功能总结数据库表设计注册功能登录功能显示登录信息功能发布帖子评论私信点赞功能关注功能通知搜索网站数据统计热帖排行缓存 论坛项目技术总结Http的无状态cookie和session的区别为什么要…

Linux-应用编程学习笔记(二、文件I/O、标准I/O)

一、文件I/O基础 文件 I/O 指的是对文件的输入/输出操作&#xff0c;就是对文件的读写操作。Linux 下一切皆文件。 1.1 文件描述符 在 open函数执行成功的情况下&#xff0c; 会返回一个非负整数&#xff0c; 该返回值就是一个文件描述符&#xff08;file descriptor&#x…

Python3 笔记:sort() 和 sorted() 的区别

1、sort() 可以对列表中的元素进行排序&#xff0c;会改变原列表&#xff0c;之前的顺序不复存在。 list.sort&#xff08;key&#xff0c; reverse None&#xff09; key&#xff1a;默认值是None&#xff0c;可指定项目进行排序&#xff0c;此参数可省略。 reverse&#…

零基础PHP入门(一)选择IDE和配置环境

配置环境 官网下载安装包&#xff0c;windows https://windows.php.net/download#php-8.3 我是下载的最新版&#xff0c;也可以切换其他版本 https://windows.php.net/downloads/releases/archives/ 下载好压缩文件后&#xff0c;双击解压到一个目录 D:\soft\php 复制ph…

Vue 3 的 setup语法糖工作原理

前言 我们每天写vue3项目的时候都会使用setup语法糖&#xff0c;但是你有没有思考过下面几个问题。setup语法糖经过编译后是什么样子的&#xff1f;为什么在setup顶层定义的变量可以在template中可以直接使用&#xff1f;为什么import一个组件后就可以直接使用&#xff0c;无需…

匝间冲击耐压试验仪产品介绍及工作原理

产品简介 武汉凯迪正大KD2684S匝间冲击耐压试验仪适用于电机、变压器、电器线圈等这些由漆包线绕制的产品。因漆包线的绝缘涂敷层本身存在着质量问题&#xff0c;以及在绕线、嵌线、刮线、接头端部整形、绝缘浸漆、装配等工序工艺中不慎而引起绝缘层的损伤等&#xff0c;都会造…

零基础代码随想录【Day42】|| 1049. 最后一块石头的重量 II,494. 目标和,474.一和零

目录 DAY42 1049.最后一块石头的重量II 解题思路&代码 494.目标和 解题思路&代码 474.一和零 解题思路&代码 DAY42 1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度&#xff1a;中等 有一堆石头&#xff0c;每块石头的重量都是正整…

Axure软件安装教程

链接&#xff1a;https://pan.baidu.com/s/1fHrSrZ7PIeDZZpn6QyJ6jQ?pwdb4mv 提取码&#xff1a;b4mv 安装完后点击Finish 名字随便起 关闭Axure 复制到安装目录下 最后成果

ASP+ACCESS基于WEB社区论坛设计与实现

摘要&#xff1a;系统主要实现BBS网站全部功能。采用目前应用最为广泛的ASP作为开发工具来开发此系统、以保证系统的稳定性。采用目前最为流行的网页制作工具Dreamweaver和目前最为流行的动画制作工具Flash MX。整个系统从符合操作简便、界面友好、灵活、实用、安全的要求出发&…

第七步 实现打印函数

文章目录 前言一、如何设计我们的打印函数&#xff1f;二、实践检验&#xff01; 查看系列文章点这里&#xff1a; 操作系统真象还原 前言 现在接力棒意见交到内核手中啦&#xff0c;只不过我们的内核现在可谓是一穷二白啥都没有&#xff0c;为了让我们设计的内核能被看见被使用…

uniapp微信小程序在ios端返回不显示弹窗的bug解决

这个问题其实是因为返回页面的时候弹的太快了导致的解决办法&#xff1a; 其实就是返回页面的弹窗加个延迟就好啦

电脑同时配置两个版本mysql数据库常见问题

1.配置时&#xff0c;要把bin中的mysql.exe和mysqld.exe 改个名字&#xff0c;不然两个版本会重复&#xff0c;当然&#xff0c;在初始化数据库的时候&#xff0c;如果时57版本的&#xff0c;就用mysql57(已经改名的)和mysqld57 代替 mysql 和 mysqld 例如 mysql -u root -p …

golang通过go-aci适配神通数据库

1. go-aci简介 go-aci是神通数据库基于ACI(兼容Oracle的OCI)开发的go语言开发接口&#xff0c;因此运行时需要依赖ACI驱动和ACI库的头文件。支持各种数据类型的读写、支持参数绑定、支持游标范围等操作。 2. Linux部署步骤 2.1. Go安装&#xff1a; 版本&#xff1a;1.9以上…