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  000005c0000000000000003c  0000000000000000   A       0     0     4[17] .eh_frame         PROGBITS         0000000000400600  000006000000000000000114  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.htypedef 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) or0xffffffffffffffff (for 64-bit ELF).  However, .eh_frameuses 0.  */if (is_debug_frame)return (val == (uint32_t)(-1) || val == (uint64_t)(-1));elsereturn (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 thatit 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 skipover the parts that we don't understand, so we're OK. */goto done;elsereturn -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.mzph.cn/bicheng/14691.shtml

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

相关文章

双向链表C++,C#,Java版,这些程序大多已经过测试,一直在用。

先C版吧&#xff0c;我最先用的是C#,后来是Java&#xff0c;后来改用C版的&#xff0c;因为现在一直在用C&#xff0c;单链 表一直没写上去&#xff0c;因为我很少用&#xff0c;用的是双链表。 执行代码例子1&#xff1a; int main() { _DList<_string> s…

9.STL中list的常见操作(图文并茂)

目录 1.list的介绍及使用 1.1.list的构造 1.2 list iterator的使用 1.3. list capacity 1.4.list modifiers 1.5.list的迭代器失效 1.list的介绍及使用 list介绍 &#xff0c;可以通过以下图直观的感受到 vector 和 list 的区别 Vector 插入代价高&#xff0c;但便于排…

力扣HOT100 - 72. 编辑距离

解题思路&#xff1a; 动态规划 class Solution {public int minDistance(String word1, String word2) {int n1 word1.length();int n2 word2.length();int[][] dp new int[n1 1][n2 1];for (int j 1; j < n2; j) dp[0][j] dp[0][j - 1] 1;for (int i 1; i < …

《艺术大观》知网艺术刊:可加急, 出刊上网快

《艺术大观》 《艺术大观》征文通知 《艺术大观》期刊诚邀学者、艺术家和文化工作者积极投稿&#xff0c;共同探索艺术领域的前沿问题&#xff0c;促进学术交流和艺术创作的发展。我们欢迎各类艺术形式的研究与评论&#xff0c;包括但不限于绘画、雕塑、音乐、舞蹈、戏剧、电…

【数据结构】排序详解(希尔排序,快速排序,堆排序,插入排序,选择排序,冒泡排序)

目录 0. 前情提醒&#xff1a; 1. 插入排序 1.1 基本思想&#xff1a; 1.2 直接插入排序 实现步骤&#xff1a; 动图演示&#xff1a; 特性总结&#xff1a; 代码实现&#xff1a; 1.3 希尔排序&#xff08;缩小增量排序&#xff09; 基本思想&#xff1a; 步骤演示&…

AI大模型如何赋能智能座舱

AI 大模型如何赋能智能座舱 从上海车展上&#xff0c;我们看到由于智能座舱配置性价比较高&#xff0c;已经成为车企的核心竞争点之一&#xff0c;随着座舱硬件规模化装车&#xff0c;蔚小理、岚图、极狐等新势力开始注重座舱多模态交互&#xff0c;通过集成语音/手势/触控打造…

Leetcode—2769. 找出最大的可达成数字【简单】

2024每日刷题&#xff08;139&#xff09; Leetcode—2769. 找出最大的可达成数字 实现代码 class Solution { public:int theMaximumAchievableX(int num, int t) {return num t * 2;} };运行结果 之后我会持续更新&#xff0c;如果喜欢我的文章&#xff0c;请记得一键三连…

【实战】SpringBoot整合Websocket、Redis实现Websocket集群负载均衡

文章目录 前言技术积累什么是Websocket什么是Redis发布订阅Redis发布订阅与消息队列的区别 实战演示SpringBoot整合WebsoketWebsoket集群负载均衡 实战测试IDEA启动两台服务端配置nginx负载均衡浏览器访问模拟对话 前言 相信很多同学都用过websocket来实现服务端主动向客户端推…

【知识蒸馏】deeplabv3 logit-based 知识蒸馏实战,对剪枝的模型进行蒸馏训练

本文将对【模型剪枝】基于DepGraph(依赖图)完成复杂模型的一键剪枝 文章中剪枝的模型进行蒸馏训练 一、逻辑蒸馏步骤 加载教师模型定义蒸馏loss计算蒸馏loss正常训练 二、代码 1、加载教师模型 教师模型使用未进行剪枝&#xff0c;并且已经训练好的原始模型。 teacher_mod…

利用Python去除PDF水印

摘要 本文介绍了如何使用 Python 中的 PyMuPDF 和 OpenCV 库来从 PDF 文件中移除水印&#xff0c;并将每个页面保存为图像文件的方法。我们将深入探讨代码背后的工作原理&#xff0c;并提供一个简单的使用示例。 导言 简介&#xff1a;水印在许多 PDF 文件中都很常见&#x…

全国数据库管理系统设计赛-人大金仓内核实训安排正式发布

作为数据库领域国家队&#xff0c;人大金仓积极响应国家战略&#xff0c;通过赛题设计、内核技术支撑及赛前培训等多方面&#xff0c;大力支持全国大学生计算机系统能力大赛-数据库管理系统设计大赛成功举办。目前第二届全国大赛正在火热报名中&#xff0c;各种奖项等你来拿&am…

《web应用设计》第八次作业

我的小组长是姚若希&#xff0c;我们组课程设计的题目是&#xff1a;学生管理系统 &#xff0c;我认领的功能模块是&#xff1a;课程管理 2.查询并分页

只需三步,即可配置HTTPS跳转

HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket Layer&#xff09;&#xff0c;是以安全为目标的HTTP通道&#xff0c;简单讲是HTTP的安全版。通过SSL/TLS协议对数据进行加密&#xff0c;保证了数据传输的安全&#xff0c;防止数据被截获、…

UWB论文:Introduction to Impulse Radio UWB Seamless Access Systems(2):脉冲;超宽带;测距;定位

3) 测距/接收器 像全球定位系统&#xff08;GPS&#xff09;这样的系统依赖于单向测距One Way Ranging&#xff08;OWR&#xff09;&#xff0c;其中多个卫星&#xff08;代表固定节点&#xff0c;称为锚点anchors&#xff09;定期传输同步的无线电数据包集合&#xff0c;这允许…

