本文基于编写一个最小的 64 位 Hello World中的方法,粗浅的说一下编译_链接和装载。请配合编写一个最小的 64 位 Hello World食用

  • 使用gcc编译
    1. 去符号
      1. 什么是符号表?
        • 符号表是程序中函数、变量、类型等信息与源代码的关联
      2. 符号表的生成?
        • 程序在编译过程中会生成符号表并进行语法和语义的检查,如果存在语义错误将会被丢弃反之将生成目标文件并包含符号表。<目标文件中的符号表将用于链接>
      3. 去除符号表的作用
        • 减小文件大小:符号表通常占据了文件中相当数量的空间,去除符号表可以有效减小文件大小。
        • 提高加载速度:符号表中包含了定义的所有符号信息,去除符号表可以提高加载速度。
        • 增加安全性:移除符号表可以一定程度上提高安全性,因为去除符号表后,程序的符号信息不再容易被泄露。
        • 降低调试能力:去除符号表后,调试程序时可能会变得更加困难,因为无法准确定位符号信息。
    2. 优化,开启优化以后编译器会生成更加高效的指令,减少文件体积
    3. 去掉对crt库的链接
      • c语言程序入口为什么都是main函数?
        • 可执行文件的文件头中,记录了可执行文件的指令入口地址,它指示了程序该从哪里开始执行,在这里跳转到程序的真正入口地址。所以可以设置自定义函数为程序的入口,只是默认为main函数
    • c内联汇编
      1. 如果程序比较小巧的话可以把库函数改为系统调用,涉及到汇编语言
      2. 由于编译器会进行内存对齐,导致size还是很大。可以自己写一个脚本去除所有无关的内容,这样size就会大幅度缩小。<文章内有用的内容是 .test .data .rodata>
  • 使用汇编
    1. 直接使用汇编编写程序
    2. elf文件是编译器格式化生成的,可以手动生成elf文件从而达到对每个字节的控制
    3. 关于为 Linux 创建真正的小型 ELF可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
custom Linker Script:
ENTRY(nomain)

SECTIONS
{
. = 0x8048000 + SIZEOF_HEADERS;

tiny : { *(.text) *(.data) *(.rodata*) }

/DISCARD/ : { *(*) }
}
EOF

这段代码看起来是一个链接脚本(Linker Script),用于告诉链接器(linker)如何组织可执行文件的各个部分。在这个链接脚本中:

ENTRY(nomain) 指定了程序的入口点为 nomain。
SECTIONS 部分定义了程序的各个段(sections)的布局。
. = 0x8048000 + SIZEOF_HEADERS; 将当前位置设置为 0x8048000 加上头部的大小。
tiny : { *(.text) *(.data) *(.rodata*) } 定义了一个名为 tiny 的段,包含了.text、.data和.rodata*这些部分。
/DISCARD/ : { *(*) } 告诉链接器在链接时丢弃所有未匹配到其他段的内容。
总的来说,这段代码的目的是定义了程序的段布局和入口点,以便链接器能够正确地组装可执行文件。

1715852094995.png

  • ELF header:字的大小、字节顺序、目标文件类型(.o,exec,.so)、机器类型等
  • Segment header table:段头表,也叫Program header,告诉系统如何创建一个进程
  • .text:已经编译好的机器代码
  • .rodata:只读数据
  • .data:已初始化的全局和静态变量,局部变量在运行时被保存在栈中,不在.data和.bss中
  • .bss:未初始化的全局和静态变量,以及所有被初始化为0的全局和静态变量。在节头表中有一项,但是不占用实际存储空间
  • .symtab:符号表
  • .rel.txt:.text节重定位信息
  • .rel.data:.data节重定位信息
  • .debug:调试符号表(gcc -g)
  • Section header table:节头表,包含各个节的大小和偏移

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
47
48
49
50
51
52
53
54
55
56
57
custom elf file:
BITS 64
org 0x400000

ehdr: ; Elf64_Ehdr
db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
times 8 db 0
dw 2 ; e_type
dw 0x3e ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq phdr - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
ehdrsize equ $ - ehdr

phdr: ; Elf64_Phdr
dd 1 ; p_type
dd 5 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq filesize ; p_filesz
dq filesize ; p_memsz
dq 0x1000 ; p_align
phdrsize equ $ - phdr

_start:
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 13
syscall
mov rax, 60
xor rdi, rdi
syscall

message: db "hello, world", 0xa

filesize equ $ - $$

这段代码是一个简单的64位ELF可执行文件的汇编代码示例。下面是对代码的解读:

首先,通过BITS 64指令指定了使用64位模式。
org 0x400000指令将程序的起始地址设置为0x400000。
ehdr部分定义了ELF文件头(Elf64_Ehdr)的结构,包括标识符、类型、机器类型、入口地址等信息。
phdr部分定义了程序头表(Elf64_Phdr)的结构,包括段类型、标志、偏移量、虚拟地址、物理地址、文件大小、内存大小等信息。
_start部分是程序的入口点,包括一些系统调用来输出"hello, world"字符串并退出程序。
message部分包含要输出的字符串"hello, world"。
filesize部分计算了文件的大小。
这段代码实际上是一个简单的汇编程序,用于生成一个最小的64位ELF可执行文件,其中包含一个输出"hello, world"字符串的程序。

参考文章:

在计算机编程和链接的上下文中,内部链接和外部链接是两个重要的概念,主要用于描述符号(如变量和函数)的可见性和作用域。

内部链接(Internal Linkage)
定义:内部链接指的是符号的可见性仅限于定义它的编译单元(通常是一个源文件)。其他编译单元无法访问这些符号。
特征:通常通过在变量或函数前加上 static 关键字来实现。这样做可以避免与其他编译单元中的同名符号发生冲突。
外部链接(External Linkage)
定义:外部链接指的是符号可以在多个编译单元之间共享和访问。其他编译单元可以引用这些符号。
特征:如果一个变量或函数没有使用 static 关键字声明,它就具有外部链接属性,可以在其他源文件中使用。
总结
内部链接:符号仅在当前文件可见,适用于避免命名冲突。
外部链接:符号在多个文件间可见,适用于需要共享的变量和函数。