linux pwn 基础知识

环境搭建

虚拟机安装

  • 镜像下载网站
  • 为了避免环境问题建议 22.04 ,20.04,18.04,16.04 等常见版本 ubuntu 虚拟机环境各准备一份。注意定期更新快照以防意外。
  • 虚拟机建议硬盘 256 G 以上,内存也尽量大一些。硬盘大小只是上界,256 G 不是真就占了 256 G,而后期如果硬盘空间不足会很麻烦。

基础工具

vim

sudo apt install vim

gedit

不习惯 vim 的可以使用 gedit 文本编辑器。

sudo apt install gedit

git

sudo apt install git

gcc

sudo apt install gcc

python

ipython 提供了很好的 python 交互命令行,建议安装。

sudo apt install python2 ipython2
sudo apt install python3 ipython3

另外有的版本 ubuntu 的不好安装 pip2 可以使用 get-pip.py 脚本安装。

curl https://bootstrap.pypa.io/get-pip.py --output get-pip.py
sudo python2 get-pip.py

pwn 相关工具

gdb

sudo apt-get install gdb gdb-multiarch

pwntools

注意我这里的 pwntools 是 python2 版本的。

pip install pwntools

这样安装的 pwntools 的 plt 功可能无法正常使用,需要手动安装 Unicorn 库。

pip install unicorn==1.0.3

gdb 插件

主要有 pwndbg,peda,gef ,这里我常用的是 pwndbg 。对于一些版本过于古老导致环境装不上的可以尝试一下 peda 。

先将三个项目的代码都拉取下来。

git clone https://github.com/longld/peda.git
git clone https://github.com/pwndbg/pwndbg.git
git clone https://github.com/hugsy/gef.git

pwndbg 需要运行初始化脚本。

cd pwndbg & sudo ./setup.sh

另外还有一个 pwngdb 插件在调试多线程堆(heapinfoall 命令)的时候很有用,建议安装。

git clone https://github.com/scwuaptx/Pwngdb.git 

gdb 在启动的时候会读取当前用户的主目录的 .gdbinit 文件进行 gdb 插件的初始化,这里提供一个配置方案。

source /home/sky123/tools/pwndbg/gdbinit.py 
#source /home/sky123/tools/peda/peda.py
#source /home/sky123/tools/gef/gef.py#source /home/sky123/tools/muslheap/muslheap.pysource /home/sky123/tools/Pwngdb/pwngdb.py
source /home/sky123/tools/Pwngdb/angelheap/gdbinit.pydefine hook-run
python
import angelheap
angelheap.init_angelheap()
end
end

注意,以普通用权限和管理员权限启动 gdb 时读取的 .gdbinit 文件的路径是不同的,普通权限读取的是 /home/<username>/.gdbinit 而管理员权限读取的是 /root/.gdbinit

gadget 搜索工具

ROPgdbget

安装:

git clone https://github.com/JonathanSalwan/ROPgadget.git
cd ROPgadget
sudo python3 setup.py install

使用:

 ROPgadget --binary ntdll.dll > rop

ropper

  • 安装:
    • 在 pypi 的 ropper 官网上下载 ropper
    • 运行安装脚本完成 ropper 安装
      python setup.py install
      
  • 使用:
    ropper --file ./pwn --nocolor > rop
    

one_gadget

用于搜索 libc 中能够实现 execve("/bin/sh", (char *[2]) {"/bin/sh", NULL}, NULL); 的效果的跳转地址,由于是采用特征匹配的方法,因此只能是在 libc 中查找。

  • 安装:
    sudo apt install -y ruby ruby-dev
    sudo gem install one_gadget
    
  • 使用:可以查找到 gadget 地址以及条件限制。
    ➜  ~ one_gadget /lib/x86_64-linux-gnu/libc.so.6
    0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
    constraints:rsp & 0xf == 0rcx == NULLrbp == NULL || (u16)[rbp] == NULL0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
    constraints:address rbp-0x78 is writable[r10] == NULL || r10 == NULL[[rbp-0x70]] == NULL || [rbp-0x70] == NULL0xebcf5 execve("/bin/sh", r10, rdx)
    constraints:address rbp-0x78 is writable[r10] == NULL || r10 == NULL[rdx] == NULL || rdx == NULL0xebcf8 execve("/bin/sh", rsi, rdx)
    constraints:address rbp-0x78 is writable[rsi] == NULL || rsi == NULL[rdx] == NULL || rdx == NULL
    

seccomp-tools

用于查看和生成程序沙箱规则。

  • 安装:
    sudo gem install seccomp-tools
    
  • 使用:
    seccomp-tools dump ./pwn
    

LibcSearcher

通过泄露的 libc 中函数的地址来确定 libc 版本。

git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
sudo python3 setup.py install

patchelf

用于对于依赖不是很复杂的程序更换 libc ,有一下几点需要注意:

  • 如果在漏洞利用时用到了动态链接相关结构最好不要 patchelf,因为 patchelf 会改变动态链接相关结构的位置。
  • 一个程序在一个版本的虚拟机里面 patchelf 后换到另一个版本虚拟机中可能会运行失败。
  • 在 patch 完 libc 后最好把 ld 也 patch 成大版本相同的 ld ,否则会运行失败。

安装:

sudo apt install patchelf

修改 libc:

patchelf --replace-needed libc.so.6 ./libc.so.6 ./pwn

修改 ld:

patchelf --set-interpreter ./ld-2.31.so ./pwn

qemu

sudo apt install qemu-user qemu-system 

工具使用

docker

shell

ssh

ELF 文件格式

ELF(Executable and Linkable Format)是一种常见的可执行文件和可链接文件格式,主要用于Linux和类Unix系统。ELF 文件可以包含不同的类型,常见的 ELF 文件类型包括:

  • 可执行文件(ET_EXEC):这种类型的 ELF 文件是可直接执行的程序,可以在操作系统上运行。
  • 共享目标文件(ET_DYN):这种类型的 ELF 文件是可被动态链接的共享库,可以在运行时与其他程序动态链接。该类型文件后缀名为 .so
  • 可重定位文件(ET_REL):这种类型的 ELF 文件是编译器生成的目标文件,通常用于将多个目标文件链接到一个可执行文件或共享库中。该类型文件后缀名为 .o ,静态链接库(.a)也可以归为这一类。
  • 核心转储文件(ET_CORE):这种类型的 ELF 文件是操作系统在程序崩溃或发生错误时生成的核心转储文件,用于调试和分析程序崩溃的原因。

ELF 文件结构及相关常数被定义在 /usr/include/elf.h 里,因为 ELF 文件在各种平台下都通用,ELF文件有 32 位版本和 64 位版本。32 位版本与 64 位版本的 ELF 文件的格式基本是一样的(部分结构体为了优化对齐后大小调整了成员的顺序),只不过有些成员的大小不一样。

elf.h 使用 typedef 定义了一套自己的变量体系:

自定义类型描述原始类型长度(字节)
Elf32_Addr32 位版本程序地址uint32_t4
Elf32_Half32 位版本的无符号短整型uint16_t2
Elf32_Off32 位版本的偏移地址uint32_t4
Elf32_Sword32 位版本有符号整型uint32_t4
Elf32_Word32 位版本无符号整型int32_t4
Elf64_Addr64 位版本程序地址uint64_t8
Elf64_Half64 位版本的无符号短整型uint16_t2
Elf64_Off64 位版本的偏移地址uint64_t8
Elf64_Sword64 位版本有符号整型uint32_t4
Elf64_Word64 位版本无符号整型int32_t4

ELF 主要管理结构为文件头,程序头表(可重定位文件没有)和节表,其他部分有一个个节组成,多个属性相同的节构成一个段。对于节的介绍这里按照静态链接相关和动态链接相关分别介绍。
在这里插入图片描述

文件头

我们这里以 32 位版本的文件头结构 Elf32_Ehdr 作为例子来描述,它的定义如下:

/* 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;
  • e_ident:ELF 文件的魔数和其他信息。
    • 前 4 字节为 ELFMAG\x7fELF
    • 第 5 字节为 ELF 文件类型,值为 ELFCLASS32(1) 代表 32 位,值为 ELFCLASS64(2) 代表 64 位。
    • 第 6 字节为 ELF 的字节序,0 为无效格式,1 为小端格式,2 为大端格式。
    • 第 7 字节为 ELF 版本,一般为 1 ,即 1.2 版本。
    • 后面 9 字节没有定义一般填 0 ,有些平台会使用这 9 个字节作为扩展标志。
  • e_type:表示ELF文件类型,如可执行文件、共享对象文件(.so)、可重定位文件(.o)等。
  • e_machine:表示目标体系结构,即程序的目标平台,如 x86、ARM 等。相关常量以 EM_ 开头。
  • e_version:ELF 文件版本号,一般为常数 1 。
  • e_entry:表示程序入口点虚拟地址。操作系统加载完程序后从这个地址开始执行进程的命令。可重定位文件一般没有入口地址,则这个值为 0 。
  • e_phoff:表示程序头表的文件偏移量。
  • e_shoff:表示节表的文件偏移量。
  • e_flags:表示处理器特定标志。
  • e_ehsize:表示 ELF 文件头的大小。
  • e_phentsize:表示程序头表中每个表项的大小。
  • e_phnum:表示程序头表中表项的数量。
  • e_shentsize:表示节表中每个表项的大小。
  • e_shnum:表示节表中表项的数量。
  • e_shstrndx表示节表中字符串表的索引。

程序头表

ELF 可执行文件中有一个专门的数据结构叫做程序头表(Program Header Table)用来保存注意不是节)的信息。因为 ELF 目标文件不需要被装载,所以它没有程序头表,而 ELF 的可执行文件共享库文件都有程序头表。

程序头表是由 Elf*_Phdr 组成的数组,用于描述 ELF 文件中每个节的属性和信息。

/* 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;
  • p_type:段的类型,例如可执行段、数据段等。
  • p_offset:段在文件中的偏移量。
  • p_vaddr:段在虚拟内存中的起始地址。
  • p_paddr:段在物理内存中的起始地址。因为 ELF 还没装载不知道物理地址,所以作为保留字段。通常和 p_vaddr 的值是一样的。
  • p_filesz:段在文件中的大小。
  • p_memsz:段在内存中的大小。
  • p_flags:段的标志,例如可读、可写、可执行等。
  • p_align:段在文件和内存中的对齐方式。段的的加载地址要能被 2 p_align 2^{\text{p\_align}} 2p_align 整除。

节表

ELF文件里面定义一个固定长度的 Elf*_Shdr 结构体数组用来存放相关信息,与 PE 文件的节表相似。

在 ELF 文件中,(Segment)和(Section)是两个不同的概念,它们在文件结构中具有不同的作用和目的。

段(Segment)是一种逻辑上的组织单位,它定义了可执行文件或共享库在内存中的一个连续区域。每个段都有自己的虚拟地址空间,可以包含多个节。常见的段类型包括代码段(.text),数据段(.data.bss),只读数据段(.rodata)等。段在加载和执行时被操作系统用来管理内存,设置内存保护属性以及指定虚拟地址空间的起始地址和大小。

节(Section)是一种更细粒度的组织单位,它包含了文件中的特定类型的数据或代码。每个节都有自己的名字、类型和内容。常见的节类型包括代码节(.text),数据节(.data.bss),只读数据节(.rodata),符号表节(.symtab),字符串表节(.strtab)等。节不直接参与内存的加载和执行,而是用于链接器(Linker)和调试器(Debugger)等工具对文件进行处理和分析。

通俗的讲,在装载程序的时候为了节省内存会将 ELF 文件中属性相同的节(Section)合并成在一个段(Segment)加载到内存中。

段和节之间存在对应关系和映射关系:

  • 一个段可以包含多个节,这些节的内容和属性都属于该段。
  • 段提供了对应于虚拟内存的逻辑映射,而节则提供了对应于文件的逻辑映射。
  • 段的加载和执行涉及内存管理和地址映射,而节则用于链接和调试过程中的符号解析、重定位等操作。

其中 Elf32_Shdr 定义如下:

/* 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;
  • sh_name:表示节的名称在字符串表中的索引。字符串表节存储了所有节的名称,sh_name 指定了节的名称在字符串表中的位置。
  • sh_type:表示节的类型,指定了节的用途和属性。常见的类型包括代码段(SHT_PROGBITS(1))、数据段(SHT_PROGBITS(1))、符号表(SHT_SYMTAB(2))、字符串表(SHT_STRTAB(3))等。
  • sh_flags:表示节的标志,用于描述节的特性和属性。标志的具体含义取决于节的类型和上下文。
  • sh_addr:表示节的虚拟地址,只在可执行文件中有意义。对于可执行文件,sh_addr 指定了节在内存中的加载地址,如果该节不可被加载,则该值为 0 。
  • sh_offset:表示节在文件中的偏移量,指定了节在文件中的位置。对于 bss 段来说该值没有意义。
  • sh_size:表示节的大小,指定了节所占据的字节数。
  • sh_link:表示链接到的其他节的索引,用于建立节之间的关联关系,具体含义依赖于节的类型。
  • sh_info:附加信息,具体含义依赖于节的类型。
  • sh_addralign:表示节的地址对齐要求,指定了节在内存中的对齐方式。即 sh_addr 需要满足 sh_addr m o d 2 sh_addralign = 0 \text{sh\_addr} \mod 2^{\text{sh\_addralign}} = 0 sh_addrmod2sh_addralign=0 。如果 sh_addralign 为 0 或 1 表示该段没有对齐要求。
  • sh_entsize:表示节中每个项的大小,如果该字段为 0 说明节中不包含固定大小的项。

ELF 中常见的节如下:

  • .text:代码段(Code Section),用于存储程序的可执行指令。
  • .rodata:只读数据段(Read-Only Data Section),用于存储只读的常量数据,例如字符串常量。
  • .data:数据段(Data Section),用于存储已初始化的全局变量和静态变量。
  • .bss:未初始化的数据段(Block Started by Symbol),用于存储未初始化的全局变量和静态变量。它不占用实际的文件空间,而是在运行时由系统自动初始化为零。
  • .symtab:符号表节(Symbol Table Section),用于存储程序的符号表信息,包括函数、变量和其他符号的名称、类型和地址等。
  • .strtab:字符串表节(String Table Section),用于存储字符串数据,如节名称、符号名称等。字符串表节被多个其他节引用,通过偏移量和索引来访问具体的字符串。
  • .rel.text.rela.text:代码重定位节(Relocation Section),用于存储代码段中的重定位信息,以便在链接时修正代码中的符号引用。
  • .rel.data.rela.data:数据重定位节(Relocation Section),用于存储数据段中的重定位信息,以便在链接时修正数据段中的符号引用。
  • .dynamic:动态节(Dynamic Section),用于存储程序的动态链接信息,包括动态链接器需要的重定位表、共享对象的名称、版本信息等。
  • .note:注释节(Note Section),用于存储与程序或库相关的注释或调试信息。

静态链接相关

注意:静态链接相关只在可重定位文件中存在。比如可执行文件,如果不开启 PIE 加载地址固定,不需要对自身进行重定位,而开启 PIE 后为地址无关代码,也不需要对自身进行重定位。因此不需要静态链接也就丢弃了静态链接相关的节。

符号表(.symtab)

注意:符号表除了静态链接外没有用,但是程序为了方便调试会保留符号表,我们可以通过 strip + 程序名 的方式将符号表去除,这就是为什么有的 pwn 题的附件没有函数和变量名而有的却有。

ELF 文件中的符号表往往是文件中的一个段,段名一般叫 .symtab 。符号表是一个 Elf*_Sym 结构(32 位 ELF 文件)的数组,每个 Elf*_Sym 结构对应一个符号。

/* 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:符号的值,即符号的地址或偏移量。
    • 如果该符号在目标文件中,如果是符号的定义并且该符号不是 COMMON 块类型的则 st_value 表示该符号在段中的偏移
    • 目标文件中,如果符号是 COMMON 块类型的则 st_value 表示该符号的对齐属性
    • 可执行文件中,st_value 表示符号的虚拟地址
  • st_size:符号的大小,如果符号是一个函数,则表示函数的大小。如果该值为 0 表示符号的大小为 0 或未知。
  • st_info:该字段是一个字节,包含符号的类型和绑定信息。符号类型包括函数、数据、对象等,符号绑定包括局部符号、全局符号、弱符号等。该字段的高 4 位表示符号的类型,低 4 位表示符号的绑定信息。
  • st_other:保留字段,通常为 0 。
  • st_shndx:通常为符号所在的索引。
    • 如果符号是一个常量,该字段为 SHN_ABS(初始值不为 0 的全局变量) 或 SHN_COMMON(初始值为 0 的全局变量)。
    • 如果该符号未定义但是在该文件中被引用到,说明该符号可能定义在其他目标文件中,则该字段为 SHN_UNDEF

重定位表(.rel.text/.rel.data)

重定位表是一个 Elf*_Rel 结构的数组,每个数组元素对应一个重定位入口。重定位表主要有.rel.text.rela.text,即代码重定位节(Relocation Section)和 .rel.data.rela.data:数据重定位节(Relocation Section)。

/* 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;
  • r_offset:需要进行重定位的位置的偏移量或地址。这个位置通常是指令中的某个操作数或数据的地址,需要在链接时进行修正,以便正确地引用目标符号。
    • 对于可执行文件或共享库,r_offset 表示需要修改的位置在内存中的位置(用于动态链接)。
    • 对于可重定位文件r_offset 表示需要修改的位置相对于段起始位置的偏移(用于静态链接)。
  • r_info:低 8 位表示符号的重定位类型,重定位类型指定了进行何种类型的修正,例如绝对重定位、PC 相对重定位等。高 24 位表示该符号在符号表中的索引,用于解析重定位所引用的符号。

字符串表(.strtab)

ELF 文件中用到了很多字符串,比如段名、变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。

通过这种方法,在ELF文件中引用字符串只须给出一个数字下标即可,不用考虑字符串长度的问题。一般字符串表在ELF文件中也以段的形式保存,常见的段名为“.strtab”或“.shstrtab”。这两个字符串表分别为字符串表(String Table)和段表字符串表(Section Header String Table)。顾名思义,字符串表用来保存普通的字符串,比如符号的名字;段表字符串表用来保存段表中用到的字符串,最常见的就是段名(sh_name )。

注意,在字符串表中的每个字符串的开头结尾都有一个 \x00 填充。

动态链接相关

.interp 段

在动态链接的 ELF 可执行文件中,有一个专门的段叫做 .interp 段(“interp”是“interpreter”(解释器)的缩写)。

.interp 的内容很简单,里面保存的就是一个字符串 /lib64/ld-linux-x86-64.so.2 ,这个字符串就是可执行文件所需要的动态链接器的路径。

通常系统通过判断一个 ELF 程序是否有 .interp 来判断该 ELF 文件是否为动态链接程序。

.dynamic 段

动态链接 ELF 中最重要的结构是 .dynamic 段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。.dynamic 段是由Elf*_Dyn 构成的结构体数组。

/* Dynamic section entry.  */typedef struct
{Elf32_Sword	d_tag;			/* Dynamic entry type */union{Elf32_Word d_val;			/* Integer value */Elf32_Addr d_ptr;			/* Address value */} d_un;
} Elf32_Dyn;

Elf32_Dyn 结构由一个类型值加上一个附加的数值或指针,对于不同的类型,后面附加的数值或者指针有着不同的含义。我们这里列举几个比较常见的类型值(这些值都是定义在 elf.h 里面的宏),

  • DT_SYMTAB:指定了符号表的地址,d_ptr 表示 .dynsym 的地址。
  • DT_STRTAB:指定了字符串表的地址,d_ptr 表示 .synstr 的地址。
  • DT_STRSZ:指定了字符串表的大小,d_val 表示大小。
  • DT_HASH:指定了符号哈希表的地址,用于加快符号查找的速度,d_ptr 表示 .hash 的地址。
  • DT_SONAME:指定了共享库的名称。
  • DT_RPATH:指定了库搜索路径(已废弃,不推荐使用)。
  • DT_INIT:指定了初始化函数的地址,动态链接器在加载可执行文件或共享库时会调用该函数。
  • DT_FINI:指定了终止函数的地址,动态链接器在程序结束时会调用该函数。
  • DT_NEEDED:指定了需要的共享库的名称。
  • DT_REL/DT_RELA:指定了重定位表的地址。