sh控制台输入文字多行 按“# ꧂ ꧁”结束

如果在Unix shell中输入多行文字&#xff0c;那么这样操作&#xff1a; 1. 打开您的终端&#xff08;Terminal&#xff09;。 2. 输入您的文字&#xff0c;每行文字后按回车键。 3. 当您完成输入所有文字后&#xff0c;输入“# ꧂ ꧁”然后按回车键&#xff0c;表示输入结束。…

将Surface的分辨率减半以省电(二合一本\笔记本电脑适用)

【完全自定义分辨率教程】这篇教程用于将Surface之类的高分屏&#xff08;高分辨率&#xff09;的二合一本或笔记本等的分辨率调整为原来的一半&#xff0c;以实现省电等目的。 下载CRU&#xff08;Custom Resolution Utility&#xff09;解压后&#xff0c;打开CRU.exe选择当…

Java期末复习指南(1):知识点总结+思维导图,考试速成!

&#x1f516;面向对象 &#x1f4d6; Java作为面向对象的编程语言&#xff0c;我们首先必须要了解类和对象的概念&#xff0c;本章的所有内容和知识都是围绕类和对象展开的&#xff01; ▐ 思维导图1 ▐ 类和对象的概念 • 简单来说&#xff0c;类就是对具有相同特征的一类事…

(全面)Nginx格式化插件,Nginx生产工具,Nginx常用命令

目录 &#x1f3ab; 前言 &#x1f389; 开篇福利 &#x1f381; 开篇福利 x2 Double happiness # 介绍 # 地址 # 下载 &#x1f4bb; 命令及解析 # 整个文件系统中搜索名为nginx.conf的文件 # 编辑nginx.conf文件 # 重新加载配置文件 # 快速查找nginx.conf文件并使…

建筑施工突发事故应急处置vr安全培训平台

在不断发展的时代背景下&#xff0c;掌握必要的应急安全知识已成为我们生活中不可或缺的一部分。由央企携手我们华锐推出的3D线上应急宣教虚拟体验馆&#xff0c;标志着民众应急安全教育的全新里程碑&#xff0c;不仅突破了传统学习模式的局限&#xff0c;还让每个人都能在灵活…

防火墙技术基础篇:基于IP地址的转发策略

防火墙技术基础篇&#xff1a;基于IP地址的转发策略的应用场景及实现 什么是基于IP地址的转发策略&#xff1f; 基于IP地址的转发策略是一种网络管理方法&#xff0c;它允许根据目标IP地址来选择数据包的转发路径。这种策略比传统的基于目的地地址的路由更灵活&#xff0c;因…