Linux/Unix系统编程

本文是《Hands-On System Programming with C++》读书笔记,重点介绍了Linux ABI,包括System V ABI的寄存器格式、栈格式、函数调用约定、异常处理和虚拟内存布局。深入探讨了ELF文件格式,如section和segment。此外,还涵盖了Unix文件系统、进程(fork、wait)、进程间通信(管道、共享内存)和信号处理等内容。
摘要由CSDN通过智能技术生成

《Hands-On System Programming with C++》读书笔记之五

The Linux ABI

The System V ABI

Unix System V是Unix的第一个版本。Linux采用了了System V ABI规范并沿用至今。注意这里指的是x86平台,在ARM平台上的ABI并不是完全相同的。
System V AB定义了一个应用程序的底层细节,包括:

寄存器格式

这里仅限介绍Intel-64bit(AMD64)平台。
rip是指令指针,定义指令的当前位置。
rsp是栈指针,rbp是栈头指针,共同定义了栈的当前位置。
其他通用寄存器包括rax/rbx/rcx/rdx/rdi/rsi/r8~r15。还有一些浮点寄存器和用于SSE/AVX指令的专用寄存器。

栈格式

high |----------| <- top of stack
| |
| Used |
| |
|----------| <- Current frame (rbp)
| | <- Stack pointer (rsp)
|----------|
| |
| Unused |
| |
low |----------|

注意是从Top向Bottom长的。
Stack由Stack frame组成,每个stack frame长这样:

high |----------|
| … |
|----------|
| arg8 |
|----------|
| arg7 |
|----------|
| ret addr |
|----------| <- Stack pointer (rbp)
| |
low |----------|

每个frame代表一个函数调用,操作系统负责确保栈的空间够用。
在x86平台下,call指令用来跳转到调用的函数同时把rip指针推入到栈中,ret指令则弹出返回地址并跳到这个地址开始执行。

函数的序与跋(prologs and epilogs)

通过一个函数调用的例子查看了汇编代码,函数执行的“序”是

push %rbp
mov  %rsp %rbp

即原rbp入栈并用rsp更新rbp
函数执行的“跋”是

pop %rbp
retq

rbp出栈恢复,最后跳回函数调用前的地址。

调用的约定

调用的约定指明了在函数调用过程中哪些寄存器是挥发性的(volatile),哪些是费挥发性的(non-volatile),哪些用来传递参数,哪些用来返回结果。
在System V ABI中,rbx/rbp/r12/r13/r14/r15是非挥发性的,调入的函数要首先入栈保存这些寄存器的内容,返回时出栈恢复它们。
rdi/rsi/rdx/rcx/r8/r9用来传递参数,rax/rdx用来返回结果。当多于128bit的结果需要返回时,需要调用栈来完成。
注意以r开头命名的寄存器是64位的,以r开头命名的寄存器是32位的。

Exception handling and debugging
  • C++异常处理的性能代价很高,因此处理错误处理,不能用在其它的控制流程中;
  • 不使用异常时处理时,GGC参数*-fno-exceptions*可以减少最终代码的大小。

这里举了这样一个例子

#include <iostream>
#include <exception>
void test(int i)
{
   
    if (i == 42) {
   
        throw 42;
    }
}

int main()
{
   
    try{
   
        test(1);
        std::cout << "attempt #1: passed" << std::endl;

        test(42); // exception catch
        std::cout << "attempt #1: passed" << std::endl;
    }
    catch(...) {
   
        std::cout << "exception catch" << std::endl;
    }
}

并配合其汇编代码讲解了异常处理的过程。整个过程还是比较复杂的,细节需要结合汇编代码看书。
一旦进入异常__cxa_throw(),函数的返回部分就得不到执行了,而哪些非挥发性的寄存器还是要恢复的,这又引入了DWARF机制。细节很复杂,简而言之,书中结论,使用exception的成本很高。

Virtual Memory Layout

虚拟内存的布局也是System V规定的内容。

Executable and Linkable Format (ELF)

例如

int main(void)
{
   
}

hexdump -C a.out命令查看十六进制文件

7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF…|

.ELF字符串出现在所有可执行文件的开头。
readelf -hW a.out命令查看ELF文件的信息:

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: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4f0
Start of program headers: 64 (bytes into file)
Start of section headers: 6384 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 28
Section header string table index: 27

在ELF文件中,有很多section,若干section又组成segment。

