0%

Excutable and Linking Format,ELF,有UNIX系统实验室发布并确定为应用程序二进制接口ABI(二进制程序标识所有工具集所遵循的一组运行时约定,包括:编译器、汇编器、连接器和语言运行时支持)的落地文件形式,其重要性不言而喻。

ELF文件类型

ELF文件是由汇编器和连接器创建,是程序的二进制表示,以提供直接在目标处理器上执行的代码(指令)和数据。

ELF有三类文件类型,参与程序的构建和执行:

  • 可重定位 (relocatable file),也叫静态链接库,包含部分可执行文件格式或者动态链接格式的数据或代码,需与其他两种格式进行整合
  • 可执行(executable file),包含程序执行的所有代码和数据信息,被系统读取用于创建进程
  • 共享对象文件(shared object file),也叫动态链接库,包含适合在以上两种文件格式中需要链接的代码和数据信息。

我们以C代码来学习,main.c是主函数,引用了fun.c中的printHello函数:

main.c

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
extern func(int a);
int g1 = 100;
int g2;
int main(){
static int a = 1;
static int b = 0;
int c = 1;
func(a+b+c);
printf("this main!");
return 0;
}

fun.c

1
2
3
4
5
6
7
#include<stdio.h>
int count = 0;
int value;
void func(int sum)
{
printf("sum is %d\n",sum);
}

gcc编译命令帮助:

*-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link 只编译成汇编代码
-c Compile and assemble, but do not link 编译二进制的ELF文件,没有连接
-o Place the output into *

我们只研究和ELF有关的gcc操作。下面,编译ELF文件。

1
gcc -c fun.c main.c 

生成了对应.o的ELF文件。

1
gcc -o main main.o fun.o

生成了main的可执行ELF文件。

1
gcc -shared -fPIC -o fun.so fun.c

生成了fun.so的共享ELF文件。

命令readelf 可读取ELF文件,使用帮助查看相关参数。

查看文件的type类型是什么

1
2
3
readelf -h fun.o
readelf -h fun.so
readelf -h main

ELF定义源码在linux文件/user/include/elf.h中,相关的结构都能通过源码获取到。

ELF内部的数据表示

这里的数据指一切可以表示信息的二进制记录。

ELF文件支持具有8位字节和32位架构的处理器,其目的是在更大(更小)的体系结构中执行,具有平台无关性,因此,ELF的对象文件是以一种独立于执行机器的格式来表示控制数据,而数据则使用目标处理器进行编码和解释,而不管ELF是在哪台机器上创建的。即:

  • ELF记录的控制信息无视处理器结构
  • ELF记录的操作数据按目标处理器解释

ELF文件格式定义的所有数据结构都遵循特定的大小和对齐规则。如有必要,对应的struct结构体需要填充0以确保4字节对齐,相应的基础数据类型(1、2、4、8字节)均需对齐。处于可移植目的,ELF不实用位(1bit)。