动态符号表(.dynsym)

为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。我们知道在静态链接中,有一个专门的段叫做符号表 .symtab(Symbol Table),里面保存了所有关于该目标文件的符号的定义和引用。为了表示动态链接这些模块之间的符号导入导出关系,ELF 专门有一个叫做动态符号表(Dynamic Symbol Table)的段用来保存这些信息,这个段的段名通常叫做 .dynsym(Dynamic Symbol),同样也是由 Elf*_Sym 构成的结构体数组。

.symtab 不同的是,.dynsym 只保存了与动态链接相关的符号,对于那些模块内部的符号,比如模块私有变量则不保存。很多时候动态链接的模块同时拥有 .dynsym.symtab 两个表,.symtab 中往往保存了所有符号,包括 .dynsym 中的符号。

.symtab 类似,动态符号表也需要一些辅助的表,比如用于保存符号名的字符串表。静态链接时叫做符号字符串表 .strtab(String Table),在这里就是动态符号字符串表 .dynstr(Dynamic String Table);由于动态链接下,我们需要在程序运行时查找符号,为了加快符号的查找过程,往往还有辅助的符号哈希表(.hash)。

动态链接重定位表(.rel.dyn/.rel.data)

共享对象需要重定位的主要原因是导入符号的存在。动态链接下,无论是可执行文件或共享对象,一旦它依赖于其他共享对象,也就是说有导入的符号时,那么它的代码或数据中就会有对于导入符号的引用。在编译时这些导入符号的地址未知,在静态链接中,这些未知的地址引用在最终链接时被修正。但是在动态链接中,导入符号的地址在运行时才确定,所以需要在运行时将这些导入符号的引用修正,即需要重定位。

共享对象的重定位与我们在前面“静态链接”中分析过的目标文件的重定位十分类似,唯一有区别的是目标文件的重定位是在静态链接时完成的,而共享对象的重定位是在装载时完成的。在静态链接中,目标文件里面包含有专门用于表示重定位信息的重定位表,比如 .rel.text 表示是代码段的重定位表,.rel.data 是数据段的重定位表。

动态链接的文件中,也有类似的重定位表分别叫做 .rel.dyn.rel.plt ,它们分别相当于 .rel.data.rel.text.rel.dyn 实际上是对数据引用的修正,它所修正的位置位于 .got 以及数据段;而 .rel.plt 是对函数引用的修正,它所修正的位置位于 .got.plt

PLT 表(.plt)

在未开启 FULL RELRO 的情况下 PLT 表的结构如下图所示, PLT 表在 .plt 中。
在这里插入图片描述
PLT 表的形式如下所示:
PLT0: push *(GOT+8) jmp *(GOT+16) ⋮ bar@PLT: jmp *(bar@GOT) push n jmp PLT0 \begin{align*} & \text{PLT0:} \\ & \qquad \text{push *(GOT+8)} \\ & \qquad \text{jmp *(GOT+16)} \\ & \qquad \vdots \\ & \text{bar@PLT:} \\ & \qquad \text{jmp *(bar@GOT)} \\ & \qquad \text{push n} \\ & \qquad \text{jmp PLT0} \\ \end{align*} PLT0:push *(GOT+8)jmp *(GOT+16)bar@PLT:jmp *(bar@GOT)push njmp PLT0
其中 n n n 为函数 bar 在 GOT 表中的值的索引,bar@GOT 中初始值为 jmp *(bar@GOT) 指令的下一条指令,也就是说第一次调用 bar 函数的时候会继续执行跳转至 PLT0 进行 bar@GOT 的重定位并调用 bar 函数;第二次调用 bar 函数的时候由于 bar@GOT 已完成重定位因此会直接跳转至 bar 函数。

在开启 FULL RELRO 的情况下 PLT 表的结构如下图所示,此时的 PLT 表在 .plt.sec 而不是 .plt 中。
在这里插入图片描述
由于 GOT 表在装载时已经完成重定位且不可写,因此不存在延迟绑定,PLT 直接根据 GOT 表存储的函数地址进行跳转。

GOT 表(.got/.got.plt)

ELF 将 GOT 拆分成了两个表叫做 .got.got.plt 。其中 .got 用来保存全局变量引用的地址,.got.plt 用来保存函数引用的地址,也就是说,所有对于外部函数的引用全部被分离出来放到了 .got.plt 中。另外 .got.plt 还有一个特殊的地方是它的前三项是有特殊意义的,分别含义如下:

  • 第一项保存的是 .dynamic 段的偏移。
  • 第二项是一个 link_map 的结构体指针,里面保存着动态链接的一些相关信息,是重定位函数 _dl_runtime_resolve 的第一个参数。
  • 第三项保存的是 _dl_runtime_resolve 的地址。

.got.plt 在内存中的状态如下图所示:
在这里插入图片描述

辅助信息数组

站在动态链接器的角度看,当操作系统把控制权交给它的时候,它将开始做链接工作,那么至少它需要知道关于可执行文件和本进程的一些信息,比如可执行文件有几个段(“Segment”)、每个段的属性、程序的入口地址(因为动态链接器到时候需要把控制权交给可执行文件)等。

这些信息往往由操作系统传递给动态链接器,保存在进程的堆栈里面。我们在前面提到过,进程初始化的时候,事实上,堆栈里面还保存了动态链接器所需要的一些辅助信息数组(Auxiliary Vector)。辅助信息的格式也是一个结构数组,它的结构被定义在 elf.h

typedef struct
{uint32_t a_type;		/* Entry type */union{uint32_t a_val;		/* Integer value *//* We use to have pointer elements added here.  We cannot do that,though, since it does not work when using 32-bit definitionson 64-bit platforms and vice versa.  */} a_un;
} Elf32_auxv_t;
  • a_type 字段表示辅助信息数组的类型。下面是一些常见的 a_type 值及其对应的含义:
    • AT_NULL (0):辅助向量列表的结束标志。在列表的最后一个条目中使用。
    • AT_IGNORE (1):忽略的辅助向量类型。在某些情况下,可以将该类型的辅助向量忽略。
    • AT_EXECFD (2):可执行文件的文件描述符。表示打开可执行文件的文件描述符。
    • AT_PHDR (3):程序头表的地址。指向程序头表在内存中的起始地址。
    • AT_PHENT (4):程序头表中每个条目的大小(字节)。指示每个程序头表条目的字节数。
    • AT_PHNUM (5):程序头表的条目数量。指示程序头表中的条目数量。
    • AT_PAGESZ (6):页面大小。表示操作系统使用的页面大小。
    • AT_BASE (7):共享对象的基地址。指向主共享对象的基地址。
    • AT_FLAGS (8):标志位。包含一些特定于操作系统的标志。
    • AT_ENTRY (9):程序入口点的地址。指向程序的入口点地址。
    • AT_NOTELF (10):不是ELF文件。指示加载程序的文件不是有效的ELF文件。
  • a_un:该成员是一个联合体(union),用于存储辅助向量条目的值。在这段代码中,由于指针类型的元素会在 32 位和 64 位平台上产生兼容性问题,所以注释中提到不再添加指针元素。
    • a_val:如果辅助向量条目的类型是一个整数值,那么该成员将存储该整数值。它也是一个 32 位的无符号整数。

程序编译过程

从源文件编译链接形成 ELF 文件的过程如下图所示:
在这里插入图片描述

预编译

首先是源代码文件和相关的头文件,如 stdio.h 等被预编译器 cpp 预编译成一个 .i 文件。对于 C++ 程序来说,它的源代码文件的扩展名可能是 .cpp.cxx ,头文件的扩展名可能是 .hpp ,而预编译后的文件扩展名是 .ii

第一步预编译的过程相当于如下命令(-E 表示只进行预编译):

gcc –E hello.c –o hello.i

或者:

cpp hello.c > hello.i

预编译过程主要处理那些源代码文件中的以 # 开始的预编译指令。比如 #include#define 等,主要处理规则如下:

  • 将所有的 #define 删除,并且展开所有的宏定义。
  • 处理所有条件预编译指令,比如 #if#ifdef#elif#else#endif
  • 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
  • 删除所有的注释 ///* */
  • 添加行号和文件名标识,比如 #2"hello.c"2 ,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
  • 保留所有的 #pragma 编译器指令,因为编译器须要使用它们。

经过预编译后的 .i 文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到 .i 文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。

编译

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。

上面的编译过程相当于如下命令:

gcc –S hello.i –o hello.s

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了,“汇编”这个名字也来源于此。

上面的汇编过程我们可以调用汇编器 as 来完成:

as hello.s –o hello.o

或者使用 gcc 命令从 C 源代码文件开始,经过预编译、编译和汇编直接输出目标文件(Object File):

gcc –c hello.c –o hello.o

链接

静态链接

静态链接是在编译过程的最后阶段将多个目标文件(如 .o 文件)以及所需的库文件合并在一起,生成最终的可执行文件或共享库的过程。

可以使用如下命令将 a.ob.o 链接为目标文件 ab 。

ld a.o b.o -o ab

合并代码和数据段(Code and Data Segment Merging)

链接器将多个目标文件中的代码段和数据段合并成一个更大的代码段和数据段。这样,所有的目标文件中的代码和数据都会被整合到最终的可执行文件或静态库中。

符号解析(Symbol Resolution)

链接器负通过重定位表解析目标文件中的符号引用。每个目标文件都包含对其他目标文件或库中定义的符号的引用,例如函数、变量等。链接器会检查这些引用并确定对应的定义位置。

对于可重定位的 ELF 文件来说,它必须包含有重定位表,用来描述如何修改相应的段里的内容。对于每个要被重定位的 ELF 段都有一个对应的重定位表,而一个重定位表往往就是 ELF 文件中的一个段,所以其实重定位表也可以叫重定位段。

比如代码段 .text 如有要被重定位的地方,那么会有一个相对应叫 .rel.text 的段保存了代码段的重定位表;如果代码段 .data 有要被重定位的地方,就会有一个相对应叫 .rel.data 的段保存了数据段的重定位表。

