操作系统第二次实验报告——Linux创建进程及可执行文件结构分析
0 个人信息
- 张樱姿
- 201821121038
- 计算1812
1 实验目的
- 熟练Linux创建进程fork操作。
2 实验内容
- 在服务器上用VIM编写一个程序:一个进程创建两个子进程。
- 查看进程树
- 查看进程相关信息
3 实验报告
3.1编写程序创建两个子进程
1 #include<sys/types.h> 2 #include<stdio.h> 3 #include<unistd.h> 4 5 int main(){ 6 pid_t cpid1 = fork(); //创建子进程1 7 8 if(cpid1<0){ 9 printf("fork cd1 failed\n"); 10 } 11 else if(cpid1==0){ 12 printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid()); 13 } 14 else{ 15 pid_t cpid2 = fork(); //创建子进程2 16 if(cpid2<0){ 17 printf("fork cd2 failed\n"); 18 } 19 else if(cpid2==0){ 20 printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid()); 21 } 22 else{ 23 printf("Parent: pid :%d\n",getpid()); 24 } 25 } 26 }
编译运行后的结果:
3.2打印进程树
添加sleep函数以挂起进程,方便打印进程树:
1 #include<sys/types.h> 2 #include<stdio.h> 3 #include<unistd.h> 4 5 int main(){ 6 pid_t cpid1 = fork(); 7 8 if(cpid1<0){ 9 printf("fork cd1 failed\n"); 10 } 11 else if(cpid1==0){ 12 printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid()); 13 sleep(30); //挂起30秒 14 } 15 else{ 16 pid_t cpid2 = fork(); 17 if(cpid2<0){ 18 printf("fork cd2 failed\n"); 19 } 20 else if(cpid2==0){ 21 printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid()); 22 sleep(30); //挂起30秒 23 } 24 else{ 25 printf("Parent: pid :%d\n",getpid()); 26 sleep(60); //挂起60秒 27 } 28 } 29 }
pstree -p pid #打印进程树
3.3 解读进程相关信息
3.3.1 解释执行ps -ef后返回结果中每个字段的含义
ps -ef输出格式 :
UID PID PPID C STIME TTY TIME CMD
- UID: User ID,用户ID。
- PID: Process ID ,进程ID。
- PPID: Parent Process Pid,父进程ID。
- C: CPU使用的资源百分比。
- STIME: Start Time,进程启动时间。
- TTY: Controlling Tty,进程的控制终端。
- TIME: 进程占用CPU的时间总和。如果运行时间达到 100 分钟,以 mm:ss 或 mmmm:ss 格式显示时间。
- CMD: Command name/line,所下达的指令名。
3.3.2 解释执行ps -aux后返回结果中每个字段的含义
ps -au(x) 输出格式 :
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
- USER: User Name,行程拥有者。
- PID: Process ID ,进程ID。
- %CPU: CPU usage,该进程占用的 CPU 资源百分比。
- %MEM: Memory usage (RES),该进程所占用的物理内存百分比。
- VSZ: Virtual Memory Size,该进程占用的虚拟内存大小,表明了该进程可以访问的所有内存,包括被交换的内存和共享库内存。
- RSS: Resident Set Size,常驻内存集合大小,表示相应进程在RAM中占用了多少内存,并不包含在SWAP中占用的虚拟内存。
- TTY: Controlling Tty,进程的控制终端。
- STAT: Status,该进程程的状态:
进程状态 | 含义 |
D | 不可中断 Uninterruptible sleep (usually IO) |
R | 正在运行,或在队列中的进程 |
S | 处于休眠状态 |
T | 停止或被追踪 |
Z | 僵尸进程 |
W | 进入内存交换(从内核2.6开始无效) |
X | 死掉的进程 |
< | 高优先级 |
N | 低优先级 |
L | 有些页被锁进内存 |
s | 包含子进程 |
+ | 位于后台的进程组 |
l | 多线程,克隆线程 |
- START: 行程开始时间
- TIME: 执行的时间
- COMMAND:所执行的指令
3.4 分析Linux可执行文件构成
首先写一个hello.c,
3.4.1 使用file查看文件类型
①使用以下命令生成可重定位目标文件。即文件中的代码段和数据的地址 还没有最终确定。
gcc -c hello.c -o hello.o
②使用以下命令生成一个可执行共享对象文件。
gcc -o hello.out hello.c
Linux下可执行文件的格式主要是ELF格式,即可链接格式(Executable and Linkable Format)。
ELF文件由四个部分组成:ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。包括三个索引表:
- ELF头:ELF header,在文件开始处描述了整个文件的组织情况。ELF的文件头包含整个执行文件的控制结构。
- 程序头表:program header table,用来告知系统如何创建进程映像。
- 节头表:section header table,包含描述文件节区的信息,每个节区在表中都有一项,给出节区名称、大小等信息。
上面生成的hello.o和hello.out有什么区别?
①.o文件通常没有程序头表(program header table),而是由一个或多个.o文件链接编译后生成程序头表。
②.o文件用section来概括它的各个部分,而.out文件,编译器会将它的各个section进一步整合成各大的部分,称为segment。
③因此,对于目标代码文件(.o文件)来说,program header table是可选的,而对于可执行文件(.out文件)来说,section header table是可选的。
所以目标文件并不是可执行的,其链接后才生成可执行文件。这里讨论的是可执行文件的内部结构,即hello.out。可见,绿色也代表了它的可执行性。
3.4.2 vim查看该可执行文件(hello.out):
发现这是个二进制文件,因此(在vim中)先把它转换成十六进制文件以便查看。之后要记得转换回来。
:%!xxd #将2进制格式文件转换为16进制格式
:%!xxd -r #转换回2进制格式
转换为16进制后(小端地址存储,采取两个两个从右往左读的方法):
3.4.3 readelf/objdump解析该可执行文件(hello.out):
readelf命令本身也是一个elf类型的可执行文件,用来解析二进制的elf文件;另外objdump命令可以用来解析exe或elf文件,也更具一般性。
readelf命令的使用方法:
Usage: readelf <option(s)> elf-file(s) Display information about the contents of ELF format files Options are: -a --all Equivalent to: -h -l -S -s -r -d -V -A -I -h --file-header Display the ELF file header -l --program-headers Display the program headers --segments An alias for --program-headers -S --section-headers Display the sections' header --sections An alias for --section-headers -g --section-groups Display the section groups -t --section-details Display the section details -e --headers Equivalent to: -h -l -S -s --syms Display the symbol table --symbols An alias for --syms --dyn-syms Display the dynamic symbol table -n --notes Display the core notes (if present) -r --relocs Display the relocations (if present) -u --unwind Display the unwind info (if present) -d --dynamic Display the dynamic section (if present) -V --version-info Display the version sections (if present) -A --arch-specific Display architecture specific information (if any) -c --archive-index Display the symbol/file index in an archive -D --use-dynamic Use the dynamic section info when displaying symbols -x --hex-dump=<number|name> Dump the contents of section <number|name> as bytes -p --string-dump=<number|name> Dump the contents of section <number|name> as strings -R --relocated-dump=<number|name> Dump the contents of section <number|name> as relocated bytes -z --decompress Decompress section before dumping it -w[lLiaprmfFsoRtUuTgAckK] or --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames, =frames-interp,=str,=loc,=Ranges,=pubtypes, =gdb_index,=trace_info,=trace_abbrev,=trace_aranges, =addr,=cu_index,=links,=follow-links] Display the contents of DWARF debug sections --dwarf-depth=N Do not display DIEs at depth N or greater --dwarf-start=N Display DIEs starting with N, at the same depth or deeper -I --histogram Display histogram of bucket list lengths -W --wide Allow output width to exceed 80 characters @<file> Read options from <file> -H --help Display this information -v --version Display the version number of readelf
①使用以下命令查看hello.out的ELF Header:
readelf -h hello.out
该可执行文件hello.out文件的程序头大小为56字节。
64位系统的ELF头文件(位于/usr/include/elf.h中)定义:
typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf64_Half e_type; /* Object file type */ Elf64_Half e_machine; /* Architecture */ Elf64_Word e_version; /* Object file version */ Elf64_Addr e_entry; /* Entry point virtual address */ Elf64_Off e_phoff; /* Program header table file offset */ Elf64_Off e_shoff; /* Section header table file offset */ Elf64_Word e_flags; /* Processor-specific flags */ Elf64_Half e_ehsize; /* ELF header size in bytes */ Elf64_Half e_phentsize; /* Program header table entry size */ Elf64_Half e_phnum; /* Program header table entry count */ Elf64_Half e_shentsize; /* Section header table entry size */ Elf64_Half e_shnum; /* Section header table entry count */ Elf64_Half e_shstrndx; /* Section header string table index */ } Elf64_Ehdr;
第一行:
- e_ident用16个字节表示为“7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00”,其中“7f45 4c46”表示“ELF”的ASCII码表。“0102”中前一个“02”表示是64位的机器,后一个“01”表示使用的是小端法。“0100”中的“01”表示的是版本号是01。剩下的0为默认填充。
第二行:
- e_type用2个字节表示,为“0003”,表示是一个动态共享目标文件。
- e_machine用2个字节表示,为“003e”,表示Inter 80386的处理器体系结构(64位)。
- e_version用4个字节表示,为“0000 0001”,表示的是当前版本。
- e_entry用4个字节表示,为“0000 0530 ”表示没有入口地址为0x530。
第三行:
- e_phoff用8个字节表示,为“0000 0000 0000 0040”表示程序头表(Program header table)在文件中的偏移量为64字节。
- e_shoff用8个字节表示,为“0000 0000 0000 1930”表示段表(Section header table)在文件中的偏移量为6448字节。
第四行:
- e_flags用4个字节表示,为“0000 0000”表示未知处理器特定标志。
- e_ehsize用2个字节表示,为“0040”表示elf文件头大小为64字节。
- e_phentsize用2个字节表示,为“0038”表示程序头表中每一个条目的大小为56字节。
- e_phnum用2个字节表示,为“0009”表示Program header table中有9个条目。
- e_shentsize用2个字节表示,为“0040”,表示段头大小为64个字节(由此知道section header table里面每一个table的大小为64个字节)。
- e_shnum用2个字节表示,为“001d”,表示段表入口有29个,即段有29个。
- e_shstrndx用2个字节表示,为“001c”,表示段名串表在段表中的索引,(符号表的信息在段表的索引号是28)。
②使用以下命令查看hello.out的段表信息:
readelf -S hello.out
段表信息:其中.text section是可执行指令的集合,位偏移0x0000 0530,size=0x0000 01a2(即418字节),.data section是初始化后数据的集合,位偏移 0x0000 1000,size=0x0000 0010(即16字节),.symtab section存放所有section中定义的符号名字,.strtab section位偏移0x0000 1628,size=0x0000 0203(即515字节),.shstrtab section与.symtab section之间存储的是段表。
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000000254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000000274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000000298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000000002b8 000002b8 00000000000000a8 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000000360 00000360 0000000000000082 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 00000000000003e2 000003e2 000000000000000e 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 00000000000003f0 000003f0 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000000410 00000410 00000000000000c0 0000000000000018 A 5 0 8 [10] .rela.plt RELA 00000000000004d0 000004d0 0000000000000018 0000000000000018 AI 5 22 8 [11] .init PROGBITS 00000000000004e8 000004e8 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000000500 00000500 0000000000000020 0000000000000010 AX 0 0 16 [13] .plt.got PROGBITS 0000000000000520 00000520 0000000000000008 0000000000000008 AX 0 0 8 [14] .text PROGBITS 0000000000000530 00000530 00000000000001a2 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 00000000000006d4 000006d4 0000000000000009 0000000000000000 AX 0 0 4 [16] .rodata PROGBITS 00000000000006e0 000006e0 0000000000000010 0000000000000000 A 0 0 4 [17] .eh_frame_hdr PROGBITS 00000000000006f0 000006f0 000000000000003c 0000000000000000 A 0 0 4 [18] .eh_frame PROGBITS 0000000000000730 00000730 0000000000000108 0000000000000000 A 0 0 8 [19] .init_array INIT_ARRAY 0000000000200db8 00000db8 0000000000000008 0000000000000008 WA 0 0 8 [20] .fini_array FINI_ARRAY 0000000000200dc0 00000dc0 0000000000000008 0000000000000008 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000200dc8 00000dc8 00000000000001f0 0000000000000010 WA 6 0 8 [22] .got PROGBITS 0000000000200fb8 00000fb8 0000000000000048 0000000000000008 WA 0 0 8 [23] .data PROGBITS 0000000000201000 00001000 0000000000000010 0000000000000000 WA 0 0 8 [24] .bss NOBITS 0000000000201010 00001010 0000000000000008 0000000000000000 WA 0 0 1 [25] .comment PROGBITS 0000000000000000 00001010 000000000000002b 0000000000000001 MS 0 0 1 [26] .symtab SYMTAB 0000000000000000 00001040 00000000000005e8 0000000000000018 27 43 8 [27] .strtab STRTAB 0000000000000000 00001628 0000000000000203 0000000000000000 0 0 1 [28] .shstrtab STRTAB 0000000000000000 0000182b 00000000000000fe 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)
根据刚才在ELF头文件中读到的段表偏移为1930H字节,即6448字节。且段表存储在0x0000 0000 0000 1930~~~0x0000 0000 0000 2070,共40H*29=740H字节。以第二个段表(0x0000 0000 0000 1970~~~0x0000 0000 0000 19B0)作为例子分析,每个段表占40H字节(64字节)。
64位系统的段表(位于/usr/include/elf.h中)定义:
typedef struct { Elf64_Word sh_name; /* Section name (string tbl index) */ Elf64_Word sh_type; /* Section type */ Elf64_Xword sh_flags; /* Section flags */ Elf64_Addr sh_addr; /* Section virtual addr at execution */ Elf64_Off sh_offset; /* Section file offset */ Elf64_Xword sh_size; /* Section size in bytes */ Elf64_Word sh_link; /* Link to another section */ Elf64_Word sh_info; /* Additional section information */ Elf64_Xword sh_addralign; /* Section alignment */ Elf64_Xword sh_entsize; /* Entry size if section holds table */ } Elf64_Shdr;
- sh_name用4个字节表示,为“0000 001b”,该值代表section header string table中的索引。
- sh_type 用4个字节表示,为“0000 0001”,表示该段的类型是“SHT_PROGBITS”,程序节。
- sh_flags 用8个字节表示,为“0000 0000 0000 0002”,指示该section在进程执行时的特性,这里表示表示该section在进程空间中必须要分配空间。
- sh_addr 用8个字节表示,为“0000 0000 0000 0238”,表示该节在进程中的起始地址为“0x238”。
- sh_offset用8个字节表示,为“0000 0000 0000 0238”表示该节在整个文件中的起始偏移量为“0x238”。
- sh_size用8个字节表示,为“0000 0000 0000 001c”,表示该节的字节大小为0x1c。
- sh_link用4个字节表示,为“0000 0000”,表示没有和该节相关联的节。
- sh_info用4个字节表示,为“0000 0000”表示没有文件信息。
- sh_addralign用8个字节表示,为“0000 0000 0000 0001”,该值用于表示地址对齐信息,值为1时表示不用地址对齐。
- sh_entsize用8个字节表示,为“0000 0000 0000 0000”,对特定节(动态符号)才有意义,这里没有意义。
使用以下命令查看hello.out的.text节(编号为14),即代码部分:
readelf -x 14 hello.out
同理,可打印.data节(编号为23)的内容:
readelf -x 23 hello.out
③使用以下命令查看hello.out的重定位信息:
readelf -r hello.out
4 References
- https://blog.51cto.com/icyhome/1674101
- http://blog.itpub.net/29757574/viewspace-2150678/
- https://blog.csdn.net/abc_12366/article/details/88205670
- https://www.cnblogs.com/java-stx/p/5551160.html
- https://www.jianshu.com/p/44edcc1a9f60
- https://www.cnblogs.com/zzzz5/p/5527545.html
- https://baike.baidu.com/item/ELF/7120560
- https://www.cnblogs.com/jiqingwu/p/elf_explore_3.html
- https://tool.oschina.net/hexconvert
- https://tool.oschina.net/hexconvert