操作系统第二次实验报告——Linux创建进程及可执行文件结构分析

0 个人信息

  • 张樱姿
  • 201821121038
  • 计算1812

1 实验目的

  • 熟练Linux创建进程fork操作。

2 实验内容

  • 在服务器上用VIM编写一个程序:一个进程创建两个子进程。
  • 查看进程树
  • 查看进程相关信息

3 实验报告

 3.1编写程序创建两个子进程

 1 #include
 2 #include
 3 #include
 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 }

   编译运行后的结果:

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第1张图片

     3.2打印进程树

 添加sleep函数以挂起进程,方便打印进程树:

 1 #include
 2 #include
 3 #include
 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  #打印进程树

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第2张图片  3.3 解读进程相关信息

    3.3.1 解释执行ps -ef后返回结果中每个字段的含义

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第3张图片

     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后返回结果中每个字段的含义

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第4张图片

      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):

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第5张图片

    发现这是个二进制文件,因此(在vim中)先把它转换成十六进制文件以便查看。之后要记得转换回来。

:%!xxd     #将2进制格式文件转换为16进制格式
:%!xxd -r  #转换回2进制格式

     转换为16进制后(小端地址存储,采取两个两个从右往左读的方法):

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第6张图片

   3.4.3 readelf/objdump解析该可执行文件(hello.out):

  readelf命令本身也是一个elf类型的可执行文件,用来解析二进制的elf文件;另外objdump命令可以用来解析exe或elf文件,也更具一般性。

  readelf命令的使用方法:

Usage: readelf  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=
                         Dump the contents of section  as bytes
  -p --string-dump=
                         Dump the contents of section  as strings
  -R --relocated-dump=
                         Dump the contents of section  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

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第7张图片

    该可执行文件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字节)。

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第8张图片

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

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第9张图片

   同理,可打印.data节(编号为23)的内容:

readelf -x 23 hello.out

   ③使用以下命令查看hello.out的重定位信息:

readelf -r hello.out

操作系统第二次实验报告——Linux创建进程及可执行文件结构分析_第10张图片

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

你可能感兴趣的:(操作系统第二次实验报告——Linux创建进程及可执行文件结构分析)