链接器通过 Elf32_Rel r_offset 加上所在段的起始位置得到重定位入口的位置;通过 r_info 的低 8 为得知重定位类型;通过 r_info 的高 24 位得到重定位符号在符号表(.symtab)中的下标。

符号重定位(Symbol Relocation)

链接器通过符号表对应的 Elf32_Relst_value 表示该符号在段中的偏移,进而可以根据重定位类型计算出重定位入口所要修正的值。最后将对应的重定位入口 patch 成正确的值。32 位静态链接常用到的重定位类型如下:

  • R_386_32:绝对地址。
  • R_386_PC32:相对于当前指令地址的下一条指令相对地址。

解析库依赖关系(Library Dependency Resolution)

如果目标文件依赖于外部库文件(如标准库或其他第三方库),链接器会解析这些库的依赖关系,并将所需的库文件链接到最终的可执行文件或静态库中。这样,在运行时,可执行文件或静态库就能够访问和使用这些库中提供的功能。

生成重定位表(Relocation Table)

链接器生成重定位表,记录了需要进行符号重定位的位置和相关信息。这些重定位表将在最终的可执行文件或静态库中被使用,以便在加载和执行时进行正确的符号重定位。

动态链接

动态链接(Dynamic Linking)本质是指把链接这个过程推迟到了运行时再进行,准确的说这个过程应该放在装载部分。不过动态链接的出现很大一部分原因是为了解决内存浪费问题,因此直接照搬静态链接的方式不合理,需要做一些改变。

另外我们称一个程序为动态链接程序或静态链接程序指的是该程序是否有动态链接过程。

注意动态链接不包括合并代码和数据段的过程,各个模块在内存中独立存在。

装载时重定位

由于需要将多个模块装载到内存中,因此动态链接难免会有地址冲突问题,这就需要我们在加载的时候将模块中的相关地址修改为正确的值,这就是装载时重定位。

Linux和GCC支持这种装载时重定位的方法,在产生共享对象时,使用了两个GCC参数 -shared-fPIC ,如果只使用 -shared ,那么输出的共享对象就是使用装载时重定位的方法。

地址无关代码

如果采用装载时重定位的方法虽然能够做到任意地址装载,但存在弊端。比如模块装载到不同位置会导致模块的代码段内容发生改变,无法实现共享库的复用,造成内存浪费;每次装载重定位会影响性能等。

地址无关代码的出现很好的解决了装载时重定位的缺点。地址无关代码的基本想法就是把指令中那些需要被修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本。这种方案就是目前被称为地址无关代码(PIC,Position-independent Code)的技术。这也就是 GCC 的 -fPIC 编译参数。

模块中各种类型的地址引用方式有以下 4 种:

  • 模块内部的函数调用、跳转等。
  • 模块内部的数据访问,比如模块中定义的全局变量、静态变量。
  • 模块外部的函数调用、跳转等。
  • 模块外部的数据访问,比如其他模块中定义的全局变量。

对于前两种引用方式由于是在模块内部,相对地址偏移固定,因此可以通过 [rip + xxx] (注意这里的 rip 是当前指令的下一条指令的地址,下一条指令指的是地址相邻的下一条指令)的方式进行引用,从而做到地址无关。因此关键在于后两种怎么解决。

模块间的访问比模块内部稍微麻烦一点,因为模块间的数据访问目标地址要等到装载时才决定,我们前面提到要使得代码地址无关,基本的思想就是把跟地址相关的部分放到数据段里面,很明显,这些其他模块的全局变量的地址是跟模块装载地址有关的。ELF 的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(Global Offset Table,GOT),当代码需要引用该全局变量时,可以通过 GOT 中相对应的项间接引用。

前面模块内部的解决方法实际上并不严谨,比如一些全局变量以及函数声明没有初始化会被认为是若弱符号,这些弱符号编译器并不知道是否只在本模块定义,因此不能仅使用 [rip + xxx] 的方式访问。

针对这种情况的解决办法是所有的使用这个变量的指令都指向位于可执行文件中的那个副本。ELF 共享库在编译时,默认都把定义在模块内部的全局变量当作定义在其他模块的全局变量,也就是说当作前面的类型四,通过 GOT 来实现变量的访问。当共享模块被装载时,如果某个全局变量在可执行文件中拥有副本,那么动态链接器就会把 GOT 中的相应地址指向该副本,这样该变量在运行时实际上最终就只有一个实例。如果变量在共享模块中被初始化,那么动态链接器还需要将该初始化值复制到程序主模块中的变量副本;如果该全局变量在程序主模块中没有副本,那么 GOT 中的相应地址就指向共享模块内部的该变量副本。这就是为什么 libc 的 GOT 表中会有自身函数。

地址无关代码虽然解决了模块复用的问题,但是本质还是装载时重定位因此没有解决性能问题,实际上 ELF 采用了延迟绑定的方法来解决这一问题。

地址无关代码技术除了可以用在共享对象上面,它也可以用于可执行文件,一个以地址无关方式编译的可执行文件被称作地址无关可执行文件(PIE, Position-Independent Executable)。与 GCC 的 -fPIC-fpic 参数类似,产生 PIE 的参数为 -fPIE-fpie

延迟绑定

在动态链接下,程序模块之间包含了大量的函数引用(全局变量往往比较少,因为大量的全局变量会导致模块之间耦合度变大),所以在程序开始执行前,动态链接会耗费不少时间用于解决模块之间的函数引用的符号查找以及重定位,这不过可以想象,在一个程序运行过程中,可能很多函数在程序执行完时都不会被用到,比如一些错误处理函数或者是一些用户很少用到的功能模块等,如果一开始就把所有函数都链接好实际上是一种浪费。所以 ELF 采用了一种叫做延迟绑定(Lazy Binding)的做法,基本的思想就是当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。所以程序开始执行时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定。这样的做法可以大大加快程序的启动速度,特别有利于一些有大量函数引用和大量模块的程序。

注意,延迟绑定一般只出先在未开启 FULL RELRO 的时候,如果开启 FULL RELRO 则 got 表不可写,程序在装载时完成 got 表的重定位。当然特殊情况也有在开启 FULL RELRO 的时候进行重定位,比如 ret2dlresolve 。

我们以调用 puts 函数为例讲解一下延迟绑定的过程。

首先第一次调用 puts 时由于 puts@got 没有进行重定位,因此会调用 _dl_runtime_resolve 函数进行重定位,_dl_runtime_resolve 函数将查找到的 puts 函数地址填写到 puts@got 后会调用 puts 函数。
在这里插入图片描述
再次调用 puts 函数时由于 puts@got 已经完成重定位,因此会直接调用 puts 函数。
在这里插入图片描述
其中在第一次调用 puts 函数时调用的 _dl_runtime_resolve 函数的具体实现为:

  • 用第一个参数 link_map 访问 .dynamic ,取出 .dynstr.dynsym.rel.plt 的指针。
  • .rel.plt + 第二个参数 求出当前函数的重定位表项 Elf32_Rel 的指针,记作 rel
  • rel->r_info >> 8 作为 .dynsym 的下标,求出当前函数的符号表项 Elf32_Sym 的指针,记作 sym
  • .dynstr + sym->st_name 得出符号名字符串指针。
  • 在动态链接库查找这个函数的地址,并且把地址赋值给 *rel->r_offset ,即 GOT 表。
  • 调用这个函数。

动态链接的步骤和实现

动态链接器自举

由于动态链接器本身的作用是重定位,因此自身的重定位也需要自身来完成,完成自身重定位的过程成为自举(Bootstrap)。

动态链接器入口地址即是自举代码的入口,当操作系统将进程控制权交给动态链接器时,动态链接器的自举代码即开始执行。自举代码首先会找到它自己的 GOT 。而 GOT 的第一个入口保存的即是 .dynamic 段的偏移地址,由此找到了动态连接器本身的“.dynamic”段。通过 .dynamic 中的信息,自举代码便可以获得动态链接器本身的重定位表和符号表等,从而得到动态链接器本身的重定位入口,先将它们全部重定位。

从这一步开始,动态链接器代码中才可以开始使用自己的全局变量和静态变量。

装载共享对象

完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,我们可以称它为全局符号表(Global Symbol Table)。然后链接器开始寻找可执行文件所依赖的共享对象,我们前面提到过 .dynamic 段中,有一种类型的入口是 DT_NEEDED ,它所指出的是该可执行文件(或共享对象)所依赖的共享对象。由此,链接器可以列出可执行文件所需要的所有共享对象,并将这些共享对象的名字放入到一个装载集合中。然后链接器开始从集合里取一个所需要的共享对象的名字,找到相应的文件后打开该文件,读取相应的 ELF 文件头和 .dynamic 段,然后将它相应的代码段和数据段映射到进程空间中。

如果这个 ELF 共享对象还依赖于其他共享对象,那么将所依赖的共享对象的名字放到装载集合中。如此循环直到所有依赖的共享对象都被装载进来为止,当然链接器可以有不同的装载顺序,如果我们把依赖关系看作一个图的话,那么这个装载过程就是一个图的遍历过程,链接器可能会使用深度优先或者广度优先或者其他的顺序来遍历整个图,这取决于链接器,比较常见的算法一般都是广度优先的。

当一个新的共享对象被装载进来的时候,它的符号表会被合并到全局符号表中,所以当所有的共享对象都被装载进来的时候,全局符号表里面将包含进程中所有的动态链接所需要的符号。

重定位和初始化

当上面的步骤完成之后,链接器开始重新遍历可执行文件和每个共享对象的重定位表,将它们的 GOT/PLT 中的每个需要重定位的位置进行修正。因为此时动态链接器已经拥有了进程的全局符号表,所以这个修正过程也显得比较容易,跟我们前面提到的地址重定位的原理基本相同。

动态链接重定位除了前面静态链接重定位类型外还有如下重定位类型:

  • R_386_RELATIVE:针对下面这种代码的重定位,由于加载地址不确定,需要加载后的才能确定。
    static int a;
    static int* p = &a;
    
  • R_386_GLOB_DAT:位于 got.plt 的重定位入口,只需要填入正确变量地址即可。
  • R_386_JUMP_SLOT:位于 got.plt 的重定位入口,只需要填入正确的函数地址即可。

