{"action":"opened","issue":{"url":"https://api.github.com/repos/chenpengcong/blog/issues/14","repository_url":"https://api.github.com/repos/chenpengcong/blog","labels_url":"https://api.github.com/repos/chenpengcong/blog/issues/14/labels{/name}","comments_url":"https://api.github.com/repos/chenpengcong/blog/issues/14/comments","events_url":"https://api.github.com/repos/chenpengcong/blog/issues/14/events","html_url":"https://github.com/chenpengcong/blog/issues/14","id":326806309,"number":14,"title":"ELF可执行文件在虚拟地址空间的分布","user":{"login":"chenpengcong","id":12814622,"avatar_url":"https://avatars2.githubusercontent.com/u/12814622?v=4","gravatar_id":"","url":"https://api.github.com/users/chenpengcong","html_url":"https://github.com/chenpengcong","followers_url":"https://api.github.com/users/chenpengcong/followers","following_url":"https://api.github.com/users/chenpengcong/following{/other_user}","gists_url":"https://api.github.com/users/chenpengcong/gists{/gist_id}","starred_url":"https://api.github.com/users/chenpengcong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chenpengcong/subscriptions","organizations_url":"https://api.github.com/users/chenpengcong/orgs","repos_url":"https://api.github.com/users/chenpengcong/repos","events_url":"https://api.github.com/users/chenpengcong/events{/privacy}","received_events_url":"https://api.github.com/users/chenpengcong/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-05-27T10:55:32Z","updated_at":"2018-05-27T10:55:32Z","closed_at":null,"author_association":"OWNER","body":"本文将根据一个实际可执行文件，画出其ELF文件结构图及其与虚拟地址空间和物理内存的映射关系。\r\n\r\n> 本机环境: deepin15.5 64bit\r\n\r\n可执行程序源代码如下：\r\n```C\r\n#include \"unistd.h\"\r\nint main()\r\n{\r\n    while (1) {\r\n        sleep(1000);\r\n    }\r\n    return 0;\r\n}\r\n```\r\n\r\n编译出可执行文件：`$ gcc -static -o SectionMapping SectionMapping.c`\r\n\r\n要画出其ELF文件结构图，我们需要查看该可执行文件各个section的大小和偏移等信息，这些信息通过查看ELF header和段表得知\r\n\r\n查看ELF header：`$ readelf -h SectionMapping`\r\n```\r\n$ readelf -h SectionMapping\r\nELF Header:\r\n  Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00 \r\n  Class: ELF64\r\n  Data: 2's complement, little endian\r\n  Version: 1 (current)\r\n  OS/ABI: UNIX - GNU\r\n  ABI Version: 0\r\n  Type: EXEC (Executable file)\r\n  Machine: Advanced Micro Devices X86-64\r\n  Version: 0x1\r\n  Entry point address: 0x400930\r\n  Start of program headers: 64 (bytes into file)\r\n  Start of section headers: 804624 (bytes into file)\r\n  Flags: 0x0\r\n  Size of this header: 64 (bytes)\r\n  Size of program headers: 56 (bytes)\r\n  Number of program headers: 6\r\n  Size of section headers: 64 (bytes)\r\n  Number of section headers: 32\r\n  Section header string table index: 31\r\n```\r\n\r\n从输出结果获取到的信息为\r\n- ELF header大小为64bytes\r\n- Section table(段表)偏移为0xc4710(804624)，大小为0x800ytes(64 * 32)\r\n- Program header table(程序头表)偏移为0x40(64)，大小为0x150bytes(56 * 6)\r\n\r\n查看Section table(段表)：`$ readelf -S SectionMapping`\r\n```\r\n$ readelf -S SectionMapping\r\nThere are 32 section headers, starting at offset 0xc4710:\r\n\r\nSection Headers:\r\n  [Nr] Name              Type             Address           Offset\r\n       Size              EntSize          Flags  Link  Info  Align\r\n  [ 0]                   NULL             0000000000000000  00000000\r\n       0000000000000000  0000000000000000           0     0     0\r\n  [ 1] .note.ABI-tag     NOTE             0000000000400190  00000190\r\n       0000000000000020  0000000000000000   A       0     0     4\r\n  [ 2] .note.gnu.build-i NOTE             00000000004001b0  000001b0\r\n       0000000000000024  0000000000000000   A       0     0     4\r\nreadelf: Warning: [ 3]: Link field (0) should index a symtab section.\r\n  [ 3] .rela.plt         RELA             00000000004001d8  000001d8\r\n       0000000000000108  0000000000000018  AI       0    24     8\r\n  [ 4] .init             PROGBITS         00000000004002e0  000002e0\r\n       0000000000000017  0000000000000000  AX       0     0     4\r\n  [ 5] .plt              PROGBITS         00000000004002f8  000002f8\r\n       0000000000000058  0000000000000000  AX       0     0     8\r\n  [ 6] .text             PROGBITS         0000000000400350  00000350\r\n       00000000000888b7  0000000000000000  AX       0     0     16\r\n  [ 7] __libc_freeres_fn PROGBITS         0000000000488c10  00088c10\r\n       0000000000000ab7  0000000000000000  AX       0     0     16\r\n  [ 8] __libc_thread_fre PROGBITS         00000000004896d0  000896d0\r\n       00000000000000e1  0000000000000000  AX       0     0     16\r\n  [ 9] .fini             PROGBITS         00000000004897b4  000897b4\r\n       0000000000000009  0000000000000000  AX       0     0     4\r\n  [10] .rodata           PROGBITS         00000000004897c0  000897c0\r\n       000000000001c724  0000000000000000   A       0     0     32\r\n  [11] __libc_subfreeres PROGBITS         00000000004a5ee8  000a5ee8\r\n       0000000000000050  0000000000000000   A       0     0     8\r\n  [12] __libc_IO_vtables PROGBITS         00000000004a5f40  000a5f40\r\n       00000000000006a8  0000000000000000   A       0     0     32\r\n  [13] __libc_atexit     PROGBITS         00000000004a65e8  000a65e8\r\n       0000000000000008  0000000000000000   A       0     0     8\r\n  [14] __libc_thread_sub PROGBITS         00000000004a65f0  000a65f0\r\n       0000000000000008  0000000000000000   A       0     0     8\r\n  [15] .eh_frame         PROGBITS         00000000004a65f8  000a65f8\r\n       000000000000a64c  0000000000000000   A       0     0     8\r\n  [16] .gcc_except_table PROGBITS         00000000004b0c44  000b0c44\r\n       00000000000000af  0000000000000000   A       0     0     1\r\n  [17] .tdata            PROGBITS         00000000006b0eb8  000b0eb8\r\n       0000000000000020  0000000000000000 WAT       0     0     8\r\n  [18] .tbss             NOBITS           00000000006b0ed8  000b0ed8\r\n       0000000000000030  0000000000000000 WAT       0     0     8\r\n  [19] .init_array       INIT_ARRAY       00000000006b0ed8  000b0ed8\r\n       0000000000000010  0000000000000008  WA       0     0     8\r\n  [20] .fini_array       FINI_ARRAY       00000000006b0ee8  000b0ee8\r\n       0000000000000010  0000000000000008  WA       0     0     8\r\n  [21] .jcr              PROGBITS         00000000006b0ef8  000b0ef8\r\n       0000000000000008  0000000000000000  WA       0     0     8\r\n  [22] .data.rel.ro      PROGBITS         00000000006b0f00  000b0f00\r\n       00000000000000e4  0000000000000000  WA       0     0     32\r\n  [23] .got              PROGBITS         00000000006b0fe8  000b0fe8\r\n       0000000000000008  0000000000000008  WA       0     0     8\r\n  [24] .got.plt          PROGBITS         00000000006b1000  000b1000\r\n       0000000000000070  0000000000000008  WA       0     0     8\r\n  [25] .data             PROGBITS         00000000006b1080  000b1080\r\n       0000000000001ad0  0000000000000000  WA       0     0     32\r\n  [26] .bss              NOBITS           00000000006b2b60  000b2b50\r\n       0000000000001898  0000000000000000  WA       0     0     32\r\n  [27] __libc_freeres_pt NOBITS           00000000006b43f8  000b2b50\r\n       0000000000000030  0000000000000000  WA       0     0     8\r\n  [28] .comment          PROGBITS         0000000000000000  000b2b50\r\n       0000000000000025  0000000000000001  MS       0     0     1\r\n  [29] .symtab           SYMTAB           0000000000000000  000b2b78\r\n       000000000000b1d8  0000000000000018          30   753     8\r\n  [30] .strtab           STRTAB           0000000000000000  000bdd50\r\n       000000000000685b  0000000000000000           0     0     1\r\n  [31] .shstrtab         STRTAB           0000000000000000  000c45ab\r\n       000000000000015f  0000000000000000           0     0     1\r\nKey to Flags:\r\n  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),\r\n  L (link order), O (extra OS processing required), G (group), T (TLS),\r\n  C (compressed), x (unknown), o (OS specific), E (exclude),\r\n  l (large), p (processor specific)\r\n```\r\n从输出我们可以看到每个section的大小及其偏移\r\n\r\n通过查看段表和ELF header，此时我们已经可以画出可执行文件的整个ELF文件结构图\r\n\r\n接下来我们查看可执行程序的程序头(Program header)，该程序头描述了ELF该如何加载被操作系统映射到进程的虚拟空间\r\n```\r\n$ readelf -l SectionMapping\r\nElf file type is EXEC (Executable file)\r\nEntry point 0x400930\r\nThere are 6 program headers, starting at offset 64\r\nProgram Headers:\r\n  Type Offset VirtAddr PhysAddr\r\n                 FileSiz MemSiz Flags Align\r\n  LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000\r\n                 0x00000000000b0cf3 0x00000000000b0cf3 R E 0x200000\r\n  LOAD 0x00000000000b0eb8 0x00000000006b0eb8 0x00000000006b0eb8\r\n                 0x0000000000001c98 0x0000000000003570 RW 0x200000\r\n  NOTE 0x0000000000000190 0x0000000000400190 0x0000000000400190\r\n                 0x0000000000000044 0x0000000000000044 R 0x4\r\n  TLS 0x00000000000b0eb8 0x00000000006b0eb8 0x00000000006b0eb8\r\n                 0x0000000000000020 0x0000000000000050 R 0x8\r\n  GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000\r\n                 0x0000000000000000 0x0000000000000000 RW 0x10\r\n  GNU_RELRO 0x00000000000b0eb8 0x00000000006b0eb8 0x00000000006b0eb8\r\n                 0x0000000000000148 0x0000000000000148 R 0x1\r\n Section to Segment mapping:\r\n  Segment Sections...\r\n   00 .note.ABI-tag .note.gnu.build-id .rela.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres __libc_IO_vtables __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table \r\n   01 .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs \r\n   02 .note.ABI-tag .note.gnu.build-id \r\n   03 .tdata .tbss \r\n   04     \r\n   05 .tdata .init_array .fini_array .jcr .data.rel.ro .got\r\n``` \r\n可以看到该可执行文件有两个需要被映射到虚拟地址空间的Segment，分别为Segment0和Segment1，且它们由多个属性类似的Section组成。\r\n\r\n> Segment：对于属性类似(相同权限)的section，把它们合并到一起当作一个Segment进行映射，这样做的好处是可以明显减少页面内部碎片，从而节省了内存空间。\r\n\r\n\r\n| 段 | 起始虚拟地址 | segment在文件中所占空间的长度 | segment在虚拟地址空间所占用的长度 | segment在文件中的偏移 | 权限 | 对齐 | \r\n| --- | --- | --- | --- | --- | --- | --- |\r\n| Segment0 |  0x400000 | 0xb0cf3 | 0xb0cf3 | 0x00 | 可读可执行(RE) | 0x200000 |\r\n| Segment1 | 0x6b0eb8 | 0x1c98 | 0x3570 |0xb0eb8 | 可读可写(RW) |0x200000 |\r\n\r\n**注意：为什么Segment1在ELF文件中所占用空间长度为0x1c98，而在进程虚拟地址空间占用的大小memsize为0x3570**\r\n\r\n原因为Segment1包含了`.bss`和`__libc_freeres_ptrs`这两个Section，从查看段表的输出结果中可以看到这两个Section的`Type`为`NOBITS`，意味着它们在ELF文件中没有实际内容，不占文件空间。\r\n\r\n只要把.bss和__libc_freeres_ptrs在内存中实际占据空间加上0x1c98就可以得到0x3570这个值，计算过程如下：\r\n.bss在.data之后，.data的结束地址为0xb1080+0x1ad0 = 0xb2b50，由于.bss是32对齐，所以.bss的起始地址为0xb2b60，结束地址为0xb2b60+0x1898 = 0xb43f8,由于__libc_freeres_ptrs为8对齐，所以起始地址为0xb43f8，终止地址为0xb43f8 + 0x30 = 0xb4428，然后使用__libc_freeres_ptrs的结束地址减去.data的结束地址就可以得到这两个段在内存中占据的空间大小0xb4428-0xb2b50 = 0x18d8，加上0x1c98刚好等于0x3570\r\n\r\n查询系统页大小：`$ getconf PAGE_SIZE`\r\n```\r\n$ getconf PAGE_SIZE\r\n4096\r\n```\r\n\r\n根据上述得到的信息，我们可以画出可执行文件，虚拟地址空间和物理内存三者的映射关系\r\n\r\n![sectionmapping](https://user-images.githubusercontent.com/12814622/40585102-491d544c-61df-11e8-9b97-b19aaf65d383.jpg)\r\n\r\n\r\n\r\n值得注意的是，可执行文件的0xb0000 ~ 0xb1000区域在虚拟地址空间被映射了2次，分别对应虚拟地址空间的0x4b0000 ~ 0x4b10000和0x6b0000 ~ 0x6b1000，**然后这两部分虚拟地址空间最终映射到物理内存中的同一个页面**。\r\n\r\n**这样做的好处是增加空间使用率**。由于可执行程序0xb0000~0xb1000这一页数据同时包含了Segment0和Segment1中的section，如果将该页数据分成两部分，分别只包含一个Segment的数据，那么最终将被映射到两页物理内存，且这两页物理内存有效数据都少于页大小4096bytes，浪费内存。\r\n\r\n\r\n\r\n\r\n"}}
{"actor":{"display_login":"chenpengcong"}}