以下是ELF的标准数据申明(代码注释请勿忽略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Standard ELF types.  */

#include <stdint.h>

/* Type for a 16-bit quantity. */
typedef uint16_t Elf32_Half;
typedef uint16_t Elf64_Half;

/* Types for signed and unsigned 32-bit quantities. */
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf64_Word;
typedef int32_t Elf64_Sword;

/* Types for signed and unsigned 64-bit quantities. */
typedef uint64_t Elf32_Xword;
typedef int64_t Elf32_Sxword;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;

/* Type of addresses. */
typedef uint32_t Elf32_Addr;
typedef uint64_t Elf64_Addr;

/* Type of file offsets. */
typedef uint32_t Elf32_Off;
typedef uint64_t Elf64_Off;

/* Type for section indices, which are 16-bit quantities. */
typedef uint16_t Elf32_Section;
typedef uint16_t Elf64_Section;

从上我们可以看出:

Half、Section、word(S)、Xword(S)都是位数无关的。

只有Add地址和Off偏移是地址有关的。

Headers

header的设计目的是控制结构,描述、定位对应数据的结构和信息。

readelf -e fun.o

readelf -e fun.so

readelf -e main

有3个header:

ELF文件自身的header

ELF文件自身的header,32位(以下都以32为主)结构体定了文件的header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

/* Legal values for e_type (object file type). */

#define ET_NONE 0 /* No file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
#define ET_NUM 5 /* Number of defined types */
#define ET_LOOS 0xfe00 /* OS-specific range start */
#define ET_HIOS 0xfeff /* OS-specific range end */
#define ET_LOPROC 0xff00 /* Processor-specific range start */
#define ET_HIPROC 0xffff /* Processor-specific range end */

e_type中包含了ELF的三大文件类型。

下面是bash中查看main.o的ELF文件头打印内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*打印的ELF header内容*/
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 936 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 12

ELF文件排布:

| ELF header | Sections | Section header table |

从上面Header信息中,可得到如下大小相关信息

Header大小: Size of this header: 64 (bytes)

Sections大小:Start of section headers - Header 大小: 936byte - 64bytes

Section header table大小:Size of section headers * Number of section headers = 13 * 64

整个文件大小为:64 + 936- 64 + 13 * 64= 1768bytes

我们使用命令查看main.o的大小输出的也是1768:

1
wc -c main.o

section的header

section(翻译为节)的header,描述节的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* Section header.  */

typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

/* 截取 section header */
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000044 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002a8
0000000000000078 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000084
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 0000008c
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 0000008c
000000000000000b 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000097
000000000000002e 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000c5
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c8
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000320
0000000000000018 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000100
0000000000000180 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 00000280
0000000000000028 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000338

section中的类型(Type),类型对节section的内容和语义进行了分了。具体参考下节section。

Offset是在文件中偏移位置(即该部分起始地址),Size为其大小。通过这2部分,就可以确认每个section的位置大小。由上一小节内容,我们知道elf 文件的header有64bytes,16进制为0x40,所以我们可以知道.tex的offset值00000040,是紧跟在elf的文件header之后的。使用命令验证:

1
objdump -h main.o

得到如下内容,也即各section的排布顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000044 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000084 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 0000008c 2**2
ALLOC
3 .rodata 0000000b 0000000000000000 0000000000000000 0000008c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002e 0000000000000000 0000000000000000 00000097 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000c5 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 0000000000000000 0000000000000000 000000c8 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

program的header

program的header,用于告知操作系统如何创建进程。program的header在可执行和共享文件中有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* Program segment header.  */

typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

/* program header */
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000754 0x0000000000000754 R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000224 0x0000000000000228 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000600 0x0000000000400600 0x0000000000400600
0x000000000000003c 0x000000000000003c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1

sections

section代表的是一段连续的地址空间,包含了除header(section、elf 文件、program)外的所有信息。section的header参考上一节。

节的类型,源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* Legal values for sh_type (section type).  */

#define SHT_NULL 0 /* Section header table entry unused */
#define SHT_PROGBITS 1 /* Program data */
#define SHT_SYMTAB 2 /* Symbol table */
#define SHT_STRTAB 3 /* String table */
#define SHT_RELA 4 /* Relocation entries with addends */
#define SHT_HASH 5 /* Symbol hash table */
#define SHT_DYNAMIC 6 /* Dynamic linking information */
#define SHT_NOTE 7 /* Notes */
#define SHT_NOBITS 8 /* Program space with no data (bss) */
#define SHT_REL 9 /* Relocation entries, no addends */
#define SHT_SHLIB 10 /* Reserved */
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */
#define SHT_INIT_ARRAY 14 /* Array of constructors */
#define SHT_FINI_ARRAY 15 /* Array of destructors */
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */
#define SHT_GROUP 17 /* Section group */
#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */
#define SHT_NUM 19 /* Number of defined types. */
#define SHT_LOOS 0x60000000 /* Start OS-specific. */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */
#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */
#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */
#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */
#define SHT_SUNW_move 0x6ffffffa
#define SHT_SUNW_COMDAT 0x6ffffffb
#define SHT_SUNW_syminfo 0x6ffffffc
#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */
#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */
#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */
#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */
#define SHT_HIOS 0x6fffffff /* End OS-specific type */
#define SHT_LOPROC 0x70000000 /* Start of processor-specific */
#define SHT_HIPROC 0x7fffffff /* End of processor-specific */
#define SHT_LOUSER 0x80000000 /* Start of application-specific */
#define SHT_HIUSER 0x8fffffff /* End of application-specific */

部分汉语翻译(内容摘自:黄俊的混沌学堂知识星球,有增改)

SHT_NULL:该值将节标记为非活动状态

SHT_PROGBITS:该值保存着由程序定义的节信息,其格式和含义完全由程序决定(程序是什么?考虑下谁生成了目标文件)

SHT_SYMTAB 和 SHT_DYNSYM:表示当前节保存着一个符号表。SHT_SYMTAB 表示提供了用于链接器静态链接的符号,其作为一个完整的符号表,它可能包含许多动态链接中不需要的符号,因此,一个对象文件也可以包含一个 SHT_DYNSYM 的节用于保存动态链接符号的最小集合,以节省空间。所以我们可以通过strip去掉完整符号表,但这会导致不能进行静态链接 。SYM = symbol。

SHT_DYNAMIC:表示当前节保存动态链接信息

SHT_STRTAB:表示当前节为字符串表

SHT_RELA:表示当前节保存着带有显式加数的重定位表项,例如对象文件的32位类的Elf32_Rela类型(后面会说这里的加数是什么)

SHT_REL:表示该节保存着没有显式加数的重定位表项。一个ELF文件可能有多个重定位的section。

SHT_HASH:表示当前节保存着一个符号哈希表。所有参与动态链接的对象文件必须包含一个符号哈希表

SHT_NOTE:表示当前节保存文件的注释信息

SHT_NOBITS:表示不占任何空间的节信息,虽然该节不包含字节,但是sh_offset成员包含概念上的文件偏移量

SHT_SHLIB:该节类型是保留的,但具有未指定的语义。包含这种类型的部分的程序不符合ABI规范

SHT_LOPROC 与 SHT_HIPROC:这个范围内的值为特定于处理器的语义保留

SHT_LOUSER:该类型的节表示应用程序保留的索引范围的下界

SHT_HIUSER:该类型的节表示应用程序保留的索引范围的上限

下面重点介绍几个重要Section Type。

String table

字符串表节保存以空白符结尾的字符串序列。ELF文件使用这些字符串来表示符号和节名。使用方式,是符号保存一个指向支付串标的索引下标,结束字符是空白符。

symbol table

一个ELF文件的符号表保存了需要进行重定位程序的符号定义。

在Section Header中,type为SYMTAB的就是。

readelf -s fun.o

readelf -s fun.so

readelf -s main.o

有2个符号表,分别是.dynsym 、.symtab,其中.o的静态链接文件没有.dynsym。

源码定义的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
/* Symbol table entry.  */

typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

st_name,目标文件的字符串标的索引下标。

st_value,关联符号值(Symbol Values)

在不同的对象文件(ELF)中解释不同:

  • 可重定位文件中,节index是SHN_COMMON,st_value保存的是一个符号的对齐约束
  • 可重定位文件中,st_value保存的是是一个已定义的节偏移量,即st_shndx标识开始的地方开始的偏移量。
  • 在可执行和共享文件中,st_value保存一个用户内存排布的虚拟地址。

st_info在符号表中显示为Type字段和Bind字段,Type提供了关联实体的一般分类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* How to extract and insert information held in the st_info field.  */

#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val) ((val) & 0xf)
#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))
/* Legal values for ST_TYPE subfield of st_info (symbol type). */

#define STT_NOTYPE 0 /* Symbol type is unspecified */
#define STT_OBJECT 1 /* Symbol is a data object */
#define STT_FUNC 2 /* Symbol is a code object */
#define STT_SECTION 3 /* Symbol associated with a section */
#define STT_FILE 4 /* Symbol's name is file name */
#define STT_COMMON 5 /* Symbol is a common data object */
#define STT_TLS 6 /* Symbol is thread-local data object*/
#define STT_NUM 7 /* Number of defined types. */
#define STT_LOOS 10 /* Start of OS-specific */
#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */
#define STT_HIOS 12 /* End of OS-specific */
#define STT_LOPROC 13 /* Start of processor-specific */
#define STT_HIPROC 15 /* End of processor-specific */

Bind字段,决定了该符号记录的可见性和行为。

1
2
3
4
5
6
7
8
9
10
11
12
/* Legal values for ST_BIND subfield of st_info (symbol binding).  */

#define STB_LOCAL 0 /* Local symbol */
#define STB_GLOBAL 1 /* Global symbol */
#define STB_WEAK 2 /* Weak symbol */
#define STB_NUM 3 /* Number of defined types. */
#define STB_LOOS 10 /* Start of OS-specific */
#define STB_GNU_UNIQUE 10 /* Unique symbol. */
#define STB_HIOS 12 /* End of OS-specific */
#define STB_LOPROC 13 /* Start of processor-specific */
#define STB_HIPROC 15 /* End of processor-specific */

主要关注STB_LOCAL、STB_GLOBAL、STB_WEAK。

STB_GLOBAL和STB_WEAK区别主要体现在,当连接器组合几个可重定位文件时,不允许STB_GLOBAL符号有相同名称的多个定义。但是,如果已定义的全局符号存在了,则用同名弱符号不会报错,此时忽略弱符号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
readelf -s main.o

Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 a.2180
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 b.2181
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g1
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM g2
13: 0000000000000000 68 FUNC GLOBAL DEFAULT 1 main
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND func
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf

Ndx是section的header table中的index,其中UND不在main中。

g1、g2全局Object(数据对象),所属的section不一样。g2 COMMON存放未初始化的全局变量,.bss未初始化的静态变量,初始化为0的全局或静态变量(两者区别)。

局部Objecta和b存放位置不同,a初始化了,b初始化为0(并没什么用)。 a.2180 和b.2181,是名称修饰,防止重复。

Relocation section

重定位Relocation基本概念:是将符号引用和符号定义连接起来的过程,比如编译中静态链接库合并到一起时,各自文件中的内存相对地址更新到新文件中的地址。又比如,一个程序调用一个函数时,相关调用指令(call sum)必须在执行时将调用符号分配适当的目标地址(call 0x800001)。换句话说,可重定位文件必须提供具有描述如何修改节内容的信息,从而允许可执行和共享目标文件保存进程装载执行所需要的正确信息。

在Section Header中,Type是RELA或REL的就是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Relocation table entry without addend (in section of type SHT_REL).  */

typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

/* Relocation table entry with addend (in section of type SHT_RELA). */

typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;

r_offset不同目标文件的重定位表象的解释略有不同:

  • 可重定位文件,r_offset保存着一个从节开头到需要重定位的内存单元的字节偏移量。
  • 可执行和共享文件中,r_offset保存的是虚拟地址,用户内存排布标识。
1
2
3
4
readelf -r main.o
readelf -r fun.o
readelf -r fun.so

r_info表示符号表索引以及重定位类型(地址解析类型)。

一个重定位节引用另外2个节,符号表和要修改的节。节头的sh_info和sh_link成员,在上面的节信息中描述,指定了这些关系。

特殊的节

1
readelf -l main

Section to Segment mapping,节和段segment的映射。是个特殊的节,用于保存程序信息和控制信息。不清楚为什么被划分到 program headers类别中。.systab和.rel(.rela)上面两节描述了。

  • .init:表示该节保存了进程初始化代码的可执行指令。也就是说,当一个程序开始运行时,将会在调用主程序入口点(C程序的main函数)之前执行这一部分的代码
  • .bss:表示该节保存着未初始化的数据,这些数据构成了程序的数据内存排布。根据定义,当程序开始运行时,系统用零初始化这些数据。节类型 SHT_NOBITS 表示该节不占用文件空间。
  • .data 和 .data1:表示该节保存着已经初始化的数据,这些数据构成了程序的数据内存排布
  • .dynamic:表示该节保存动态链接信息
  • .dynstr:表示该节保存动态链接所需的字符串,最常见的是表示符号表项相关名称的字符串
  • .dynsym:表示该节保存着动态链接符号表
  • .fini:表示该节保存了进程终止代码的可执行指令。也就是说,当一个程序正常退出时将会执行这一部分的代码
  • .got:表示该节保存了全局偏移量表
  • .hash:表示该节保存了符号hash表
  • .plt:表示该节保存了程序连接表信息
  • .rel name 和 .rela name:表示该节保存着重定位信息。如果文件中有一个包含重定位的可加载段,则节的属性将包含 SHF_ALLOC 。通常,name 由重定位的节提供。因此.text 的重定位节的名称通常为.rel.text 或 .rela.text
  • .rodata 和 .rodata1:表示该节保存只读数据,这些数据通常会在进程中形成一个不可写的数据段
  • .shstrtab:表示该节保存节的名字
  • .strtab:表示该节保存字符串信息,最常见的是表示与符号表项关联的名称的字符串
  • .symtab:表示该节保存符号表信息
  • .text:表示当前节包含程序的可执行的指令序列信息
  • .comment:持有注释信息
  • .interp:表示该节保存程序动态链接器的路径名。如果文件中有一个包含该节的可加载段,该节的属性将包括SHF_ALLOC位
  • .line:表示该节保存用于符号调试的行号信息,它描述了源程序和机器码之间的对应关系
  • .note:表示该节保存了标注信息
  • .debug:表示该节保存了用于调试的符号信息

参考

elf官方文档:

https://refspecs.linuxfoundation.org/elf/elf.pdf

知识星球混沌学堂—黄俊:

https://articles.zsxq.com/id_iwals7svgn0g.html

https://articles.zsxq.com/id_ohm2xh6becp2.html

https://articles.zsxq.com/id_n35kk7n2sf0e.html

前言

自学这件事,要坚持下来真的挺耗费精神力的。本来打算把博客系统敲完,遇到面试,要准备,参加面试几轮几轮的面,准备入职的事,又有新面试等等,对入职公司的技术栈的研究涉及go,又大概看了go。测试学院开学,当了助教,也需要上课,答疑解惑。精神分散,还要做家务煮饭什么的,还是比较难办。差点把这个博客计划半途而废了。然后想起来还有件事没有利索,回头继续前进,一定完成!做一件成一件!

2020年6月入职现在的公司开启上班模式后这件事又耽搁了几个月,期间根据公司实际,研习了很多客户端的技术,博客代码的事有中断,后继续,目前是2021年6月7日,实际前前后后已经完成一个多月,打算对第一阶段的博客代码和套路做个总结。下一个阶段的总结应该是要前后端分离,并带上VUE相关内容的。

本文不适合跟操作,仅适合查看涉及的知识点,或扩充一二。

我的项目地址:https://gitee.com/windanchaos/my-blog-v2

师出同门的博客地址(可以大致跟敲):https://onestar.newstar.net.cn/types/33

李仁密教课的地址(我很多地方没跟,比如持久框架和很多细节):https://www.bilibili.com/video/BV13t411T72J

前置动作

  • jQuery速度学习一下,以便在操控界面元素上能有思路
  • mybatis速度学习一下,以便脱离视频教程的持久化方法

用户故事

三个关键点角色、功能、商业价值。

模板:作为一个(角色),我可以做什么,从而达到什么目的。

个人博客系统的用户故事

角色:普通访客、管理员(我)

这里的概念是头一次听到和见到,类似一个产品经理的视角看待问题。

访客的用户故事

  • 可以分页查看所有博客
  • 快速查看博客数最多的6个分类
  • 可以查看所有的分类
  • 可以查看某个分类下的博客列表
  • 查看标记博客最多的10个标签
  • 查看所有标签
  • 查看标签下的博客列表
  • 可以根据年度时间线查看博客列表
    阅读全文 »

一点总结

由于自己本身就对shell中常用的命令有所了解,所以基础的就不啰嗦了。在我之前的博客中已经有不少内容

就写过的很多shell脚本经验看,最近作为助教又在跟着霍格沃兹第三期,又有了新感受。结合以前的经验,做个简单的总结。

  • 代码要规范
  • 虽然是shell,代码在逻辑上也要缜密

Linux文本三剑客

awk

最基本的作用,按规则输出列。

前置学习printf 或print。

printf

命令格式:

printf ‘匹配式’ 输入内容

它的输出全部当成字符串。所以需要自己来设置换行或空格。

%ns

输出字符串。n是数字指代输出几个字符

%ni

输出整数。n是数字自带输出几个数字。

%m.nf

输出浮点数。m是总的位数,n是小数位数

下面是例子,文本demo.txt

ID Name Linux PHP MySQL Java
1 Liming 82 55 58 87

阅读全文 »

MyBatis

ORMapping: Object Relationship Mapping 对象关系映射的一种流行框架。相同技术还有Hibernate。两者对比,可参考:https://www.cnblogs.com/javacatalina/p/6590321.html

ORMapping是Java 到 MySQL 的映射,开发者可以以面面向对象的思想来管理理数据库。

使用套路

两种使用方式

  • mybatis原生方式
  • mybatis动态代理

两者的区别是在获取到sqlsession链接后,操作对象的动作差异。下面举例原生的方式。之后默认用的动态代理。

1
2
3
4
// statement是mapper中的‘namespace.方法’
String statement = "com.chaosbom.mapper.AccoutMapper.save";
Account account = new Account(1L,"张三","123123",22);
sqlSession.insert(statement,account);

mapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chaosbom.mapper.AccoutMapper">
<insert id="save" parameterType="com.chaosbom.entity.Account">
insert into t_account(username,password,age) values(#{username},#
{password},#{age})
</insert>
</mapper>

实体对象和表

实体对象是mysql中表字段对应到java的类,通常在代码框架中被叫做entity。下面分别展示了java类和表结构。

阅读全文 »

这篇读书笔记是《Java多线程编程核心技术》图书的最后一篇。由于写笔记的时候已经读完了整本书,留下了一个大致的印象——适合做多线程的入门读物,所以在读完后感觉缺点什么东西,顺手就打开了另一本《Java并发编程的艺术》,瞬间感觉当年书买得十分的合理,《Java并发编程的艺术》虽然没有开始读,但是大致看了几页,感觉这本书还是要深入一些,更加贴近原理,知识的密度要高很多。

第3章 线程间通信

这一章基本围绕“wait等待/notify通知” 机制来描述线程之间通信的方式。

wait/notify

其核心思想是处理好锁的获取,锁的释放,唤醒(本质是改变线程自己的状态)的关系。

每个锁都有两个队列,一个就绪,一个阻塞。就绪是要需要锁的(可参与锁竞争),阻塞是不需要锁的(被其他事情中断而不能参与锁的争夺,notify就是改变这个队列中的线程的状态,使其进入就绪状态)

某线程调用的lock对象的wait()方法,线程释放锁,进入waiting状态(线程的等待队列);

其他线程的lock对象调用notify()或notifyall()方法,则随机或全部讲waiting该锁的线程设置回runnable可执行状态,同时线程会在完成自己代码的执行后,释放自己占用的lock(注意不是notify动作的时候释放)。等待队列中的线程则被cpu随机调用执行。

这里要题外话,书上描述“如果线程抢占到cpu资源…………”,这个描述很容易让新手误解线程是主动的,实际上并不是,是线程自己的状态配置被其他获取到cpu资源的线程改变了,然后被cpu调用到了才对。

wait(long)可以加时间参数,等带long时间看看有没有被唤醒,超过long就自动唤醒。

使用wait/notify,需要避免混乱:如过早通知、等待条件发生改变

生产/消费者模式

生产者和消费者数量在{1,N}的笛卡尔积的组合,如何处理好同步。主要是利用wait/notify的技巧。

还介绍了以下管道进行线程间的通信。这个之前了解比较少就专门记录一下。

  • PipedInputStream 和 PipedOutputStream,通过各自对象的connect(stream)方法建立进和出通道的联系
  • PipedReader 和PipedWriter,同上

线程的join方法

线程若需要其他线程等待自己线程结束,可以使用的方法。为什么要等?比如等子线程执行后返回值。是被等待的线程调用join()方法来阻塞等待线程。join()具有使线程排队运行的作用,与synchronized的区别:join内部使用的wait()方法进行等待,而synchronized是使用的对象监视器做同步。

join(long) 同wait(long)。

join(long) 和sleep(long) 区别。锁释放阻塞和和锁持有等待(阻塞)。

阅读全文 »

题记

在B站上看到马士兵老师讲解美团研发死亡七连问的视频,讲课中听到马老师的一句鸡汤,“往深了学,往宽了学”,被马老师深厚的技术功底和探求精神折服。遂,翻出了买了很久但是还没有读的《java多线程编程核心技术》和《java并发编程的艺术》,选择了简单入门的《java多线程编程核心技术》读了起来。之前买了没有去看,因为觉得并发编程对我来说太深了,我还没到那个层次,所以书都没有打开过。这次在家待业,学了一些基础的jvm原理,不再恐惧了。翻出来,一天看了2章,并没有觉得看不懂。恩,我可能是低估了自己能力。所以计划4天把书看完,并整理出笔记。先看书,再行笔记。看书过程中,如果有看不懂或理解不上来的,直接就敲了代码了。所以本笔记,没有代码。

并发问题的来源

本节内容纯属自编自导,非教程内容,不做质保。

最根本的原因是cpu运算速度远超其他存储设备,每一次脉冲就是它的一次总线宽度的二进制信号变换,变换的规则都烙印在了硬件当中,为了便于控制,形成了很多叫指令的命令(多次脉冲组合),有些指令是原子的不可再分,有些则可以再分开(不知道描述是否准确)。

进程(线程)和CPU的关系是,被CPU执行,进程和线程是被动的,它要达到什么目的,只能写到自己身上,安静的等着CPU来调度的时候由CPU来解析后被执行。个人觉得理解这点很重要。

人们为了挖掘计算机最大的算力,而设计了一套多线程机制,多个线程一起干活。多线程之间存在“公共资源”的情况。多线程的运行机制大致是,以java为例,代码编译成字节码,加载到jvm成为指令,指令被jvm翻译给cpu执行。这些个线程中的代码指令集合(编码后最终的底层指令)就开始等待cpu的时间片来宠幸自个。但是对于这些个指令来说,它是没有权利控制CPU什么时候来找你的。这就导致了,虽然线程内部的指令执行是线性的,但是多线程和线程之间的指令可就不是有顺序的,它们之间彼此交叉,是随机的,这种随机推进一步就是线程告知CPU的信息也是随机的。既然出现了顺序的随机性,在“公共资源”的情形下,就需要同步机制来控制线程的执行顺序执行。至于为什么要同步……恩,哲学问题。线程之间同步信息方可行为达预期。

一句话,时间片分配的随机性、共同资源需要顺序执行是并发问题的主要来源。高并发下,只要高级语言中的代码被转换成的指令不是原子的指令,就可能出现线程的同步问题。

第1章 多线程技能

介绍了进程线程的关系,入门级的,就不记了。以后会深化理解。

多线程的使用姿势

怎么申明多线程。

  • extend Thread 覆盖run方法
  • implement Runnable 实现run方法

Thread的构造法中支持实现了Runnble接口的类,恩,面向抽象的体现。

一些常见方法:

  • Thread.currentThread()可以获取当前线程。
  • isAlive() 判断线程是否处于活动状态
  • Thread.sleep() 休眠(暂停执行)当前线程,不会让出锁
  • getId()获取线程的唯一标识

停止线程需要说的。

阅读全文 »

计划覆盖一些常见的框架,首选spring boot,在这之前先学习spring基础原理。大致了解后再进入spring boot。所以,spring去学习的时候不会过于纠结太深入的内容。大致了解原则,这篇内容就是这套逻辑下的笔记内容。看了2套视频,前后花了6天时间。

以下内容结合了个人理解整理而成,不保证描述完全准确。

初识Spring

面向接口编程、解耦。私有变量只申明,但不new对象,变量用它的接口。

Spring解决企业级开发的痛点,数不清的类之间的相互关系,委托给Spring来管理。用个术语:装配。

Bean实际上是Spring中负责加载具体业务对象的类。Spring提供了装配类对象实例的机制。这套机制是IOC\AOP的思想,对应的具体实现是DI(依赖注入)和java的注解实现的AOP和aspectJ实现的AOP。

在学习spring过程中,突发灵感,认识到计算机世界中,context是多么的重要。context在这里我表征软件执行的环境、输入条件、依赖等前置因素。所有的代码,都是在进程开始启动过程中,完成初始化、环境构造、代码执行这么一套流程的。大体,这是目前我所理解的软件的抽象。

Spring配置类注解作为入口,把类加载到运行时中(默认是单例模式,可控制),类和类之间的组合关系,实现相同接口的接口实现之间的如何区分。都提供了注解机制。

注入和装配的套路

入门级的套路:xml配置和注解基本上在自己可控源码的化效果是等价的。在第三方的源码上,利用xml方式。

Spring的在做注入时,秉着一条“傻瓜”原则,只要spring的容器中某类的实例只有一个,那么只要你告诉(配置xml和注解)spring
那么,就会自动去绑定。如果有多个,设定必要的标识(名称、顺序等)区分也就完成了绑定工作。

注解的套路

下面的注解是入门级的示意。

1
2
3
4
5
6
7
8
9
10
11
12
13
#需要被Spring加载标识
@Component @Controller @Service @Repository

# 解决歧义(让引擎识别相同接口不同的实现对象,或者相同类的不同bean,靠名称或默认类名首字母小写)
@Autowired(required=true)
#相同接口的区分手段之一
@Qualifier
# java原生的区别手段
@Resource

# 引入外部类不方便使用Componet等时,可以加注解
# 被加了备注的方法,会自动去运行时环境中查找参数涉及的类的实例
@Bean
阅读全文 »

本文是学习Spring框架的副产品。Spring中的AOP涉及动态代理的原理。

什么是代理

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用;

目的:

  • 通过引入代理对象的方式来间接访问目标对象,以防止直接访问目标对象给系统带来的不必要的复杂性;
  • 通过代理对象对原有业务进行增强

代理设计模式

https://www.runoob.com/design-pattern/proxy-pattern.html

静态代理不能解决代码冗余的问题。一般不推荐静态代理(代理的关系用代码直接定义,代理和被代理需要有相同的接口,被代理作为代理对象的成员变量)。

jdk机制的动态代理

动态代理有以下特点:

  • 代理对象,不需要实现接口
  • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

动态代理的实现方案

  • jdk代理,通过和目标实现相同接口保证功能一致
  • cglib代理(第三方cglib库中的一套api),通过继承保证功能一致

主要涉及:

1
2
3
4
// 静态方法
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
// 接口
public interface InvocationHandler {....}
阅读全文 »

我喜欢追本溯源,渴望真理,而不至被表象迷惑。

这篇文章未写完。可能计划读一下《计算机的数学基础》这本书再写。

计算机的数学基础

形式语言与自动机理论、可计算理论、逻辑学和程序设计理论,都是研究计算模型的。它们之间也是相互关联的,共同构成了现代计算机科学技术的理论基础。这些理论都是属于数学学科的。

形式语言与自动机理论、可计算理论和逻辑学的研究都始于20世纪初叶,特别是20世纪30年代的数学家Church(邱奇)、GMel(哥德尔)、Kleene(克林)、Post(波斯特)以及Turing(图灵)等人的杰出工作催生了现代电子数字计算机的硬件和软件的诞生。

程序设计理论的研究相比则要迟一些,是20世纪后半叶现代电子数字计算机以及程序设计语言和软件诞生之后的事情了。它是专门研究程序设计语言和程序设计方法的数学理论。这些工作对于计算机科学的实践和理论的发展有着深远的影响。比如,图灵机模型就被证明是现代电子数字计算机的理论模型。这些先驱者的工作在今天看来似乎是很平常的,它们的思想渊源甚至并不为今天众多的计算机的使用者所知道。但是这些先驱者的工作确实是应该被那些从事计算机科学技术的工作者们所熟悉、所掌握的。

语言和逻辑

人类由猿进化成人,成为灵长类的智慧生物,是进化的奇迹。语言在数百万年的进化进程中,在人类这台大自然打造的“生物机器”中,也不断的进化,相互促进,形成目前人类拥有的——自然语言。

语言,我认为它的本质是承载信息的载体。它可以装载情感、请求、命令、陈述等。

自然语言是丰富而复杂的,同一句话,不同的语境、时间、预期所要传递的信息是多样的。这里面很多段子就不一一罗列了。根本原因是自然语言属于人类,它适应人类文明。自然语言的信息传递,依赖于外界环境。语言和它所传递的信息之间,没有一对一的关系,就像算命先生算命,用些模棱两可的话说给你听,还有些星座算命也是一样的,听、读的人接受信息的背景差异,导致相同语言传递的信息千差万别。

语言的诞生,促进了文明发展,萌生了逻辑、推理。先哲用语言讲理,随着理讲得越多,人们发现要讲好道理,首先要做好陈述。没有可靠的陈述作为前提,再完美的论证过程也只是空中楼阁。因此逻辑学逐渐放大研究视野,将语法和词汇承载的信息也纳入到研究范畴中。他们(我不知具体是哪些人)得出:人类使用的陈述句、疑问句、感叹句、祈使句,在着重考虑陈述和论证,就只需要陈述句了。小学学过的,各种句型互转就明白。比如:“你是谁?”,陈述句为:“我希望知道你是谁”。感叹句:“好美的女子!”,陈述句:“女子的美丽让我感叹”。

逻辑、推理进步,让自然语言作为信息传递载体做了一次丢弃,仅留下陈述句。本身语言其实就是一种和承载信息间的“指代”和“映射”。接下来对陈述句的组成部分进行拆解,仅仅做简单拆解。

代词。所谓代词就是用于代替对象的词汇,替代具体的对象出现在语句中并表示被代替的对象。比如,“月球绕地球公转,它的自转周期和地球自转周期相同”。“月球”指代我们想指代的那个对象,“它”指代月球,代词可以是指代的指代,代词的作用和语境有关。

名词。词典中约定词汇含义的方法是用一个固定句式的语句来描述它。比较简单的做法是用“某词是什么意思”的句式把词汇的含义表达出来。

谓词。一类可以将语句中其他成员建立起联系的词。“我喜欢你“,“我在写”,“我写文章”。(个人理解,可能不当)。

接下来讨论语言中的逻辑。单纯的判断你一句话是否是真或假,是没有固定结论的,因为结论依赖于说话的场景。所以,在纯逻辑层面,我们去讨论真假,就只关注语言内部的自恰性了。所以,论述的起点,它必然是整个推理的基石。在没有任何已知前提的情况下,逻辑不能给我们任何新的知识。因此必须在已经有一些已知信息的情况下,我们才能运用逻辑。题外话,基石在很多科幻电影,比如“西部世界”中很多接待员的底层都一个他们行为的基石概念。

先哲们逐步定义了一门叫数理逻辑的学科。 其研究对象是对证明和计算这两个直观概念进行符号化以后的形式系统。

https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E7%9A%84%E6%95%B0%E5%AD%A6%E5%9F%BA%E7%A1%80/866204?fr=aladdin

阅读全文 »