重定位完成之后,如果某个共享对象有 .init 段,那么动态链接器会执行 .init 段中的代码,用以实现共享对象特有的初始化过程,比如最常见的,共享对象中的 C++ 的全局/静态对象的构造就需要通过 .init 来初始化。相应地,共享对象中还可能有 .finit 段,当进程退出时会执行 .finit 段中的代码,可以用来实现类似 C++ 全局对象析构之类的操作。

如果进程的可执行文件也有 .init 段,那么动态链接器不会执行它,因为可执行文件中的 .init 段和 .finit 段由程序初始化部分代码负责执行。当完成了重定位和初始化之后,所有的准备工作就宣告完成了,所需要的共享对象也都已经装载并且链接完成了,这时候动态链接器就如释重负,将进程的控制权转交给程序的入口并且开始执行。

装载

Linux 内核装载 ELF 过程

首先在用户层面,bash 进程会调用 fork() 系统调用创建一个新的进程,然后新的进程调用 execve() 系统调用执行指定的 ELF 文件,原先的 bash 进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。

execve() 系统调用被定义在 unistd.h ,它的原型如下:

/* Replace the current process, executing PATH with arguments ARGV andenvironment ENVP.  ARGV and ENVP are terminated by NULL pointers.  */
int execve (const char *__path, char *const __argv[], char *const __envp[]);

它的三个参数分别是被执行的程序文件名、执行参数和环境变量。

Glibc 对 execvp() 系统调用进行了包装,提供了 execl()execlp()execle()execv()execvp() 等5个不同形式的 exec 系列 API ,它们只是在调用的参数形式上有所区别,但最终都会调用到 execve() 这个系统调用。下面是一个简单的使用 fork()execlp() 实现的 minibash :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdbool.h>#define MAX_COMMAND_LENGTH 1024int main() {char command[MAX_COMMAND_LENGTH];while (true) {printf("minibash$ ");fgets(command, sizeof(command), stdin);// 删除换行符command[strcspn(command, "\n")] = '\0';// 检查是否输入了退出命令if (strcmp(command, "exit") == 0) {break;}if (strlen(command) == 0) {continue;}pid_t pid = fork();if (pid == 0) {// 子进程执行命令if (execlp(command, command, NULL) < 0) {perror("minibash");exit(1);}} else if (pid > 0) {// 父进程等待子进程结束int status;waitpid(pid, &status, 0);} else {// fork失败printf("fork error\n");exit(1);}}return 0;
}

在进入 execve() 系统调用之后,Linux 内核就开始进行真正的装载工作。在内核中, execve() 系统调用相应的入口是 sys_execve(), 它被定义在 arch\i386\kernel\Process.csys_execve() 进行一些参数的检查复制之后,调用 do_execve()do_execve() 会首先查找被执行的文件,如果找到文件,则 do_execve() 读取文件的前128个字节判断文件的格式,每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,常常被称做魔数(Magic Number),通过对魔数的判断可以确定文件的格式和类型。比如 ELF 的可执行文件格式的头 4 个字节为 \x7felf ;而 Java 的可执行文件格式的头4个字节为 cafe ;如果被执行的是 Shell 脚本或 perl 、python 等这种解释型语言的脚本,那么它的第一行往往是 #!/bin/sh#!/usr/bin/perl#!/usr/bin/python ,这时候前两个字节 #! 就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序的路径。

do_execve() 读取了这 128 个字节的文件头部之后,然后调用 search_binary_handle() 去搜索和匹配合适的可执行文件装载处理过程。Linux中所有被支持的可执行文件格式都有相应的装载处理过程, search_binary_handle() 会通过判断文件头部的魔数确定文件的格式,并且调用相应的装载处理过程。比如 ELF 可执行文件的装载处理过程叫做 load_elf_binary(); a.out 可执行文件的装载处理过程叫做 load_aout_binary();而装载可执行脚本程序的处理过程叫做 load_script() 。 这里我们只关心 ELF 可执行文件的装载, load_elf_binary() 被定义在 fs/Binfmt_elf.c ,这个函数的代码比较长,它的主要步骤是:

  • 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量。
  • 寻找动态链接的 .interp 段,设置动态链接器路径。
  • 根据 ELF 可执行文件的程序头表的描述,对 ELF 文件进行映射,比如代码、数据、只读数据。
  • 初始化 ELF 进程环境,比如进程启动时 EDX 寄存器的地址应该是 DT_FINI 的地址(参照动态链接)。
  • 将系统调用的返回地址修改成 ELF 可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的 ELF 可执行文件,这个程序入口就是 ELF 文件的文件头中 e_entry 所指的地址;对于动态链接的 ELF 可执行文件,程序入口点是动态链接器

load_elf_binary() 执行完毕,返回至 do_execve() 再返回至 sys_execve() 时,上面的第 5 步中已经把系统调用的返回地址改成了被装载的 ELF 程序的入口地址了。所以当 sys_execve() 系统调用从内核态返回到用户态时,EIP 寄存器直接跳转到了 ELF 程序的入口地址,于是新的程序开始执行,ELF 可执行文件装载完成。

进程虚拟地址空间

在现代操作系统中,每个进程都有自己的虚拟地址空间,这是一个抽象的地址空间,由连续的虚拟地址组成。每个进程在其虚拟地址空间中运行,不会直接访问物理内存地址。

操作系统将每个进程的虚拟地址空间划分为多个区域,例如代码段、数据段、堆和栈等。每个区域具有特定的用途和权限。

  • 代码段:包含可执行程序的机器指令。
  • 数据段:包含静态和全局变量的初始值。
  • BSS 段:包含需要初始化为零的静态和全局变量。
  • 动态链接段:包含动态链接所需的信息。

加载器将这些段从 ELF 文件中复制到相应的虚拟内存地址,并建立虚拟地址与物理内存地址的映射关系。

运行

进程栈的初始化

我们知道进程刚开始启动的时候,须知道一些进程运行的环境,最基本的就是系统环境变量和进程的运行参数。很常见的一种做法是操作系统在进程启动前将这些信息提前保存到进程的虚拟空间的栈中。

假设我们运行如下命令,即运行 ls 程序,传入的参数为 /home

ls /home

在程序初始状态的栈如下图所示。
在这里插入图片描述
栈顶寄存器 rsp 指向的位置是初始化以后堆栈的顶部,最前面的 8 个字节表示命令行参数的数量,我们的例子里面是两个,即 /usr/bin/ls/home ,紧接的就是分布指向这两个参数字符串的指针;后面跟了一个0;接着是一个以 0 结尾的指向环境变量字符串的指针数组。
在这里插入图片描述
进程在启动以后,程序的库部分会把堆栈里的初始化信息中的参数信息传递给 main() 函数,也就是我们熟知的 main() 函数的两个 argcargv 两个参数,这两个参数分别对应这里的命令行参数数量和命令行参数字符串指针数组。

main 函数之外的代码

共享库

共享库版本

符号版本

共享库版本命名

Linux有一套规则来命名系统中的每一个共享库,它规定共享库的文件名规则必须如下:

libname.so.x.y.z

最前面使用前缀 lib 、中间是库的名字和后缀 .so ,最后面跟着的是三个数字组成的版本号。x 表示主版本号(Major Version Number),y 表示次版本号(Minor Version Number),z 表示发布版本号(Release Version Number)。三个版本号的含义不一样。

  • 主版本号表示库的重大升级,不同主版本号的库之间是不兼容的,依赖于旧的主版本号的程序需要改动相应的部分,并且重新编译,才可以在新版的共享库中运行;或者,系统必须保留旧版的共享库,使得那些依赖于旧版共享库的程序能够正常运行。
  • 次版本号表示库的增量升级,即增加一些新的接口符号,且保持原来的符号不变。在主版本号相同的情况下,高的次版本号的库向后兼容低的次版本号的库。
  • 发布版本号表示库的一些错误的修正、性能的改进等,并不添加任何新的接口,也不对接口进行更改。相同主版本号、次版本号的共享库,不同的发布版本号之间完全兼容,依赖于某个发布版本号的程序可以在任何一个其他发布版本号中正常运行,而无须做任何修改。

SO-NAME

系统普遍采用一种叫做 SO-NAME 的命名机制来记录共享库的依赖关系。每个共享库都有一个对应的 SO-NAME ,这个 SO-NAME 即共享库的文件名去掉次版本号和发布版本号,保留主版本号。比如一个共享库叫做 libfoo.so.2.6.1 ,那么它的 SO-NAME 即 libfoo.so.2 。很明显,SO-NAME 规定了共享库的接口,SO-NAME 的两个相同共享库,次版本号大的兼容次版本号小的。在 Linux 系统中,系统会为每个共享库在它所在的目录创建一个跟 SO-NAME 相同的并且指向它的软链接(Symbol Link)。比如系统中有存在一个共享库 /lib/libfoo.so.2.6.1 ,那么 Linux 中的共享库管理程序就会为它产生一个软链接 /lib/libfoo.so.2 指向它。比如 Linux 系统的 Glibc 共享库(注意稍高版本的 libc 的 libc.so.6 本身就是动态库,不是符号链接):

$ ls -l /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Apr  7  2022 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.31.so
$ ls -l /lib/x86_64-linux-gnu/libc-2.31.so
-rwxr-xr-x 1 root root 2029592 Apr  7  2022 /lib/x86_64-linux-gnu/libc-2.31.so

由于历史原因,动态链接器和 C 语言库的共享对象文件名规则不按 Linux 标准的共享库命名方法,但是 C 语言的 SO-NAME 还是按照正常的规则。

另外动态连接器的 SO-NAME 命名不按照普通的规则。

$ ls -al /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 32 Apr  7  2022 /lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.31.so

建立以 SO-NAME 为名字的软链接目的是,使得所有依赖某个共享库的模块,在编译、链接和运行时,都使用共享库的 SO-NAME ,而不使用详细的版本号。

共享库系统路径

共享库查找过程

常见保护

checksec 可以查看程序开启了哪些保护。

➜  ~ checksec /bin/ls                                   
[*] '/bin/ls'Arch:     amd64-64-littleRELRO:    Full RELROStack:    Canary foundNX:       NX enabledPIE:      PIE enabledFORTIFY:  Enabled

