当前位置: 首页 > news >正文

鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main

阅读之前的说明

先说明,本篇很长,也很枯燥,若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子,用readelf命令去窥视ELF的全貌,最后用objdump命令反汇编ELF.找到了大家熟悉main函数.

开始之前先说结论:

  • ELF 分四块,其中三块是描述信息(也叫头信息),另一块是内容,放的是所有段/区的内容.
    1. ELF头定义全局性信息
    1. Segment(段)头,内容描述段的名字,开始位置,类型,偏移,大小及每段由哪些区组成.
    1. 内容区,ELF有两个重要概念 Segment(段) 和 Section(区),段比区大,二者之间关系如下:
    • 每个Segment可以包含多个Section
    • 每个Section可以属于多个Segment
    • Segment之间可以有重合的部分
    • 拿大家熟知的.text.data.bss举例,它们都叫区,但它们又属于LOAD段.
    1. Section(区)头,内容描述区的名字,开始位置,类型,偏移,大小等信息
  • ELF一体两面,面对不同的场景扮演不同的角色,这是理解ELF的关键,链接器只关注1,3(区),4 的内容,加载器只关注1,2,3(段)的内容
  • 鸿蒙对EFL的定义在 kernel\extended\dynload\include\los_ld_elf_pri.h文件中

示例代码

在windows目录E:\harmony\docker\case_code_100下创建 main.c文件,如下:

#include <stdio.h>
void say_hello(char *who)
{printf("hello, %s!\n", who);
}
char *my_name = "harmony os";int main()
{say_hello(my_name);return 0;
}    

做好了环境映射,所以文件会同时出现在docker中.编译生成ELF->运行->readelf -h查看app头部信息.

root@5e3abe332c5a:/home/docker/case_code_100# ls
main.c
root@5e3abe332c5a:/home/docker/case_code_100# gcc -o app main.c
root@5e3abe332c5a:/home/docker/case_code_100# ls
app  main.c
root@5e3abe332c5a:/home/docker/case_code_100# ./app
hello, harmony os!

名正才言顺

一下是关于ELF的所有中英名词对照.建议先仔细看一篇再看系列篇部分.

可执行可连接格式 : ELF(Executable and Linking Format)
ELF文件头:ELF header
基地址:base address
动态连接器: dynamic linker
动态连接: dynamic linking
全局偏移量表: got(global offset table)
进程链接表: plt(Procedure Linkage Table) 
哈希表: hash table
初始化函数 : initialization function
连接编辑器 : link editor
目标文件 : object file
函数连接表 : procedure linkage table
程序头: program header
程序头表 : program header table
程序解析器 : program interpreter
重定位: relocation
共享目标 : shared object
区(节): section
区(节)头 : section header
区(节)表: section header table
段 : segment
字符串表 : string table
符号表: symbol table
终止函数 : termination function

ELF历史

  • ELF(Executable and Linking Format),即"可执行可连接格式",最初由UNIX系统实验室(UNIX System Laboratories – USL)做为应用程序二进制接口(Application Binary Interface - ABI)的一部分而制定和发布.是鸿蒙的主要可执行文件格式.

  • ELF的最大特点在于它有比较广泛的适用性,通用的二进制接口定义使之可以平滑地移植到多种不同的操作环境上.这样,不需要为每一种操作系统都定义一套不同的接口,因此减少了软件的重复编码与编译,加强了软件的可移植性.

ELF整体布局

ELF规范中把ELF文件宽泛地称为"目标文件 (object file)",这与我们平时的理解不同.一般地,我们把经过编译但没有连接的文件(比如Unix/Linux上的.o文件)称为目标文件,而ELF文件仅指连接好的可执行文件;在ELF规范中,所有符合ELF格式规范的都称为ELF文件,也称为目标文件,这两个名字是相同的,而经过编译但没有连接的文件则称为"可重定位文件 (relocatable file)“或"待重定位文件 (relocatable file)”.本文采用与此规范相同的命名方式,所以当提到可重定位文件时,一般可以理解为惯常所说的目标文件;而提到目标文件时,即指各种类型的ELF文件.

ELF格式可以表达四种类型的二进制对象文件(object files):

  • 可重定位文件(relocatable file),用于与其它目标文件进行连接以构建可执行文件或动态链接库.可重定位文件就是常说的目标文件,由源文件编译而成,但还没有连接成可执行文件.在UNIX系统下,一般有扩展名".o".之所以称其为"可重定位",是因为在这些文件中,如果引用到其它目标文件或库文件中定义的符号(变量或者函数)的话,只是给出一个名字,这里还并不知道这个符号在哪里,其具体的地址是什么.需要在连接的过程中,把对这些外部符号的引用重新定位到其真正定义的位置上,所以称目标文件为"可重定位"或者"待重定位"的.
  • 可执行文件(executable file)包含代码和数据,是可以直接运行的程序.其代码和数据都有固定的地址 (或相对于基地址的偏移),系统可根据这些地址信息把程序加载到内存执行.
  • 共享目标文件(shared object file),即动态连接库文件.它在以下两种情况下被使用:第一,在连接过程中与其它动态链接库或可重定位文件一起构建新的目标文件;第二,在可执行文件被加载的过程中,被动态链接到新的进程中,成为运行代码的一部分.包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的.
  • 核心转储文件(core dump file,就是core dump文件)
可重定位文件用在编译和链接阶段.
可执行文件用在程序运行阶段.
共享库则同时用在编译链接和运行阶段,本篇 app 就是个 DYN,可直接运行.Type:                              DYN (Shared object file)

在不同阶段,我们可以用不同视角来理解ELF文件,整体布局如下图所示:

从上图可见,ELF格式文件整体可分为四大部分:

  • ELF Header: 在文件的开始,描述整个文件的组织.即readelf -h app看到的内容
  • Program Header Table: 告诉系统如何创建进程映像.用来构造进程映像的目标文件必须具有程序头部表,可重定位文件可以不需要这个表.表描述所有段(Segment)信息,即readelf -l app看到的前半部分内容.
  • Segments:段(Segment)由若干区(Section)组成.是从加载器角度来描述 ELF 文件.加载器只关心 ELF headerProgram header tableSegment 这三部分内容。 在加载阶段可以忽略 section header table 来处理程序(所以很多加固手段删除了section header table
  • Sections: 是从链接器角度来描述 ELF 文件. 链接器只关心 ELF headerSections 以及 Section header table 这三部分内容。在链接阶段,可以忽略 program header table 来处理文件.
  • Section Header Table:描述区(Section)信息的数组,每个元素对应一个区,通常包含在可重定位文件中,可执行文件中为可选(通常包含) 即readelf -S app看到的内容
  • 从图中可以看出 Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段.

ELF头信息

ELF头部信息对应鸿蒙源码结构体为 LDElf32Ehdr, 各字段含义已一一注解,很容易理解.

//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Elf header */
#define LD_EI_NIDENT           16
typedef struct {UINT8       elfIdent[LD_EI_NIDENT]; /* Magic number and other info *///含前16个字节,又可细分成class、data、version等字段,具体含义不用太关心,只需知道前4个字节点包含`ELF`关键字,这样可以判断当前文件是否是ELF格式UINT16      elfType;                /* Object file type *///表示具体ELF类型,可重定位文件/可执行文件/共享库文件UINT16      elfMachine;             /* Architecture *///表示cpu架构UINT32      elfVersion;             /* Object file version *///表示文件版本号UINT32      elfEntry;               /* Entry point virtual address *///对应`Entry point address`,程序入口函数地址,通过进程虚拟地址空间地址表达UINT32      elfPhoff;               /* Program header table file offset *///对应`Start of program headers`,表示program header table在文件内的偏移位置UINT32      elfShoff;               /* Section header table file offset *///对应`Start of section headers`,表示section header table在文件内的偏移位置UINT32      elfFlags;               /* Processor-specific flags *///表示与CPU处理器架构相关的信息UINT16      elfHeadSize;            /* ELF header size in bytes *///对应`Size of this header`,表示本ELF header自身的长度UINT16      elfPhEntSize;           /* Program header table entry size *///对应`Size of program headers`,表示program header table中每个元素的大小UINT16      elfPhNum;               /* Program header table entry count *///对应`Number of program headers`,表示program header table中元素个数UINT16      elfShEntSize;           /* Section header table entry size *///对应`Size of section headers`,表示section header table中每个元素的大小UINT16      elfShNum;               /* Section header table entry count *///对应`Number of section headers`,表示section header table中元素的个数UINT16      elfShStrIndex;          /* Section header string table index *///对应`Section header string table index`,表示描述各section字符名称的string table在section header table中的下标
} LDElf32Ehdr;
root@5e3abe332c5a:/home/docker/case_code_100# readelf -h app
ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              DYN (Shared object file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x1060Start of program headers:          64 (bytes into file)Start of section headers:          14784 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)Size of program headers:           56 (bytes)Number of program headers:         13Size of section headers:           64 (bytes)Number of section headers:         31Section header string table index: 30

解读

显示的信息,就是 ELF header 中描述的所有内容了。这个内容与结构体 LDElf32Ehdr 中的成员变量是一一对应的!
Size of this header: 64 (bytes)也就是说:ELF header 部分的内容,一共是 64 个字节。64个字节码长啥样可以用命令od -Ax -t x1 -N 64 app看,并对照结构体LDElf32Ehdr来理解.

root@5e3abe332c5a:/home/docker/case_code_100/51# od -Ax -t x1 -N 64 app
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 03 00 3e 00 01 00 00 00 60 10 00 00 00 00 00 00
000020 40 00 00 00 00 00 00 00 c0 39 00 00 00 00 00 00
000030 00 00 00 00 40 00 38 00 0d 00 40 00 1f 00 1e 00
000040

简单解释一下命令的几个选项:

-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
-N 64:只需要读取64个字节;

这里留意这几个内容,下面会说明,先记住.

Entry point address:               0x1060   //代码区 .text 起始位置,即程序运行开始位置
Size of program headers:           56 (bytes)//每个段头大小
Number of program headers:         13       //段数量
Size of section headers:           64 (bytes)//每个区头大小
Number of section headers:         31       //区数量
Section header string table index: 30       //字符串数组索引,该区记录所有区名称

段(Segment)头信息

段(Segment)信息对应鸿蒙源码结构体为 LDElf32Phdr

//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Program Header */
typedef struct {UINT32 type;     /* Segment type */	//段类型UINT32 offset;   /* Segment file offset */		//此数据成员给出本段内容在文件中的位置,即段内容的开始位置相对于文件开头的偏移量.UINT32 vAddr;    /* Segment virtual address */	//此数据成员给出本段内容的开始位置在进程空间中的虚拟地址.UINT32 phyAddr;  /* Segment physical address */	//此数据成员给出本段内容的开始位置在进程空间中的物理地址.对于目前大多数现代操作系统而言,应用程序中段的物理地址事先是不可知的,所以目前这个成员多数情况下保留不用,或者被操作系统改作它用.UINT32 fileSize; /* Segment size in file */		//此数据成员给出本段内容在文件中的大小,单位是字节,可以是0.UINT32 memSize;  /* Segment size in memory */	//此数据成员给出本段内容在内容镜像中的大小,单位是字节,可以是0.UINT32 flags;    /* Segment flags */			//此数据成员给出了本段内容的属性.UINT32 align;    /* Segment alignment */		//对于可装载的段来说,其p_vaddr和p_offset的值至少要向内存页面大小对齐.
} LDElf32Phdr;

解读
readelf -l查看app段头部表内容,先看命令返回的前半部分:

root@5e3abe332c5a:/home/docker/case_code_100# readelf -l app 
Elf file type is DYN (Shared object file)
Entry point 0x1060
There are 13 program headers, starting at offset 64
Program Headers:Type           Offset             VirtAddr           PhysAddrFileSiz            MemSiz              Flags  AlignPHDR           0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000002d8 0x00000000000002d8  R      0x8INTERP         0x0000000000000318 0x0000000000000318 0x00000000000003180x000000000000001c 0x000000000000001c  R      0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD           0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000618 0x0000000000000618  R      0x1000LOAD           0x0000000000001000 0x0000000000001000 0x00000000000010000x0000000000000225 0x0000000000000225  R E    0x1000LOAD           0x0000000000002000 0x0000000000002000 0x00000000000020000x0000000000000190 0x0000000000000190  R      0x1000LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000260 0x0000000000000268  RW     0x1000DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc80x00000000000001f0 0x00000000000001f0  RW     0x8NOTE           0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000020 0x0000000000000020  R      0x8NOTE           0x0000000000000358 0x0000000000000358 0x00000000000003580x0000000000000044 0x0000000000000044  R      0x4GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x00000000000003380x0000000000000020 0x0000000000000020  R      0x8GNU_EH_FRAME   0x000000000000201c 0x000000000000201c 0x000000000000201c0x000000000000004c 0x000000000000004c  R      0x4GNU_STACK      0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000  RW     0x10GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db80x0000000000000248 0x0000000000000248  R      0x1

数一下一共13个段,其实在ELF头信息也告诉了我们共13个段

Size of program headers:           56 (bytes)//每个段头大小
Number of program headers:         13       //段数量

仔细看下这些段的开始地址和大小,发现有些段是重叠的.那是因为一个区可以被多个段所拥有.例如:0x2db8 对应的 .init_array区就被第四LOADGNU_RELRO两段所共有.

PHDR,此类型header元素描述了program header table自身的信息.从这里的内容看出,示例程序的program header table在文件中的偏移(Offset)为0x40,即64号字节处.该段映射到进程空间的虚拟地址(VirtAddr)为0x40.PhysAddr暂时不用,其保持和VirtAddr一致.该段占用的文件大小FileSiz0x2d8.运行时占用进程空间内存大小MemSiz也为0x2d8.Flags标记表示该段的读写权限,这里R表示只读,Align对齐为8,表明本段按8字节对齐.

INTERP,此类型header元素描述了一个特殊内存段,该段内存记录了动态加载解析器的访问路径字符串.示例程序中,该段内存位于文件偏移0x318处,即紧跟program header table.映射的进程虚拟地址空间地址为0x318.文件长度和内存映射长度均为0x1c,即28个字符,具体内容为/lib64/ld-linux-x86-64.so.2.段属性为只读,并按字节对齐.

LOAD,此类型header元素描述了可加载到进程空间的代码区或数据区:

  • 其第二段包含了代码区,文件内偏移为0x1000,文件大小为0x225,映射到进程地址0x001000处,属性为只读可执行(RE),段地址按0x1000(4K)边界对齐.
  • 其第四段包含了数据区,文件内偏移为0x2db8,文件大小为0x260,映射到进程地址0x003db8处,属性为可读可写(RW),段地址也按0x1000(4K)边界对齐.

DYNAMIC,此类型header元素描述了动态加载段,其内部通常包含了一个名为.dynamic的动态加载区.这也是一个数组,每个元素描述了与动态加载相关的各方面信息,将在系列篇(动态加载篇)中介绍.该段是从文件偏移0x2dc8处开始,长度为0x1f0,并映射到进程的0x3dc8.可见该段和上一个段LOAD4 0x2db8是有重叠的.

GNU_STACK,可执行栈,即栈区,在加载段的过程中,当发现存在PT_GNU_STACK,也就是GNU_STACK segment 的存在,如果存在这个这个段的话,看这个段的 flags 是否有可执行权限,来设置对应的值.必须为RW方式.

再看命令返回内容的后半部分-段区映射关系

 Section to Segment mapping:Segment Sections...0001     .interp02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt03     .init .plt .plt.got .plt.sec .text .fini04     .rodata .eh_frame_hdr .eh_frame05     .init_array .fini_array .dynamic .got .data .bss06     .dynamic07     .note.gnu.property08     .note.gnu.build-id .note.ABI-tag09     .note.gnu.property10     .eh_frame_hdr1112     .init_array .fini_array .dynamic .got

13个段和31个区的映射关系,右边其实不止31个区,是因为一个区可以共属于多个段,例如 .dynamic.interp.got
Segment:Section(M:N)是多对多的包含关系.Segment是由多个Section组成,Section也能属于多个段.这个很重要,说第二遍了.

  • INTERP段只包含了.interp
  • LOAD2段包含.interp.plt.text等区,.text代码区位于这个段. 这个段是 'RE’属性,只读可执行的.
  • LOAD4包含.dynamic.data.bss等区, 数据区位于这个段.这个段是 'RW’属性,可读可写. .data.bss都是数据区,有何区别呢?
  • .data(ZI data)它用来存放初始化了的(initailized)全局变量(global)和初始化了的静态变量(static).
  • .bss(RW data )它用来存放未初始化的(uninitailized)全局变量(global)和未初始化的静态变量.
  • DYNAMIC段包含.dynamic区.

区表

区(section)头表信息对应鸿蒙源码结构体为 LDElf32Shdr

//kernel\extended\dynload\include\los_ld_elf_pri.h
/* Section header */
typedef struct {UINT32 shName;      /* Section name (string tbl index) *///表示每个区的名字UINT32 shType;      /* Section type *///表示每个区的功能UINT32 shFlags;     /* Section flags *///表示每个区的属性UINT32 shAddr;      /* Section virtual addr at execution *///表示每个区的进程映射地址UINT32 shOffset;    /* Section file offset *///表示文件内偏移UINT32 shSize;      /* Section size in bytes *///表示区的大小UINT32 shLink;      /* Link to another section *///Link和Info记录不同类型区的相关信息UINT32 shInfo;      /* Additional section information *///Link和Info记录不同类型区的相关信息UINT32 shAddrAlign; /* Section alignment *///表示区的对齐单位UINT32 shEntSize;   /* Entry size if section holds table *///表示区中每个元素的大小(如果该区为一个数组的话,否则该值为0)
} LDElf32Shdr;

示例程序共生成31个区.其实在头文件中也已经告诉我们了

Size of section headers:           64 (bytes)//每个区头大小
Number of section headers:         31       //区数量

通过readelf -S命令看看示例程序中 section header table的内容,如下所示.

root@5e3abe332c5a:/home/docker/case_code_100# readelf -S app
There are 31 section headers, starting at offset 0x39c0:Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000000318  00000318000000000000001c  0000000000000000   A       0     0     1[ 2] .note.gnu.propert NOTE             0000000000000338  000003380000000000000020  0000000000000000   A       0     0     8[ 3] .note.gnu.build-i NOTE             0000000000000358  000003580000000000000024  0000000000000000   A       0     0     4[ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c0000000000000020  0000000000000000   A       0     0     4[ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a00000000000000024  0000000000000000   A       6     0     8[ 6] .dynsym           DYNSYM           00000000000003c8  000003c800000000000000a8  0000000000000018   A       7     1     8[ 7] .dynstr           STRTAB           0000000000000470  000004700000000000000084  0000000000000000   A       0     0     1[ 8] .gnu.version      VERSYM           00000000000004f4  000004f4000000000000000e  0000000000000002   A       6     0     2[ 9] .gnu.version_r    VERNEED          0000000000000508  000005080000000000000020  0000000000000000   A       7     1     8[10] .rela.dyn         RELA             0000000000000528  0000052800000000000000d8  0000000000000018   A       6     0     8[11] .rela.plt         RELA             0000000000000600  000006000000000000000018  0000000000000018  AI       6    24     8[12] .init             PROGBITS         0000000000001000  00001000000000000000001b  0000000000000000  AX       0     0     4[13] .plt              PROGBITS         0000000000001020  000010200000000000000020  0000000000000010  AX       0     0     16[14] .plt.got          PROGBITS         0000000000001040  000010400000000000000010  0000000000000010  AX       0     0     16[15] .plt.sec          PROGBITS         0000000000001050  000010500000000000000010  0000000000000010  AX       0     0     16[16] .text             PROGBITS         0000000000001060  0000106000000000000001b5  0000000000000000  AX       0     0     16[17] .fini             PROGBITS         0000000000001218  00001218000000000000000d  0000000000000000  AX       0     0     4[18] .rodata           PROGBITS         0000000000002000  00002000000000000000001b  0000000000000000   A       0     0     4[19] .eh_frame_hdr     PROGBITS         000000000000201c  0000201c000000000000004c  0000000000000000   A       0     0     4[20] .eh_frame         PROGBITS         0000000000002068  000020680000000000000128  0000000000000000   A       0     0     8[21] .init_array       INIT_ARRAY       0000000000003db8  00002db80000000000000008  0000000000000008  WA       0     0     8[22] .fini_array       FINI_ARRAY       0000000000003dc0  00002dc00000000000000008  0000000000000008  WA       0     0     8[23] .dynamic          DYNAMIC          0000000000003dc8  00002dc800000000000001f0  0000000000000010  WA       7     0     8[24] .got              PROGBITS         0000000000003fb8  00002fb80000000000000048  0000000000000008  WA       0     0     8[25] .data             PROGBITS         0000000000004000  000030000000000000000018  0000000000000000  WA       0     0     8[26] .bss              NOBITS           0000000000004018  000030180000000000000008  0000000000000000  WA       0     0     1[27] .comment          PROGBITS         0000000000000000  00003018000000000000002a  0000000000000001  MS       0     0     1[28] .symtab           SYMTAB           0000000000000000  000030480000000000000648  0000000000000018          29    46     8[29] .strtab           STRTAB           0000000000000000  000036900000000000000216  0000000000000000           0     0     1[30] .shstrtab         STRTAB           0000000000000000  000038a6000000000000011a  0000000000000000           0     0     1
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)

String Table

在 ELF header 的最后 2 个字节是 0x1e 0x00,即30. 它对应结构体中的成员 elfShStrIndex,意思是这个 ELF 文件中,字符串表是一个普通的 Section,在这个 Section 中,存储了 ELF 文件中使用到的所有的字符串。
我们使用readelf -x读出下标30区的数据:

root@5e3abe332c5a:/home/docker/case_code_100# readelf -x 30 app Hex dump of section '.shstrtab':0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte0x00000020 7270002e 6e6f7465 2e676e75 2e70726f rp..note.gnu.pro0x00000030 70657274 79002e6e 6f74652e 676e752e perty..note.gnu.0x00000040 6275696c 642d6964 002e6e6f 74652e41 build-id..note.A0x00000050 42492d74 6167002e 676e752e 68617368 BI-tag..gnu.hash0x00000060 002e6479 6e73796d 002e6479 6e737472 ..dynsym..dynstr0x00000070 002e676e 752e7665 7273696f 6e002e67 ..gnu.version..g0x00000080 6e752e76 65727369 6f6e5f72 002e7265 nu.version_r..re0x00000090 6c612e64 796e002e 72656c61 2e706c74 la.dyn..rela.plt0x000000a0 002e696e 6974002e 706c742e 676f7400 ..init..plt.got.0x000000b0 2e706c74 2e736563 002e7465 7874002e .plt.sec..text..0x000000c0 66696e69 002e726f 64617461 002e6568 fini..rodata..eh0x000000d0 5f667261 6d655f68 6472002e 65685f66 _frame_hdr..eh_f0x000000e0 72616d65 002e696e 69745f61 72726179 rame..init_array0x000000f0 002e6669 6e695f61 72726179 002e6479 ..fini_array..dy0x00000100 6e616d69 63002e64 61746100 2e627373 namic..data..bss0x00000110 002e636f 6d6d656e 7400              ..comment.

可以发现,这里其实是一堆字符串,这些字符串对应的就是各个区的名字.因此section header table中每个元素的Name字段其实是这个string table的索引.为节省空间而做的设计,再回头看看ELF header中的 elfShStrIndex

Section header string table index: 30 //字符串数组索引,该区记录所有区名称

它的值正好就是30,指向了当前的string table.

符号表 Symbol Table

Section Header Table中,还有一类SYMTAB(DYNSYM)区,该区叫符号表.符号表中的每个元素对应一个符号,记录了每个符号对应的实际数值信息,通常用在重定位过程中或问题定位过程中,进程执行阶段并不加载符号表.符号表对应鸿蒙源码结构体为 LDElf32Sym.
//kernel\extended\dynload\include\los_ld_elf_pri.h

/* Symbol table */
typedef struct {UINT32 stName;  /* Symbol table name (string tbl index) *///表示符号对应的源码字符串,为对应String Table中的索引UINT32 stValue; /* Symbol table value *///表示符号对应的数值UINT32 stSize;  /* Symbol table size *///表示符号对应数值的空间占用大小UINT8 stInfo;   /* Symbol table type and binding *///表示符号的相关信息 如符号类型(变量符号、函数符号)UINT8 stOther;  /* Symbol table visibility */UINT16 stShndx; /* Section table index *///表示与该符号相关的区的索引,例如函数符号与对应的代码区相关
} LDElf32Sym;

readelf -s读出示例程序中的符号表,如下所示

root@5e3abe332c5a:/home/docker/case_code_100# readelf -s appSymbol table '.dynsym' contains 7 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)Symbol table '.symtab' contains 67 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000318     0 SECTION LOCAL  DEFAULT    12: 0000000000000338     0 SECTION LOCAL  DEFAULT    23: 0000000000000358     0 SECTION LOCAL  DEFAULT    34: 000000000000037c     0 SECTION LOCAL  DEFAULT    45: 00000000000003a0     0 SECTION LOCAL  DEFAULT    56: 00000000000003c8     0 SECTION LOCAL  DEFAULT    67: 0000000000000470     0 SECTION LOCAL  DEFAULT    78: 00000000000004f4     0 SECTION LOCAL  DEFAULT    89: 0000000000000508     0 SECTION LOCAL  DEFAULT    910: 0000000000000528     0 SECTION LOCAL  DEFAULT   1011: 0000000000000600     0 SECTION LOCAL  DEFAULT   1112: 0000000000001000     0 SECTION LOCAL  DEFAULT   1213: 0000000000001020     0 SECTION LOCAL  DEFAULT   1314: 0000000000001040     0 SECTION LOCAL  DEFAULT   1415: 0000000000001050     0 SECTION LOCAL  DEFAULT   1516: 0000000000001060     0 SECTION LOCAL  DEFAULT   1617: 0000000000001218     0 SECTION LOCAL  DEFAULT   1718: 0000000000002000     0 SECTION LOCAL  DEFAULT   1819: 000000000000201c     0 SECTION LOCAL  DEFAULT   1920: 0000000000002068     0 SECTION LOCAL  DEFAULT   2021: 0000000000003db8     0 SECTION LOCAL  DEFAULT   2122: 0000000000003dc0     0 SECTION LOCAL  DEFAULT   2223: 0000000000003dc8     0 SECTION LOCAL  DEFAULT   2324: 0000000000003fb8     0 SECTION LOCAL  DEFAULT   2425: 0000000000004000     0 SECTION LOCAL  DEFAULT   2526: 0000000000004018     0 SECTION LOCAL  DEFAULT   2627: 0000000000000000     0 SECTION LOCAL  DEFAULT   2728: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c29: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones30: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones31: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux32: 0000000000004018     1 OBJECT  LOCAL  DEFAULT   26 completed.806033: 0000000000003dc0     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtors_aux_fin34: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 frame_dummy35: 0000000000003db8     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_init_array_36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c38: 000000000000218c     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__39: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS40: 0000000000003dc0     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_end41: 0000000000003dc8     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC42: 0000000000003db8     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_start43: 000000000000201c     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR44: 0000000000003fb8     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_45: 0000000000001000     0 FUNC    LOCAL  DEFAULT   12 _init46: 0000000000001210     5 FUNC    GLOBAL DEFAULT   16 __libc_csu_fini47: 0000000000004010     8 OBJECT  GLOBAL DEFAULT   25 my_name48: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab49: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start50: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   25 _edata51: 0000000000001218     0 FUNC    GLOBAL HIDDEN    17 _fini52: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.553: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_54: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start55: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__56: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle57: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used58: 00000000000011a0   101 FUNC    GLOBAL DEFAULT   16 __libc_csu_init59: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   26 _end60: 0000000000001060    47 FUNC    GLOBAL DEFAULT   16 _start61: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start62: 0000000000001174    30 FUNC    GLOBAL DEFAULT   16 main63: 0000000000001149    43 FUNC    GLOBAL DEFAULT   16 say_hello64: 0000000000004018     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__65: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable66: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.2

在最后位置找到了亲切的老朋友 mainsay_hello

    62: 0000000000001174    30 FUNC    GLOBAL DEFAULT   16 main63: 0000000000001149    43 FUNC    GLOBAL DEFAULT   16 say_hello

main函数符号对应的数值为0x1174,其类型为FUNC,大小为30字节,对应的代码区索引为16.
say_hello函数符号对应数值为0x1149,其类型为FUNC,大小为43字节,对应的代码区索引同为16.
Section Header Table:

  [16] .text             PROGBITS         0000000000001060  0000106000000000000001b5  0000000000000000  AX       0     0     16

反汇编代码区

在理解了String TableSymbol Table的作用后,通过objdump反汇编来理解一下.text代码区:

root@5e3abe332c5a:/home/docker/case_code_100# objdump -j .text -l -C -S app0000000000001149 <say_hello>:
say_hello():1149:       f3 0f 1e fa             endbr64114d:       55                      push   %rbp114e:       48 89 e5                mov    %rsp,%rbp1151:       48 83 ec 10             sub    $0x10,%rsp1155:       48 89 7d f8             mov    %rdi,-0x8(%rbp)1159:       48 8b 45 f8             mov    -0x8(%rbp),%rax115d:       48 89 c6                mov    %rax,%rsi1160:       48 8d 3d 9d 0e 00 00    lea    0xe9d(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>1167:       b8 00 00 00 00          mov    $0x0,%eax116c:       e8 df fe ff ff          callq  1050 <printf@plt>1171:       90                      nop1172:       c9                      leaveq1173:       c3                      retq0000000000001174 <main>:
main():1174:       f3 0f 1e fa             endbr641178:       55                      push   %rbp1179:       48 89 e5                mov    %rsp,%rbp117c:       48 8b 05 8d 2e 00 00    mov    0x2e8d(%rip),%rax        # 4010 <my_name>1183:       48 89 c7                mov    %rax,%rdi1186:       e8 be ff ff ff          callq  1149 <say_hello>118b:       b8 00 00 00 00          mov    $0x0,%eax1190:       5d                      pop    %rbp1191:       c3                      retq1192:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)1199:       00 00 00119c:       0f 1f 40 00             nopl   0x0(%rax)

0x1149 0x1174正是say_hellomain函数的入口地址.并看到了激动人心的指令

1186:       e8 be ff ff ff          callq  1149 <say_hello>

很佩服你还能看到这里,牛逼,牛逼! 看了这么久还记得开头的C代码的样子吗? 再看一遍 : )

#include <stdio.h>
void say_hello(char *who)
{printf("hello, %s!\n", who);
}
char *my_name = "harmony os";
int main()
{say_hello(my_name);return 0;
}
root@5e3abe332c5a:/home/docker/case_code_100# ./app
hello, harmony os!    

但是!!! 晕,怎么还有but,西卡西…,上面请大家记住的还有一个地方没说到

Entry point address:               0x1060   //代码区 .text 起始位置,即程序运行开始位置

它的地址并不是main函数位置0x1174,是0x1060!而且代码区的开始位置是0x1060没错的.

  [16] .text             PROGBITS         0000000000001060  0000106000000000000001b5  0000000000000000  AX       0     0     16

难度main不是入口地址? 那0x1060上放的是何方神圣,再查符号表发现是

    60: 0000000000001060    47 FUNC    GLOBAL DEFAULT   16 _start

从反汇编堆中找到 _start

0000000000001060 <_start>:
_start():1060:       f3 0f 1e fa             endbr641064:       31 ed                   xor    %ebp,%ebp1066:       49 89 d1                mov    %rdx,%r91069:       5e                      pop    %rsi106a:       48 89 e2                mov    %rsp,%rdx106d:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp1071:       50                      push   %rax1072:       54                      push   %rsp1073:       4c 8d 05 96 01 00 00    lea    0x196(%rip),%r8        # 1210 <__libc_csu_fini>107a:       48 8d 0d 1f 01 00 00    lea    0x11f(%rip),%rcx        # 11a0 <__libc_csu_init>1081:       48 8d 3d ec 00 00 00    lea    0xec(%rip),%rdi        # 1174 <main>1088:       ff 15 52 2f 00 00       callq  *0x2f52(%rip)        # 3fe0 <__libc_start_main@GLIBC_2.2.5>108e:       f4                      hlt108f:       90                      nop

这才看到了0x1174main函数.所以真正的说法是:

  • 从内核动态加载的视角看,程序运行首个函数并不是main,而是_start.
  • 但从应用程序开发者视角看,main就是启动函数.

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN

图片

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往在这里插入图片描述

相关文章:

鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main

阅读之前的说明 先说明&#xff0c;本篇很长&#xff0c;也很枯燥&#xff0c;若不是绝对的技术偏执狂是看不下去的.将通过一段简单代码去跟踪编译成ELF格式后的内容.看看ELF究竟长了怎样的一副花花肠子&#xff0c;用readelf命令去窥视ELF的全貌&#xff0c;最后用objdump命令…...

seq2seq编码器encoder和解码器decoder详解

编码器 在序列到序列模型中&#xff0c;编码器将输入序列&#xff08;如一个句子&#xff09;转换为一个隐藏状态序列&#xff0c;供解码器生成输出。编码层通常由嵌入层和RNN&#xff08;如GRU/LSTM)等组成 Token:是模型处理文本时的基本单元&#xff0c;可以是词,子词,字符…...

前端使用 Konva 实现可视化设计器(21)- 绘制图形(椭圆)

本章开始补充一些基础的图形绘制&#xff0c;比如绘制&#xff1a;直线、曲线、圆/椭形、矩形。这一章主要分享一下本示例是如何开始绘制一个图形的&#xff0c;并以绘制圆/椭形为实现目标。 请大家动动小手&#xff0c;给我一个免费的 Star 吧~ 大家如果发现了 Bug&#xff0c…...

Python 将单词拆分为单个字母组成的列表对象

Python 将单词拆分为单个字母组成的列表对象 正文 正文 这里介绍一个简单算法&#xff0c;将英文单词拆分为其对应字母组成的列表。 str1 ACG lst1 [i for i in str1] lst2 list(str1)# Method 1 print(lst1) # Method 2 print(lst2) """ result: [A, C, G…...

欧洲 摩纳哥税务知识

摩纳哥是一个位于法国南部的城邦国家&#xff0c;以其豪华的生活环境和宽松的税收政策而闻名。自1869年以来&#xff0c;摩纳哥取消了个人所得税的征收&#xff0c;这使得它成为富裕人士和外籍人士的理想居住地。然而&#xff0c;这并不意味着摩纳哥的税收制度完全不存在。以下…...

域控制器的四大支柱分别是车载以太网、自适应Autosar

域控制器的四大支柱分别是车载以太网、自适应Autosar、高性能处理器和集中式E/E架构。 百度安全验证 。自适应Autosar采用Proxy/Skeleton的通信架构&#xff0c;同时采用中间件SOME/IP...

写给大数据开发:如何优化临时数据查询流程

你是否曾因为频繁的临时数据查询请求而感到烦恼&#xff1f;这些看似简单的任务是否正在蚕食你的宝贵时间&#xff0c;影响你的主要工作&#xff1f;如果是&#xff0c;那么这篇文章正是为你而写。 目录 引言&#xff1a;数据开发者的困境问题剖析&#xff1a;临时数据查询的…...

【MongoDB】Java连接MongoDB

连接URI 连接 URI提供驱动程序用于连接到 MongoDB 部署的指令集。该指令集指示驱动程序应如何连接到 MongoDB&#xff0c;以及在连接时应如何运行。下图解释了示例连接 URI 的各个部分&#xff1a; 连接的URI 主要分为 以下四个部分 第一部分 连接协议 示例中使用的 连接到具有…...

nginx支持的不同事件驱动模型

Nginx 支持的不同事件驱动模型 Nginx 是一款高性能的 Web 和反向代理服务器&#xff0c;它支持多种事件驱动模型来处理网络 I/O 操作。不同的操作系统及其版本支持不同的事件驱动模型&#xff0c;这些模型对于 Nginx 的并发处理能力和性能至关重要。下面详细介绍 Nginx 支持的…...

C++ TinyWebServer项目总结(7. Linux服务器程序规范)

进程 PID 进程的PID&#xff08;Process ID&#xff09;是操作系统中用于唯一标识一个进程的整数值。每个进程在创建时&#xff0c;操作系统都会分配一个唯一的PID&#xff0c;用来区分不同的进程。 PID的特点 唯一性&#xff1a; 在操作系统运行的某一时刻&#xff0c;每个…...

基于STM32单片机设计的秒表时钟计时器仿真系统——程序源码proteus仿真图设计文档演示视频等(文末工程资料下载)

基于STM32单片机设计的秒表时钟计时器仿真系统 演示视频 基于STM32单片机设计的秒表时钟计时器仿真系统 摘要 本设计基于STM32单片机&#xff0c;设计并实现了一个秒表时钟计时器仿真系统。系统通过显示器实时显示当前时间&#xff0c;并通过定时器实现秒表计时功能。显示小时…...

人才流失预测项目

在本项目中&#xff0c;通过数据科学和AI的方法&#xff0c;分析挖掘人力资源流失问题&#xff0c;并基于机器学习构建解决问题的方法&#xff0c;并且&#xff0c;我们通过对AI模型的反向解释&#xff0c;可以深入理解导致人员流失的主要因素&#xff0c;HR部门也可以根据分析…...

BUG——imx6u开发_结构体导致的死机问题(未解决)

简介&#xff1a; 最近在做imx6u的linux下裸机驱动开发&#xff0c;由于是学习的初级阶段&#xff0c;既没有现成的IDE可以使用&#xff0c;也没有GDB等在线调试工具&#xff0c;只能把代码烧写在SD卡上再反复插拔&#xff0c;仅靠卑微的亮灯来判断程序死在哪一步。 至于没有使…...

问答:什么是对称密钥、非对称密钥,http怎样变成https的?

文章目录 对称密钥 vs 非对称密钥HTTP 变成 HTTPS 的过程 对称密钥 vs 非对称密钥 1. 对称密钥加密 定义: 对称密钥加密是一种加密算法&#xff0c;其中加密和解密使用的是同一个密钥。特点: 速度快: 因为只使用一个密钥&#xff0c;所以加密和解密速度较快。密钥分发问题: 双…...

虚拟滚动列表组件ReVirtualList

虚拟滚动列表组件ReVirtualList 组件实现基于 Vue3 Element Plus Typescript&#xff0c;同时引用 vueUse lodash-es tailwindCss (不影响功能&#xff0c;可忽略) 在 ReList 的基础上&#xff0c;增加虚拟列表功能&#xff0c;在固定高度的基础上&#xff0c;可以优化大数…...

稳定、耐用、美观 一探究竟六角头螺钉螺栓如何选择

在机器与技术未被发现的过去&#xff0c;紧固件设计和品质并不稳定。但是&#xff0c;他们已成为当今许多行业无处不在的构成部分。六角头标准件或六角头标准件是紧固件中持续的头部设计之一&#xff0c;它有六个面&#xff0c;对广泛工业应用大有益处。六角头标准件或常分成六…...

数据库Mybatis基础操作

目录 基础操作 删除 预编译SQL 增、改、查 自动封装 基础操作 环境准备 删除 根据主键动态删除数据&#xff1a;使用了mybatis中的参数占位符#{ }&#xff0c;里面是传进去的参数。 单元测试&#xff1a; 另外&#xff0c;这个方法是有返回值的&#xff0c;返回这次操作…...

人物形象设计:塑造独特角色的指南

引言 人物形象设计是一种创意过程&#xff0c;它利用强大的设计工具&#xff0c;通过视觉和叙述元素塑造角色的外在特征和内在性格。这种设计不仅赋予角色以生命&#xff0c;还帮助观众或读者在心理层面上与角色建立联系。人物形象设计的重要性在于它能够增强故事的吸引力和说…...

网络安全-安全策略初认识

文章目录 前言理论介绍1. 安全策略1.1 定义&#xff1a;1.2 关键术语&#xff1a; 2. 防火墙状态监测 实战步骤1&#xff1a;实验环境搭建步骤2&#xff1a;配置实现 总结1. 默认安全策略2. 自定义安全策略3. 防火墙状态会话表 前言 who&#xff1a;本文主要写给入门防火墙的技…...

python import相对导入与绝对导入

文章目录 相对导入与绝对导入绝对导入相对导入何时使用相对导入何时使用绝对导入示例 相对导入与绝对导入 在Python中&#xff0c;from .file_manager import SomeFunction 和 from file_manager import SomeFunction 两种导入方式看似相似&#xff0c;但在模块寻找机制上存在…...

深入理解 Go 语言原子内存操作

原子内存操作提供了实现其他同步原语所需的低级基础。一般来说,你可以用互斥体和通道替换并发算法的所有原子操作。然而,它们是有趣且有时令人困惑的结构,应该深入了解它们是如何工作的。如果你能够谨慎地使用它们,那么它们完全可以成为代码优化的好工具,而不会增加复杂性…...

PostgreSQL几个扩展可以帮助实现数据的分词和快速查询

在 PostgreSQL 数据库中,有几个扩展可以帮助实现数据的分词和快速查询,特别是在处理全文搜索和文本分析时。以下是几个常用的扩展: 1. pg_trgm pg_trgm(Trigram)扩展是 PostgreSQL 中的一个强大的工具,它可以通过计算字符串之间的相似度来实现快速文本搜索。它支持基于…...

C盘满了怎么办?教你清理C盘的20个大招,值得收藏备用

C盘满了怎么办&#xff1f;教你清理C盘的20个大招&#xff0c;值得收藏备用 今天给大家介绍20种C盘清理的方法&#xff0c;下次遇到C盘满了红了就知道怎么做了&#xff0c;喜欢请点赞收藏关注点评。 清理更新缓存 清理微信缓存 查找大文件清理或者迁移 磁盘缓存清理 系统还…...

原生js实现下滑到当前模块进度条填充

<div style"height: 1500px;"></div> <div class"progress-container"><div class"progress-bar" data-progress"90%"><p class"progress-text">Google Ads在Google搜索引擎上覆盖超过90%的互…...

显示弹出式窗口的方法

文章目录 1. 概念介绍2. 使用方法3. 示例代码 我们在上一章回中介绍了Sliver综合示例相关的内容&#xff0c;本章回中将介绍PopupMenuButton组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的PopupMenuButton组件位于AppBar右侧&#xf…...

Java-什么是缓存线程池?

什么是缓存线程池? 缓存线程池 (CachedThreadPool) 是一种特殊的线程池,它能够动态地调整线程的数量,以适应任 务的需求。这种线程池非常适合处理大量短暂的任务,因为它会根据任务的数量自动增加或减少线 程的数量。 缓存线程池的特点: 线程数量动态调整:缓存线程池…...

esbuild中的Binary Loader:处理二进制文件

在前端或Node.js项目中&#xff0c;有时需要处理二进制文件&#xff0c;如图片、音频、视频或其他非文本资源。esbuild提供了一款名为Binary Loader的插件&#xff0c;它能够在构建时将二进制文件加载为二进制缓冲区&#xff0c;并使用Base64编码将其嵌入到打包文件中。在运行时…...

深度好文:从《黑神话:悟空》看未来游戏趋势:高互动性、个性化与全球化

引言 在数字时代的浪潮中&#xff0c;游戏产业以其独特的魅力和无限的可能性&#xff0c;成为了全球娱乐文化的重要组成部分。随着科技的飞速发展&#xff0c;特别是高性能计算和人工智能技术的突破&#xff0c;游戏的世界变得越来越真实、细腻且富有深度。而在这股技术洪流中…...

【中项第三版】系统集成项目管理工程师 | 第 12 章 执行过程组

前言 本章属于10大管理的内容&#xff0c;上午题预计会考8-10分&#xff0c;下午案例分析也会进行考查。学习要以教材为主。 目录 12.1 指导与管理项目工作 12.1.1 主要输入 12.1.2 主要输出 12.2 管理项目知识 12.2.1 主要输入 12.2.2 主要输出 12.3 管理质量 12.3.…...

C语言自动生成宏定义枚举类型和字符串

#include <stdio.h>// 定义错误枚举 #define ERROR_LIST(e) \e(SUCCESS) \e(FAILURE) \e(NOT_FOUND) \e(TIMEOUT)// 使用宏生成枚举 #define GENERATE_ENUM(ENUM) ENUM, typedef enum {ERROR_LIST(GENERATE_ENUM) } ErrorCode;// 使用宏生成字符串数组…...

如何做网站淘宝客/百度数据指数

3D打印机加工一般可以打印什么东西&#xff1f; 3D打印&#xff08;3DP&#xff09;即快速成型技术的一种&#xff0c;又称增材制造&#xff0c;它是一种以数字模型文件为基础&#xff0c;运用粉末状金属或塑料等可粘合材料&#xff0c;通过逐层打印的方式来构造物体的技术。 …...

申请一个自己的网站/石家庄网站建设培训

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产模拟考试一点通&#xff1a;低压电工报名考试考前必练&#xff01;安全生产模拟考试一点通每个月更新低压电工模拟考试题库题目及答案&#xff01;多做几遍&#xff0c;其实通过低压电工作业考试题库很简单。…...

中心网站建设/青岛神马排名优化

LeetCode1.两数之和JavaScript 给定一个整数数组和一个目标值&#xff0c;找出数组中和为目标值的两个数。你可以假设每个输入只对应一种答案&#xff0c;且同样的元素不能被重复利用。实例&#xff1a; 给定 nums [2, 7, 11, 15], target 9因为 nums[0] nums[1] 2 7 9所…...

深圳南山企业网站建设/网络营销的特点有哪些?

雅思考试到底是选择纸笔还是机考&#xff1f;很多同学纠结在笔试与机考两难中&#xff0c;还在担心雅思全面机考时代的来临。但说真的&#xff0c;雅思全面机考正悄悄走来&#xff0c;你不知道它什么时候来但知道它一定会来&#xff0c;就像我们不知道下一次考试会不会涨价但知…...

东莞网站优化服务公司/优化网络的软件下载

我目前正在与iis 7.0共享主机方案我在我的主机的wwwroot中有一个asp.net mvc应用程序.现在我创建了一个虚拟目录来存储我的web服务(我不想在子域上托管它并支付额外费用)但是,当我将我的Web服务代码放在我的虚拟目录中并尝试运行它时,我得到了这个Error SummaryHTTP Error 500.…...

局域网搭建wordpress慢/百度基木鱼建站

文章目录前言本次测试注解介绍注解使用SecuredPreAuthorizePostAuthorizePostFilterPreFilter代码下载前言 之前security中&#xff0c;针对配置项进行了相关的配置和测试。 但是这些都是基于在security.config.MyConfig#configure(org.springframework.security.config.annot…...