ELF的section

readelf -SW a.out命令查看各个section。

There are 28 section headers, starting at offset 0x18f0:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000238 000238 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 000254 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000000274 000274 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 000298 00001c 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002b8 0002b8 000090 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000348 000348 00007d 00 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000003c6 0003c6 00000c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000003d8 0003d8 000020 00 A 6 1 8
[ 9] .rela.dyn RELA 00000000000003f8 0003f8 0000c0 18 A 5 0 8
[10] .init PROGBITS 00000000000004b8 0004b8 000017 00 AX 0 0 4
[11] .plt PROGBITS 00000000000004d0 0004d0 000010 10 AX 0 0 16
[12] .plt.got PROGBITS 00000000000004e0 0004e0 000008 08 AX 0 0 8
[13] .text PROGBITS 00000000000004f0 0004f0 000192 00 AX 0 0 16
[14] .fini PROGBITS 0000000000000684 000684 000009 00 AX 0 0 4
[15] .rodata PROGBITS 0000000000000690 000690 000004 04 AM 0 0 4
[16] .eh_frame_hdr PROGBITS 0000000000000694 000694 00003c 00 A 0 0 4
[17] .eh_frame PROGBITS 00000000000006d0 0006d0 000108 00 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000200df0 000df0 000008 08 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000200df8 000df8 000008 08 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000200e00 000e00 0001c0 10 WA 6 0 8
[21] .got PROGBITS 0000000000200fc0 000fc0 000040 08 WA 0 0 8
[22] .data PROGBITS 0000000000201000 001000 000010 00 WA 0 0 8
[23] .bss NOBITS 0000000000201010 001010 000008 00 WA 0 0 1
[24] .comment PROGBITS 0000000000000000 001010 000029 01 MS 0 0 1
[25] .symtab SYMTAB 0000000000000000 001040 0005b8 18 26 42 8
[26] .strtab STRTAB 0000000000000000 0015f8 0001f8 00 0 0 1
[27] .shstrtab STRTAB 0000000000000000 0017f0 0000f9 00 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)

在刚才那个什么都没做的例子里,同样有这么多的section。

  • eh_frame/eh_frame_hdr:异常处理
  • init_array/fini_array/init/fini:链接库
  • dynsym:动态链接symbols
  • text:代码部分
  • data:初始化为非0的全局变量
  • bss:初始化为0的全局变量,在ELF文件中不占用空间,运行时再初始化为0
  • dynstr/strtab:存储symbol names
  • rodata:常量
ELF的segment

每个segment若干个section组成的,用readelf -lW a.out命令查看各个section。

Elf file type is DYN (Shared object file)
Entry point 0x4f0
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0001f8 0x0001f8 R 0x8
INTERP 0x000238 0x0000000000000238 0x0000000000000238 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0007d8 0x0007d8 R E 0x200000
LOAD 0x000df0 0x0000000000200df0 0x0000000000200df0 0x000220 0x000228 RW 0x200000
DYNAMIC 0x000e00 0x0000000000200e00 0x0000000000200e00 0x0001c0 0x0001c0 RW 0x8
NOTE 0x000254 0x0000000000000254 0x0000000000000254 0x000044 0x000044 R 0x4
GNU_EH_FRAME 0x000694 0x0000000000000694 0x0000000000000694 0x00003c 0x00003c R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x000df0 0x0000000000200df0 0x0000000000200df0 0x000210 0x000210 R 0x1
Section to Segment mapping:
Segment Sections…
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got

每个segment会提供信息,告诉loader该segment应当如何被加载,是否可读、可写、可执行,是否支持relocation等等。

Unix文件系统

Unix文件系统由一个虚拟的文件树构成,根目录是“/”,物理文件系统被映射到这个文件树上。文件树的结点可以被映射为各种设备和资源。

  • /bin:二进制文件
  • /boot:系统启动文件
  • /dev:物理和虚拟设备
  • /etc:系统配置文件
  • /home:用户文件
  • /lib:库文件
  • /mnt and /media:临时挂载点
  • /sbin:系统二进制文件
  • /tmp:复位即删除的临时文件
  • /usr:以上目录的用户特定版本
    Unix文件系统记录每个用户的权限,包括是否有权读、写、执行某个文件。
    熟悉Linux操作的应该都了解这部分了。

Unix Processes

A process on a Unix-based system is a userspace application executed and s

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值