Canary

canary 是一种防止缓冲区溢出攻击的保护机制。它的基本思想是在程序的堆栈中插入一个随机生成的数值,作为哨兵,用于检测缓冲区溢出攻击。

.text:0000000000001189 endbr64
.text:000000000000118D push    rbp
.text:000000000000118E mov     rbp, rsp
.text:0000000000001191 sub     rsp, 30h
.text:0000000000001195 mov     rax, fs:28h
.text:000000000000119E mov     [rbp-8], rax
...
.text:00000000000011CE mov     rdx, [rbp-8]
.text:00000000000011D2 xor     rdx, fs:28h
.text:00000000000011DB jz      short locret_11E2
.text:00000000000011DB
.text:00000000000011DD call    ___stack_chk_fail
.text:00000000000011DD
.text:00000000000011E2 ; ---------------------------------------------------------------------------
.text:00000000000011E2
.text:00000000000011E2 locret_11E2:                            ; CODE XREF: f+52↑j
.text:00000000000011E2 leave
.text:00000000000011E3 retn

canary 的初始值值存储在 tls 中。
在这里插入图片描述
在编译 c 程序时使用 -fno-stack-protector 参数可以关闭 canary 保护(注意高版本的 gcc 的 canary 保护关不掉)。

NX

NX 即 No-eXecute(不可执行),NX 的基本原理是将数据所在内存页标识为不可执行,也就是同一内存可写与可执行不共存。

gcc 编译器默认开启了 NX 选项,如果需要关闭 NX 选项,可以给 gcc 编译器添加 -zexecstack 参数。

PIE

PIE 主要随机了代码段(.text),初始化数据段(.data)和未初始化数据段(.bss)的地址。另外 PIE 是否开启还会影响堆的基址。

  • 开启 PIE:
    在这里插入图片描述
  • 关闭 PIE:
    在这里插入图片描述

在编译 c 程序时使用 -no-pie 参数可以关闭 PIE 保护。

ASLR

ASLR 是系统级别的地址随机。通过修改 /proc/sys/kernel/randomize_va_space 的值可以控制 ASLR 的级别:

  • 0:关闭 ASLR
  • 1:栈基址,共享库,mmap 基址随机
  • 2:在 1 的基础上增加堆基址的随机

RELRO

  • 当 RELRO 保护为 NO RELRO 的时候,init.arrayfini.arraygot.plt 均可读可写。
  • 为 PARTIAL RELRO 的时候,init.arrayfini.array 可读不可写,got.plt 可读可写。
  • 为 FULL RELRO 时,init.arrayfini.arraygot.plt 均可读不可写。

调用约定

栈结构

image-20220131010440822
注意 canary 不一定与 ebp 相邻,因为有些函数会先将一些寄存器保存到栈中。canary 实际位置以调试为准。

函数调用过程

32位为例:
push args call func { push next_eip jmp func push ebp mov ebp,esp ⋮ leave { mov esp,ebp pop ebp ret (pop eip) \begin{align*} & \text{push args}\\ & \text{call func}\left\{\begin{matrix} \text{push next\_eip}\\ \text{jmp func} \end{matrix}\right.\\ & \text{push ebp}\\ & \text{mov ebp,esp}\\ & \vdots \\ & \text{leave}\left\{\begin{matrix} \text{mov esp,ebp}\\ \text{pop ebp} \end{matrix}\right.\\ &\text{ret}\ \text{(pop eip)} \end{align*} push argscall func{push next_eipjmp funcpush ebpmov ebp,espleave{mov esp,ebppop ebpret (pop eip)

函数参数传递

32位程序

  • 普通函数传参:参数基本都压在栈上(有寄存器传参的情况,可查阅相关资料)。
  • syscall传参:eax对应系统调用号,ebx、ecx、edx、esi、edi、ebp 分别对应前六个参数多余的参数压在栈上。

64位程序:

  • 普通函数传参:先使用 rdi、rsi、rdx、rcx、r8、r9 寄存器作为函数参数的前六个参数,多余的参数会依次压在栈上。
  • syscall传参:rax 对应系统调用号,传参规则与普通函数传参一致。

系统调用号

32 位

#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H 1#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40
#define __NR_dup 41
#define __NR_pipe 42
#define __NR_times 43
#define __NR_prof 44
#define __NR_brk 45
#define __NR_setgid 46
#define __NR_getgid 47
#define __NR_signal 48
#define __NR_geteuid 49
#define __NR_getegid 50
#define __NR_acct 51
#define __NR_umount2 52
#define __NR_lock 53
#define __NR_ioctl 54
#define __NR_fcntl 55
#define __NR_mpx 56
#define __NR_setpgid 57
#define __NR_ulimit 58
#define __NR_oldolduname 59
#define __NR_umask 60
#define __NR_chroot 61
#define __NR_ustat 62
#define __NR_dup2 63
#define __NR_getppid 64
#define __NR_getpgrp 65
#define __NR_setsid 66
#define __NR_sigaction 67
#define __NR_sgetmask 68
#define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71
#define __NR_sigsuspend 72
#define __NR_sigpending 73
#define __NR_sethostname 74
#define __NR_setrlimit 75
#define __NR_getrlimit 76
#define __NR_getrusage 77
#define __NR_gettimeofday 78
#define __NR_settimeofday 79
#define __NR_getgroups 80
#define __NR_setgroups 81
#define __NR_select 82
#define __NR_symlink 83
#define __NR_oldlstat 84
#define __NR_readlink 85
#define __NR_uselib 86
#define __NR_swapon 87
#define __NR_reboot 88
#define __NR_readdir 89
#define __NR_mmap 90
#define __NR_munmap 91
#define __NR_truncate 92
#define __NR_ftruncate 93
#define __NR_fchmod 94
#define __NR_fchown 95
#define __NR_getpriority 96
#define __NR_setpriority 97
#define __NR_profil 98
#define __NR_statfs 99
#define __NR_fstatfs 100
#define __NR_ioperm 101
#define __NR_socketcall 102
#define __NR_syslog 103
#define __NR_setitimer 104
#define __NR_getitimer 105
#define __NR_stat 106
#define __NR_lstat 107
#define __NR_fstat 108
#define __NR_olduname 109
#define __NR_iopl 110
#define __NR_vhangup 111
#define __NR_idle 112
#define __NR_vm86old 113
#define __NR_wait4 114
#define __NR_swapoff 115
#define __NR_sysinfo 116
#define __NR_ipc 117
#define __NR_fsync 118
#define __NR_sigreturn 119
#define __NR_clone 120
#define __NR_setdomainname 121
#define __NR_uname 122
#define __NR_modify_ldt 123
#define __NR_adjtimex 124
#define __NR_mprotect 125
#define __NR_sigprocmask 126
#define __NR_create_module 127
#define __NR_init_module 128
#define __NR_delete_module 129
#define __NR_get_kernel_syms 130
#define __NR_quotactl 131
#define __NR_getpgid 132
#define __NR_fchdir 133
#define __NR_bdflush 134
#define __NR_sysfs 135
#define __NR_personality 136
#define __NR_afs_syscall 137
#define __NR_setfsuid 138
#define __NR_setfsgid 139
#define __NR__llseek 140
#define __NR_getdents 141
#define __NR__newselect 142
#define __NR_flock 143
#define __NR_msync 144
#define __NR_readv 145
#define __NR_writev 146
#define __NR_getsid 147
#define __NR_fdatasync 148
#define __NR__sysctl 149
#define __NR_mlock 150
#define __NR_munlock 151
#define __NR_mlockall 152
#define __NR_munlockall 153
#define __NR_sched_setparam 154
#define __NR_sched_getparam 155
#define __NR_sched_setscheduler 156
#define __NR_sched_getscheduler 157
#define __NR_sched_yield 158
#define __NR_sched_get_priority_max 159
#define __NR_sched_get_priority_min 160
#define __NR_sched_rr_get_interval 161
#define __NR_nanosleep 162
#define __NR_mremap 163
#define __NR_setresuid 164
#define __NR_getresuid 165
#define __NR_vm86 166
#define __NR_query_module 167
#define __NR_poll 168
#define __NR_nfsservctl 169
#define __NR_setresgid 170
#define __NR_getresgid 171
#define __NR_prctl 172
#define __NR_rt_sigreturn 173
#define __NR_rt_sigaction 174
#define __NR_rt_sigprocmask 175
#define __NR_rt_sigpending 176
#define __NR_rt_sigtimedwait 177
#define __NR_rt_sigqueueinfo 178
#define __NR_rt_sigsuspend 179
#define __NR_pread64 180
#define __NR_pwrite64 181
#define __NR_chown 182
#define __NR_getcwd 183
#define __NR_capget 184
#define __NR_capset 185
#define __NR_sigaltstack 186
#define __NR_sendfile 187
#define __NR_getpmsg 188
#define __NR_putpmsg 189
#define __NR_vfork 190
#define __NR_ugetrlimit 191
#define __NR_mmap2 192
#define __NR_truncate64 193
#define __NR_ftruncate64 194
#define __NR_stat64 195
#define __NR_lstat64 196
#define __NR_fstat64 197
#define __NR_lchown32 198
#define __NR_getuid32 199
#define __NR_getgid32 200
#define __NR_geteuid32 201
#define __NR_getegid32 202
#define __NR_setreuid32 203
#define __NR_setregid32 204
#define __NR_getgroups32 205
#define __NR_setgroups32 206
#define __NR_fchown32 207
#define __NR_setresuid32 208
#define __NR_getresuid32 209
#define __NR_setresgid32 210
#define __NR_getresgid32 211
#define __NR_chown32 212
#define __NR_setuid32 213
#define __NR_setgid32 214
#define __NR_setfsuid32 215
#define __NR_setfsgid32 216
#define __NR_pivot_root 217
#define __NR_mincore 218
#define __NR_madvise 219
#define __NR_getdents64 220
#define __NR_fcntl64 221
#define __NR_gettid 224
#define __NR_readahead 225
#define __NR_setxattr 226
#define __NR_lsetxattr 227
#define __NR_fsetxattr 228
#define __NR_getxattr 229
#define __NR_lgetxattr 230
#define __NR_fgetxattr 231
#define __NR_listxattr 232
#define __NR_llistxattr 233
#define __NR_flistxattr 234
#define __NR_removexattr 235
#define __NR_lremovexattr 236
#define __NR_fremovexattr 237
#define __NR_tkill 238
#define __NR_sendfile64 239
#define __NR_futex 240
#define __NR_sched_setaffinity 241
#define __NR_sched_getaffinity 242
#define __NR_set_thread_area 243
#define __NR_get_thread_area 244
#define __NR_io_setup 245
#define __NR_io_destroy 246
#define __NR_io_getevents 247
#define __NR_io_submit 248
#define __NR_io_cancel 249
#define __NR_fadvise64 250
#define __NR_exit_group 252
#define __NR_lookup_dcookie 253
#define __NR_epoll_create 254
#define __NR_epoll_ctl 255
#define __NR_epoll_wait 256
#define __NR_remap_file_pages 257
#define __NR_set_tid_address 258
#define __NR_timer_create 259
#define __NR_timer_settime 260
#define __NR_timer_gettime 261
#define __NR_timer_getoverrun 262
#define __NR_timer_delete 263
#define __NR_clock_settime 264
#define __NR_clock_gettime 265
#define __NR_clock_getres 266
#define __NR_clock_nanosleep 267
#define __NR_statfs64 268
#define __NR_fstatfs64 269
#define __NR_tgkill 270
#define __NR_utimes 271
#define __NR_fadvise64_64 272
#define __NR_vserver 273
#define __NR_mbind 274
#define __NR_get_mempolicy 275
#define __NR_set_mempolicy 276
#define __NR_mq_open 277
#define __NR_mq_unlink 278
#define __NR_mq_timedsend 279
#define __NR_mq_timedreceive 280
#define __NR_mq_notify 281
#define __NR_mq_getsetattr 282
#define __NR_kexec_load 283
#define __NR_waitid 284
#define __NR_add_key 286
#define __NR_request_key 287
#define __NR_keyctl 288
#define __NR_ioprio_set 289
#define __NR_ioprio_get 290
#define __NR_inotify_init 291
#define __NR_inotify_add_watch 292
#define __NR_inotify_rm_watch 293
#define __NR_migrate_pages 294
#define __NR_openat 295
#define __NR_mkdirat 296
#define __NR_mknodat 297
#define __NR_fchownat 298
#define __NR_futimesat 299
#define __NR_fstatat64 300
#define __NR_unlinkat 301
#define __NR_renameat 302
#define __NR_linkat 303
#define __NR_symlinkat 304
#define __NR_readlinkat 305
#define __NR_fchmodat 306
#define __NR_faccessat 307
#define __NR_pselect6 308
#define __NR_ppoll 309
#define __NR_unshare 310
#define __NR_set_robust_list 311
#define __NR_get_robust_list 312
#define __NR_splice 313
#define __NR_sync_file_range 314
#define __NR_tee 315
#define __NR_vmsplice 316
#define __NR_move_pages 317
#define __NR_getcpu 318
#define __NR_epoll_pwait 319
#define __NR_utimensat 320
#define __NR_signalfd 321
#define __NR_timerfd_create 322
#define __NR_eventfd 323
#define __NR_fallocate 324
#define __NR_timerfd_settime 325
#define __NR_timerfd_gettime 326
#define __NR_signalfd4 327
#define __NR_eventfd2 328
#define __NR_epoll_create1 329
#define __NR_dup3 330
#define __NR_pipe2 331
#define __NR_inotify_init1 332
#define __NR_preadv 333
#define __NR_pwritev 334
#define __NR_rt_tgsigqueueinfo 335
#define __NR_perf_event_open 336
#define __NR_recvmmsg 337
#define __NR_fanotify_init 338
#define __NR_fanotify_mark 339
#define __NR_prlimit64 340
#define __NR_name_to_handle_at 341
#define __NR_open_by_handle_at 342
#define __NR_clock_adjtime 343
#define __NR_syncfs 344
#define __NR_sendmmsg 345
#define __NR_setns 346
#define __NR_process_vm_readv 347
#define __NR_process_vm_writev 348
#define __NR_kcmp 349
#define __NR_finit_module 350
#define __NR_sched_setattr 351
#define __NR_sched_getattr 352
#define __NR_renameat2 353
#define __NR_seccomp 354
#define __NR_getrandom 355
#define __NR_memfd_create 356
#define __NR_bpf 357
#define __NR_execveat 358
#define __NR_socket 359
#define __NR_socketpair 360
#define __NR_bind 361
#define __NR_connect 362
#define __NR_listen 363
#define __NR_accept4 364
#define __NR_getsockopt 365
#define __NR_setsockopt 366
#define __NR_getsockname 367
#define __NR_getpeername 368
#define __NR_sendto 369
#define __NR_sendmsg 370
#define __NR_recvfrom 371
#define __NR_recvmsg 372
#define __NR_shutdown 373
#define __NR_userfaultfd 374
#define __NR_membarrier 375
#define __NR_mlock2 376
#define __NR_copy_file_range 377
#define __NR_preadv2 378
#define __NR_pwritev2 379#endif /* _ASM_X86_UNISTD_32_H */

64 位

#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
#define __NR_lseek 8
#define __NR_mmap 9
#define __NR_mprotect 10
#define __NR_munmap 11
#define __NR_brk 12
#define __NR_rt_sigaction 13
#define __NR_rt_sigprocmask 14
#define __NR_rt_sigreturn 15
#define __NR_ioctl 16
#define __NR_pread64 17
#define __NR_pwrite64 18
#define __NR_readv 19
#define __NR_writev 20
#define __NR_access 21
#define __NR_pipe 22
#define __NR_select 23
#define __NR_sched_yield 24
#define __NR_mremap 25
#define __NR_msync 26
#define __NR_mincore 27
#define __NR_madvise 28
#define __NR_shmget 29
#define __NR_shmat 30
#define __NR_shmctl 31
#define __NR_dup 32
#define __NR_dup2 33
#define __NR_pause 34
#define __NR_nanosleep 35
#define __NR_getitimer 36
#define __NR_alarm 37
#define __NR_setitimer 38
#define __NR_getpid 39
#define __NR_sendfile 40
#define __NR_socket 41
#define __NR_connect 42
#define __NR_accept 43
#define __NR_sendto 44
#define __NR_recvfrom 45
#define __NR_sendmsg 46
#define __NR_recvmsg 47
#define __NR_shutdown 48
#define __NR_bind 49
#define __NR_listen 50
#define __NR_getsockname 51
#define __NR_getpeername 52
#define __NR_socketpair 53
#define __NR_setsockopt 54
#define __NR_getsockopt 55
#define __NR_clone 56
#define __NR_fork 57
#define __NR_vfork 58
#define __NR_execve 59
#define __NR_exit 60
#define __NR_wait4 61
#define __NR_kill 62
#define __NR_uname 63
#define __NR_semget 64
#define __NR_semop 65
#define __NR_semctl 66
#define __NR_shmdt 67
#define __NR_msgget 68
#define __NR_msgsnd 69
#define __NR_msgrcv 70
#define __NR_msgctl 71
#define __NR_fcntl 72
#define __NR_flock 73
#define __NR_fsync 74
#define __NR_fdatasync 75
#define __NR_truncate 76
#define __NR_ftruncate 77
#define __NR_getdents 78
#define __NR_getcwd 79
#define __NR_chdir 80
#define __NR_fchdir 81
#define __NR_rename 82
#define __NR_mkdir 83
#define __NR_rmdir 84
#define __NR_creat 85
#define __NR_link 86
#define __NR_unlink 87
#define __NR_symlink 88
#define __NR_readlink 89
#define __NR_chmod 90
#define __NR_fchmod 91
#define __NR_chown 92
#define __NR_fchown 93
#define __NR_lchown 94
#define __NR_umask 95
#define __NR_gettimeofday 96
#define __NR_getrlimit 97
#define __NR_getrusage 98
#define __NR_sysinfo 99
#define __NR_times 100
#define __NR_ptrace 101
#define __NR_getuid 102
#define __NR_syslog 103
#define __NR_getgid 104
#define __NR_setuid 105
#define __NR_setgid 106
#define __NR_geteuid 107
#define __NR_getegid 108
#define __NR_setpgid 109
#define __NR_getppid 110
#define __NR_getpgrp 111
#define __NR_setsid 112
#define __NR_setreuid 113
#define __NR_setregid 114
#define __NR_getgroups 115
#define __NR_setgroups 116
#define __NR_setresuid 117
#define __NR_getresuid 118
#define __NR_setresgid 119
#define __NR_getresgid 120
#define __NR_getpgid 121
#define __NR_setfsuid 122
#define __NR_setfsgid 123
#define __NR_getsid 124
#define __NR_capget 125
#define __NR_capset 126
#define __NR_rt_sigpending 127
#define __NR_rt_sigtimedwait 128
#define __NR_rt_sigqueueinfo 129
#define __NR_rt_sigsuspend 130
#define __NR_sigaltstack 131
#define __NR_utime 132
#define __NR_mknod 133
#define __NR_uselib 134
#define __NR_personality 135
#define __NR_ustat 136
#define __NR_statfs 137
#define __NR_fstatfs 138
#define __NR_sysfs 139
#define __NR_getpriority 140
#define __NR_setpriority 141
#define __NR_sched_setparam 142
#define __NR_sched_getparam 143
#define __NR_sched_setscheduler 144
#define __NR_sched_getscheduler 145
#define __NR_sched_get_priority_max 146
#define __NR_sched_get_priority_min 147
#define __NR_sched_rr_get_interval 148
#define __NR_mlock 149
#define __NR_munlock 150
#define __NR_mlockall 151
#define __NR_munlockall 152
#define __NR_vhangup 153
#define __NR_modify_ldt 154
#define __NR_pivot_root 155
#define __NR__sysctl 156
#define __NR_prctl 157
#define __NR_arch_prctl 158
#define __NR_adjtimex 159
#define __NR_setrlimit 160
#define __NR_chroot 161
#define __NR_sync 162
#define __NR_acct 163
#define __NR_settimeofday 164
#define __NR_mount 165
#define __NR_umount2 166
#define __NR_swapon 167
#define __NR_swapoff 168
#define __NR_reboot 169
#define __NR_sethostname 170
#define __NR_setdomainname 171
#define __NR_iopl 172
#define __NR_ioperm 173
#define __NR_create_module 174
#define __NR_init_module 175
#define __NR_delete_module 176
#define __NR_get_kernel_syms 177
#define __NR_query_module 178
#define __NR_quotactl 179
#define __NR_nfsservctl 180
#define __NR_getpmsg 181
#define __NR_putpmsg 182
#define __NR_afs_syscall 183
#define __NR_tuxcall 184
#define __NR_security 185
#define __NR_gettid 186
#define __NR_readahead 187
#define __NR_setxattr 188
#define __NR_lsetxattr 189
#define __NR_fsetxattr 190
#define __NR_getxattr 191
#define __NR_lgetxattr 192
#define __NR_fgetxattr 193
#define __NR_listxattr 194
#define __NR_llistxattr 195
#define __NR_flistxattr 196
#define __NR_removexattr 197
#define __NR_lremovexattr 198
#define __NR_fremovexattr 199
#define __NR_tkill 200
#define __NR_time 201
#define __NR_futex 202
#define __NR_sched_setaffinity 203
#define __NR_sched_getaffinity 204
#define __NR_set_thread_area 205
#define __NR_io_setup 206
#define __NR_io_destroy 207
#define __NR_io_getevents 208
#define __NR_io_submit 209
#define __NR_io_cancel 210
#define __NR_get_thread_area 211
#define __NR_lookup_dcookie 212
#define __NR_epoll_create 213
#define __NR_epoll_ctl_old 214
#define __NR_epoll_wait_old 215
#define __NR_remap_file_pages 216
#define __NR_getdents64 217
#define __NR_set_tid_address 218
#define __NR_restart_syscall 219
#define __NR_semtimedop 220
#define __NR_fadvise64 221
#define __NR_timer_create 222
#define __NR_timer_settime 223
#define __NR_timer_gettime 224
#define __NR_timer_getoverrun 225
#define __NR_timer_delete 226
#define __NR_clock_settime 227
#define __NR_clock_gettime 228
#define __NR_clock_getres 229
#define __NR_clock_nanosleep 230
#define __NR_exit_group 231
#define __NR_epoll_wait 232
#define __NR_epoll_ctl 233
#define __NR_tgkill 234
#define __NR_utimes 235
#define __NR_vserver 236
#define __NR_mbind 237
#define __NR_set_mempolicy 238
#define __NR_get_mempolicy 239
#define __NR_mq_open 240
#define __NR_mq_unlink 241
#define __NR_mq_timedsend 242
#define __NR_mq_timedreceive 243
#define __NR_mq_notify 244
#define __NR_mq_getsetattr 245
#define __NR_kexec_load 246
#define __NR_waitid 247
#define __NR_add_key 248
#define __NR_request_key 249
#define __NR_keyctl 250
#define __NR_ioprio_set 251
#define __NR_ioprio_get 252
#define __NR_inotify_init 253
#define __NR_inotify_add_watch 254
#define __NR_inotify_rm_watch 255
#define __NR_migrate_pages 256
#define __NR_openat 257
#define __NR_mkdirat 258
#define __NR_mknodat 259
#define __NR_fchownat 260
#define __NR_futimesat 261
#define __NR_newfstatat 262
#define __NR_unlinkat 263
#define __NR_renameat 264
#define __NR_linkat 265
#define __NR_symlinkat 266
#define __NR_readlinkat 267
#define __NR_fchmodat 268
#define __NR_faccessat 269
#define __NR_pselect6 270
#define __NR_ppoll 271
#define __NR_unshare 272
#define __NR_set_robust_list 273
#define __NR_get_robust_list 274
#define __NR_splice 275
#define __NR_tee 276
#define __NR_sync_file_range 277
#define __NR_vmsplice 278
#define __NR_move_pages 279
#define __NR_utimensat 280
#define __NR_epoll_pwait 281
#define __NR_signalfd 282
#define __NR_timerfd_create 283
#define __NR_eventfd 284
#define __NR_fallocate 285
#define __NR_timerfd_settime 286
#define __NR_timerfd_gettime 287
#define __NR_accept4 288
#define __NR_signalfd4 289
#define __NR_eventfd2 290
#define __NR_epoll_create1 291
#define __NR_dup3 292
#define __NR_pipe2 293
#define __NR_inotify_init1 294
#define __NR_preadv 295
#define __NR_pwritev 296
#define __NR_rt_tgsigqueueinfo 297
#define __NR_perf_event_open 298
#define __NR_recvmmsg 299
#define __NR_fanotify_init 300
#define __NR_fanotify_mark 301
#define __NR_prlimit64 302
#define __NR_name_to_handle_at 303
#define __NR_open_by_handle_at 304
#define __NR_clock_adjtime 305
#define __NR_syncfs 306
#define __NR_sendmmsg 307
#define __NR_setns 308
#define __NR_getcpu 309
#define __NR_process_vm_readv 310
#define __NR_process_vm_writev 311
#define __NR_kcmp 312
#define __NR_finit_module 313
#define __NR_sched_setattr 314
#define __NR_sched_getattr 315
#define __NR_renameat2 316
#define __NR_seccomp 317
#define __NR_getrandom 318
#define __NR_memfd_create 319
#define __NR_kexec_file_load 320
#define __NR_bpf 321
#define __NR_execveat 322
#define __NR_userfaultfd 323
#define __NR_membarrier 324
#define __NR_mlock2 325
#define __NR_copy_file_range 326
#define __NR_preadv2 327
#define __NR_pwritev2 328#endif /* _ASM_X86_UNISTD_64_H */

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

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

相关文章

在vue项目使用数据可视化 echarts ,柱状图、折线图、饼状图使用示例详解及属性详解

官网地址&#xff1a;Apache ECharts ​一、下载插件并在页面中引入 npm install echarts --save 页面导入&#xff1a; import * as echarts from echarts 全局导入&#xff1a; main.js 中&#xff0c;导入并注册到全局 import echarts from echarts Vue.prototype.$echart…

Flutter实现倒计时功能,秒数转时分秒,然后倒计时

Flutter实现倒计时功能 发布时间&#xff1a;2023/05/12 本文实例为大家分享了Flutter实现倒计时功能的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下 有一个需求&#xff0c;需要在页面进行显示倒计时&#xff0c;倒计时结束后&#xff0c;做相应的逻辑处理。 实…

spring cloud alibaba 应用无法注册到sentinel dashboard

一。技术背景 由于升级jdk17的需要 我们将项目中的 spring cloud spring cloud alibaba 以及springboot进行了升级 各版本如下 spring cloud 2021.0.5 spring cloud alibaba 2021.0.5.0 spring boot 2.6.13 二。问题表现 当启动项目服务后&#xff0c;服务无法注册到 sentin…

C#,OpenCV开发指南(01)

C#&#xff0c;OpenCV开发指南&#xff08;01&#xff09; 一、OpenCV的安装1、需要安装两个拓展包&#xff1a;OpenCvSharp4和OpenCvSharp4.runtime.win 二、C#使用OpenCV的一些代码1、需要加头文件2、读取图片3、在图片上画矩形框4、 在图片上画直线 一、OpenCV的安装 1、需…

10个牛逼的编程范式,你已经用过了几个?

《10个牛逼的编程范式&#xff0c;你已经用过了几个&#xff1f;》 编程范式是计算机编程中的基本思想和方法论&#xff0c;它描述了不同的编程风格和抽象层次。随着计算机科学的不断发展&#xff0c;编程范式也在不断演进和扩展&#xff0c;从最早的命令式编程到面向对象、声明…

【JavaSpring】注解开发

注解开发定义bean 不指定名称 package org.example.service.impl;import org.example.dao.BookDao; import org.example.service.BookService; import org.springframework.stereotype.Component;Component public class BookServiceimpl implements BookService {private Bo…

opencv基础57-模板匹配cv2.matchTemplate()->(目标检测、图像识别、特征提取)

OpenCV 提供了模板匹配&#xff08;Template Matching&#xff09;的功能&#xff0c;它允许你在图像中寻找特定模板&#xff08;小图像&#xff09;在目标图像中的匹配位置。模板匹配在计算机视觉中用于目标检测、图像识别、特征提取等领域。 以下是 OpenCV 中使用模板匹配的基…

【无标题】云原生在工业互联网的落地及好处!

什么是工业互联网&#xff1f; 工业互联网&#xff08;Industrial Internet&#xff09;是新一代信息通信技术与工业经济深度融合的新型基础设施、应用模式和工业生态&#xff0c;通过对人、机、物、系统等的全面连接&#xff0c;构建起覆盖全产业链、全价值链的全新制造和服务…

AI自动驾驶

AI自动驾驶 一、自动驾驶的原理二、自动驾驶的分类三、自动驾驶的挑战四、自动驾驶的前景五、关键技术六、自动驾驶的安全问题七、AI数据与自动驾驶八、自动驾驶的AI算法总结 自动驾驶技术是近年来备受关注的热门话题。它代表了人工智能和机器学习在汽车行业的重要应用。本文将…

leetcode1052. 爱生气的书店老板(java)

爱生气的书店老板 爱生气的书店老板题目描述滑动窗口代码演示 往期经典算法 爱生气的书店老板 难度 - 中等 原题链接 - 爱生气的书店老板 题目描述 有一个书店老板&#xff0c;他的书店开了 n 分钟。每分钟都有一些顾客进入这家商店。给定一个长度为 n 的整数数组 customers &a…

C语言实现选择排序

什么是选择排序&#xff1f; 选择排序是一种简单直观的排序算法&#xff0c;它的核心思想是每次从未排序的元素中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;然后将其放到已排序序列的末尾。通过重复这个过程&#xff0c;直到所有元素都排好序为止。 选择排序…

无涯教程-Perl - link函数

描述 此函数创建一个新文件名NEWFILE,链接到文件OLDFILE。该函数创建一个硬链接&#xff1b;如果需要符号链接,请使用符号链接功能。 语法 以下是此函数的简单语法- link OLDFILE,NEWFILE返回值 如果失败,此函数返回0,如果成功,则返回1。 例 以下是显示其基本用法的示例…