Executable and Linking Format,縮寫(xiě) ELF。是 Linux 系統(tǒng)目標(biāo)文件 (Object File) 格式。
主要有如下三種類(lèi)型:
(1) 可重定位文件 (relocatable file),可與其它目標(biāo)文件一起創(chuàng)建可執(zhí)行文件或共享目標(biāo)文件。
$ gcc -g -c hello.c
$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
(2) 可執(zhí)行文件 (executable file)。
$ gcc -g hello.c -o hello
$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared
libs), for GNU/Linux 2.6.15, not stripped
(3) 共享目標(biāo)文件 (shared object file),通常是 "函數(shù)庫(kù)",可靜態(tài)鏈接到其他 ELF 文件中,或動(dòng)態(tài)鏈接共同創(chuàng)建進(jìn)程映像 (類(lèi)似 DLL)。
$ gcc -shared -fpic stack.c -o hello.so
$ file hello.so
hello.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, not
stripped
我們可以從文件 (Linking) 和執(zhí)行 (Execution) 兩個(gè)角度審視 ELF 結(jié)構(gòu) (/usr/include/elf.h)。
和 Windows COFF 格式類(lèi)似,ELF 也有一個(gè)特定的文件頭,包括一個(gè)特定的標(biāo)志串 (Magic)。
文件頭中描述了 ELF 文件版本 (Version),目標(biāo)機(jī)器型號(hào) (Machine),程序入口地址 (Entry point Address) 等信息。緊接其后的是可選的程序頭表 (Program Header Table) 和多個(gè)段(Section),其中有我們所熟悉的存儲(chǔ)了執(zhí)行代碼的 .text 段。
ELF 使用段表 (Section Header Table) 存儲(chǔ)各段的相關(guān)信息,包括名稱(chēng)、起始位置、長(zhǎng)度、權(quán)限屬性等等。除了段表,ELF 中還有符號(hào)表 (Symbol Table)、字符串表 (String Table,段、函數(shù)等名稱(chēng)) 等。
Section 和 Segment 中文翻譯雖然都是 "段",但它們并不是一個(gè)意思。Section 主要是面向目標(biāo)文件連接器,而 Segment 則是面向執(zhí)行加載器,后者描述的是內(nèi)存布局結(jié)構(gòu)。本文主要分析 ELF 靜態(tài)文件格式,也就是說(shuō)主要跟 Section 打交道,而有關(guān) ELF 進(jìn)程及內(nèi)存布局模型將另文詳述。
相關(guān)分析將使用下面這個(gè)例子,如非說(shuō)明,所有生成文件都是32位。
$ cat hello.c
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello, World!\n");
return 0;
}
$ gcc -g -c hello.c
$ gcc -g hello.c -o hello
$ ls
hello.c hello.o hello
$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared
libs), for GNU/Linux 2.6.15, not stripped
附: ELF文件標(biāo)準(zhǔn)歷史
20世紀(jì)90年代,一些廠商聯(lián)合成立了一個(gè)委員會(huì),起草并發(fā)布了一個(gè) ELF 文件格式標(biāo)準(zhǔn)供公開(kāi)使用,并且希望所有人能夠遵循這項(xiàng)標(biāo)準(zhǔn)并且從中獲益。1993年,委員會(huì)發(fā)布了 ELF 文件標(biāo)準(zhǔn)。當(dāng)時(shí)參與該委員會(huì)的有來(lái)自于編譯器的廠商,如 Watcom 和 Borland;來(lái)自 CPU 的廠商如 IBM 和 Intel;來(lái)自操作系統(tǒng)的廠商如 IBM 和 Microsoft。1995年,委員會(huì)發(fā)布了 ELF1.2 標(biāo)準(zhǔn),自此委員會(huì)完成了自己的使命,不久就解散了。所以 ELF 最新版本為1.2。
我們先看看 elf.h 中的相關(guān)定義。
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Word;
typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Off;
#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;
總長(zhǎng)度是 52 (0x34) 字節(jié)。
$ xxd -g 1 -l 0x34 hello
0000000: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
0000010: 02 00 03 00 01 00 00 00 30 83 04 08 34 00 00 00 ........0...4...
0000020: 80 16 00 00 00 00 00 00 34 00 20 00 08 00 28 00 ........4. ...(.
0000030: 26 00 23 00 &.#.
我們可以借助 readelf 這個(gè)工具來(lái)查看詳細(xì)信息。
$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048330
Start of program headers: 52 (bytes into file)
Start of section headers: 5760 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 38
Section header string table index: 35
頭信息中,我們通常關(guān)注的是 Entry point address、Start of section headers。
$ objdump -dS hello | less
08048330 <_start>:
8048330: 31 ed xor %ebp,%ebp
8048332: 5e pop %esi
8048333: 89 e1 mov %esp,%ecx
8048335: 83 e4 f0 and $0xfffffff0,%esp
8048338: 50 push %eax
注意 Entry point address 指向 而非 mian(),我們?cè)倏纯炊伪硇畔ⅰ?/p>
$ readelf -S hello
There are 38 section headers, starting at offset 0x1680:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
[ 4] .hash HASH 0804818c 00018c 000028 04 A 6 0 4
[ 5] .gnu.hash GNU_HASH 080481b4 0001b4 000020 04 A 6 0 4
[ 6] .dynsym DYNSYM 080481d4 0001d4 000050 10 A 7 1 4
[ 7] .dynstr STRTAB 08048224 000224 00004a 00 A 0 0 1
[ 8] .gnu.version VERSYM 0804826e 00026e 00000a 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 08048278 000278 000020 00 A 7 1 4
[10] .rel.dyn REL 08048298 000298 000008 08 A 6 0 4
[11] .rel.plt REL 080482a0 0002a0 000018 08 A 6 13 4
[12] .init PROGBITS 080482b8 0002b8 000030 00 AX 0 0 4
[13] .plt PROGBITS 080482e8 0002e8 000040 04 AX 0 0 4
[14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
[15] .fini PROGBITS 0804849c 00049c 00001c 00 AX 0 0 4
[16] .rodata PROGBITS 080484b8 0004b8 000016 00 A 0 0 4
[17] .eh_frame PROGBITS 080484d0 0004d0 000004 00 A 0 0 4
[18] .ctors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049f1c 000f1c 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f20 000f20 0000d0 08 WA 7 0 4
[22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 08049ff4 000ff4 000018 04 WA 0 0 4
[24] .data PROGBITS 0804a00c 00100c 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a014 001014 000008 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001014 000046 01 MS 0 0 1
[27] .debug_aranges PROGBITS 00000000 001060 000040 00 0 0 8
[28] .debug_pubnames PROGBITS 00000000 0010a0 000040 00 0 0 1
[29] .debug_info PROGBITS 00000000 0010e0 0001ae 00 0 0 1
[30] .debug_abbrev PROGBITS 00000000 00128e 0000c3 00 0 0 1
[31] .debug_line PROGBITS 00000000 001351 0000ba 00 0 0 1
[32] .debug_frame PROGBITS 00000000 00140c 00002c 00 0 0 4
[33] .debug_str PROGBITS 00000000 001438 0000c6 01 MS 0 0 1
[34] .debug_loc PROGBITS 00000000 0014fe 00002c 00 0 0 1
[35] .shstrtab STRTAB 00000000 00152a 000156 00 0 0 1
[36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
[37] .strtab STRTAB 00000000 002110 000202 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
"starting at offset 0x1680" 轉(zhuǎn)換成十進(jìn)制就是5760。
程序頭表告訴系統(tǒng)如何建立一個(gè)進(jìn)程映象。
操作系統(tǒng)依據(jù)該表對(duì)進(jìn)程地址空間進(jìn)行分段 (Segment),并依據(jù)該表數(shù)據(jù)對(duì)進(jìn)程 "內(nèi)存段" 進(jìn)行屬性和權(quán)限管理。
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;
ELF 頭信息中已經(jīng)給出了 Program 的相關(guān)數(shù)據(jù),起始位置 52(0x34),數(shù)量8,每個(gè)頭信息長(zhǎng)度32(0x20) 字節(jié),總長(zhǎng)度 256(0x100) 字節(jié)。
$ readelf -h hello
ELF Header:
... ...
Start of program headers: 52 (bytes into file)
Size of program headers: 32 (bytes)
Number of program headers: 8
... ...
$ xxd -g 1 -s 0x34 -l 0x100 hello
0000034: 06 00 00 00 34 00 00 00 34 80 04 08 34 80 04 08 ....4...4...4...
0000044: 00 01 00 00 00 01 00 00 05 00 00 00 04 00 00 00 ................
0000054: 03 00 00 00 34 01 00 00 34 81 04 08 34 81 04 08 ....4...4...4...
0000064: 13 00 00 00 13 00 00 00 04 00 00 00 01 00 00 00 ................
0000074: 01 00 00 00 00 00 00 00 00 80 04 08 00 80 04 08 ................
0000084: d4 04 00 00 d4 04 00 00 05 00 00 00 00 10 00 00 ................
0000094: 01 00 00 00 0c 0f 00 00 0c 9f 04 08 0c 9f 04 08 ................
00000a4: 08 01 00 00 10 01 00 00 06 00 00 00 00 10 00 00 ................
00000b4: 02 00 00 00 20 0f 00 00 20 9f 04 08 20 9f 04 08 .... ... ... ...
00000c4: d0 00 00 00 d0 00 00 00 06 00 00 00 04 00 00 00 ................
00000d4: 04 00 00 00 48 01 00 00 48 81 04 08 48 81 04 08 ....H...H...H...
00000e4: 44 00 00 00 44 00 00 00 04 00 00 00 04 00 00 00 D...D...........
00000f4: 51 e5 74 64 00 00 00 00 00 00 00 00 00 00 00 00 Q.td............
0000104: 00 00 00 00 00 00 00 00 06 00 00 00 04 00 00 00 ................
0000114: 52 e5 74 64 0c 0f 00 00 0c 9f 04 08 0c 9f 04 08 R.td............
0000124: f4 00 00 00 f4 00 00 00 04 00 00 00 01 00 00 00 ................
從程序表數(shù)據(jù)中,我們可以從執(zhí)行角度來(lái)看操作系統(tǒng)如何映射 ELF 文件數(shù)據(jù) (Section to Segment mapping),如何確定各段 (Segment) 加載偏移量、內(nèi)存虛擬地址以及內(nèi)存屬性 (Flag)、對(duì)齊方式等信息。
$ readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x8048330
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x004d4 0x004d4 R E 0x1000
LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x00108 0x00110 RW 0x1000
DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f0c 0x08049f0c 0x08049f0c 0x000f4 0x000f4 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym ...
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06
07 .ctors .dtors .jcr .dynamic .got
分析 Section 之前,我們需要先了解 Section Header Table,因?yàn)槲覀冃枰ㄟ^(guò)它定位 Section,并獲知相關(guān)的屬性信息。
從 ELF Header 中我們可以獲知起始位置、單條記錄長(zhǎng)度、總記錄數(shù)以及存儲(chǔ)段名稱(chēng)字符串表的索引號(hào)信息。
$ readelf -h hello
ELF Header:
Start of section headers: 5760 (bytes into file)
Size of section headers: 40 (bytes)
Number of section headers: 38
Section header string table index: 35
elf.h 中對(duì) Section Header 的數(shù)據(jù)結(jié)構(gòu)定義:
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;
每條 Header 記錄是 40(0x28) 字節(jié)。我們對(duì)照著分析看看。
$ readelf -S hello
There are 38 section headers, starting at offset 0x1680:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
... ...
[35] .shstrtab STRTAB 00000000 00152a 000156 00 0 0 1
[36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
[37] .strtab STRTAB 00000000 002110 000202 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
$ xxd -g 1 -s 0x1680 -l 0x78 hello
0001680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0001690: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00016a0: 00 00 00 00 00 00 00 00 1b 00 00 00 01 00 00 00 ................
00016b0: 02 00 00 00 34 81 04 08 34 01 00 00 13 00 00 00 ....4...4.......
00016c0: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00016d0: 23 00 00 00 07 00 00 00 02 00 00 00 48 81 04 08 #...........H...
00016e0: 48 01 00 00 20 00 00 00 00 00 00 00 00 00 00 00 H... ...........
00016f0: 04 00 00 00 00 00 00 00 ........
Sections[0] 為空,我們就從 [1] .interp 開(kāi)始分析,跳過(guò)40個(gè)字節(jié),從 0x1680 + 0x28 = 0x16a8 開(kāi)始抓取數(shù)據(jù)。
$ xxd -g 1 -s 0x16a8 -l 0x28 hello
00016a8: 1b 00 00 00 01 00 00 00 02 00 00 00 34 81 04 08 ............4...
00016b8: 34 01 00 00 13 00 00 00 00 00 00 00 00 00 00 00 4...............
00016c8: 01 00 00 00 00 00 00 00 ........
從 elf.h 結(jié)構(gòu)定義中得知,前4個(gè)字節(jié)存儲(chǔ)了該段名稱(chēng)在字符串表中序號(hào)。
$ readelf -p .shstrtab hello ; 也可以使用索引號(hào) "readelf -p 35 hello"
String dump of section '.shstrtab':
[ 1] .symtab
[ 9] .strtab
[ 11] .shstrtab
[ 1b] .interp
[ 23] .note.ABI-tag
[ 31] .note.gnu.build-id
.. ...
很好,名稱(chēng)是 "[ 1b] .interp"。
sh_type(Section type) = 0x00000001 = SHT_PROGBITS。
#define SHT_PROGBITS 1 /* Program data */
sh_flags(Section flags) = 0x00000002 = SHF_ALLOC
#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */
sh_addr(virtual addr) = 0x08048134
sh_offset(Section file offset) = 0x00000134
sh_size(Section size) = 0x00000013
... ...
嗯相關(guān)信息和 readelf 輸出的都對(duì)上號(hào)了。
字符串表是以 "堆(Heap)" 的方式存儲(chǔ)的,也就是說(shuō) "序號(hào)" 實(shí)際上是字符串在該段的偏移位置。
$ readelf -x .shstrtab hello ; 或使用索引號(hào) "readelf -x 35 hello"
Hex dump of section '.shstrtab':
0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
0x00000010 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte
0x00000020 7270002e 6e6f7465 2e414249 2d746167 rp..note.ABI-tag
0x00000030 002e6e6f 74652e67 6e752e62 75696c64 ..note.gnu.build
... ...
我們數(shù)一下:
.symtab 序號(hào)是 1
.strtab 序號(hào)是 9
...
字符串以 "\0" 結(jié)尾,并以此來(lái)分割表中的多個(gè)字符串。
$ readelf -p .shstrtab hello
String dump of section '.shstrtab':
[ 1] .symtab
[ 9] .strtab
[ 11] .shstrtab
[ 1b] .interp
... ...
符號(hào)表記錄了程序中符號(hào)的定義信息和引用信息,它是一個(gè)結(jié)構(gòu)表,每條記錄對(duì)應(yīng)一個(gè)符號(hào)。
記錄中存儲(chǔ)了符號(hào)的名稱(chēng)、類(lèi)型、尺寸等信息,這些記錄可能對(duì)應(yīng)源代碼文件、結(jié)構(gòu)類(lèi)型、某個(gè)函數(shù)或者某個(gè)常變量。
當(dāng)我們調(diào)試程序時(shí),這些信息有助于我們快速定位問(wèn)題所在,我們可以使用符號(hào)信息設(shè)置斷點(diǎn),看到更易閱讀的反匯編代碼。
typedef uint16_t Elf32_Section;
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;
每條記錄的長(zhǎng)度是 16 (0xF) 字節(jié)。我們可以用 "readelf -s" 查看符號(hào)表詳細(xì)信息。
$ readelf -s hello
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
4: 080484bc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
Symbol table '.symtab' contains 74 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 08048134 0 SECTION LOCAL DEFAULT 1
... ...
35: 00000000 0 FILE LOCAL DEFAULT ABS init.c
36: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
37: 08049f0c 0 OBJECT LOCAL DEFAULT 18 __CTOR_LIST__
... ...
49: 00000000 0 FILE LOCAL DEFAULT ABS hello.c
50: 08049ff4 0 OBJECT LOCAL HIDDEN 23 _GLOBAL_OFFSET_TABLE_
... ...
72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main
73: 080482b8 0 FUNC GLOBAL DEFAULT 12 _init
我們看看 "symtab" 段具體的數(shù)據(jù)信息。符號(hào)表所需的字符串?dāng)?shù)據(jù)存儲(chǔ)在 .strtab 字符串表。
$ readelf -S hello
There are 38 section headers, starting at offset 0x1680:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
... ...
[36] .symtab SYMTAB 00000000 001c70 0004a0 10 37 54 4
[37] .strtab STRTAB 00000000 002110 000202 00 0 0 1
我們用 "72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main" 這條記錄來(lái)比對(duì)數(shù)據(jù)。
$ readelf -x .symtab hello
Hex dump of section '.symtab':
0x00000000 00000000 00000000 00000000 00000000 ................
0x00000010 00000000 34810408 00000000 03000100 ....4...........
0x00000020 00000000 48810408 00000000 03000200 ....H...........
0x00000030 00000000 68810408 00000000 03000300 ....h...........
0x00000040 00000000 8c810408 00000000 03000400 ................
0x00000050 00000000 b4810408 00000000 03000500 ................
... ...
0x00000410 9b010000 189f0408 00000000 11021300 ................
0x00000420 a8010000 10840408 5a000000 12000e00 ........Z.......
0x00000430 b8010000 14a00408 00000000 1000f1ff ................
0x00000440 c4010000 1ca00408 00000000 1000f1ff ................
0x00000450 c9010000 00000000 00000000 12000000 ................
0x00000460 d9010000 14a00408 00000000 1000f1ff ................
0x00000470 e0010000 6a840408 00000000 12020e00 ....j...........
0x00000480 f7010000 e4830408 1c000000 12000e00 ................
0x00000490 fc010000 b8820408 00000000 12000c00 ................
記錄長(zhǎng)度是16,整好一行,我們直接挑出所需的記錄 (72 * 16 = 0x480)。
0x00000480 f7010000 e4830408 1c000000 12000e00 ................
st_name(Symbol name) = 0x000001f7
st_value(Symbol value) = 0x080483e4
st_size(Symbol size) = 0x0000001c
st_info(Symbol type and binding) = 0x12
st_other(Symbol visibility) = 0x00
st_shndx(Section index) = 0x000e
首先從字符串表找出 Name。
$ readelf -p .strtab hello
String dump of section '.strtab':
[ 1] init.c
... ...
[ 1f7] main
[ 1fc] _init
elf.h 中的相關(guān)定義:
#define STT_FUNC 2 /* Symbol is a code object */
#define STB_GLOBAL 1 /* Global symbol */
#define STV_DEFAULT 0 /* Default symbol visibility rules */
整理一下:
st_name(Symbol name) = 0x000001f7 -> "main"
st_value(Symbol value) = 0x080483e4
st_size(Symbol size) = 0x0000001c -> 28
st_info(Symbol type and binding) = 0x12 -> 參考 elf 中的轉(zhuǎn)換公式
st_other(Symbol visibility) = 0x00 -> STV_DEFAULT
st_shndx(Section index) = 0x000e -> "[14] .text"
嘿嘿,對(duì)上號(hào)了。
我們還可以用 strip 命令刪除符號(hào)表 .symtab,這可以縮減文件尺寸。
$ ls -l hello
-rwxr-xr-x 1 yuhen yuhen 8978 2009-12-04 00:24 hello
$ strip hello
$ ls -l hello
-rwxr-xr-x 1 yuhen yuhen 5528 2009-12-04 20:27 hello
$ readelf -s hello ; .symtab 不見(jiàn)了
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
2: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.0 (2)
4: 080484bc 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
$ readelf -S hello ; Section 也少了很多
There are 28 section headers, starting at offset 0x1138:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
[ 4] .hash HASH 0804818c 00018c 000028 04 A 6 0 4
[ 5] .gnu.hash GNU_HASH 080481b4 0001b4 000020 04 A 6 0 4
[ 6] .dynsym DYNSYM 080481d4 0001d4 000050 10 A 7 1 4
[ 7] .dynstr STRTAB 08048224 000224 00004a 00 A 0 0 1
[ 8] .gnu.version VERSYM 0804826e 00026e 00000a 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 08048278 000278 000020 00 A 7 1 4
[10] .rel.dyn REL 08048298 000298 000008 08 A 6 0 4
[11] .rel.plt REL 080482a0 0002a0 000018 08 A 6 13 4
[12] .init PROGBITS 080482b8 0002b8 000030 00 AX 0 0 4
[13] .plt PROGBITS 080482e8 0002e8 000040 04 AX 0 0 4
[14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
[15] .fini PROGBITS 0804849c 00049c 00001c 00 AX 0 0 4
[16] .rodata PROGBITS 080484b8 0004b8 000016 00 A 0 0 4
[17] .eh_frame PROGBITS 080484d0 0004d0 000004 00 A 0 0 4
[18] .ctors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049f1c 000f1c 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f20 000f20 0000d0 08 WA 7 0 4
[22] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 08049ff4 000ff4 000018 04 WA 0 0 4
[24] .data PROGBITS 0804a00c 00100c 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a014 001014 000008 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001014 000046 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 00105a 0000de 00 0 0 1
.text 段中保存了所有函數(shù)的執(zhí)行代碼,我們看看 main 的反匯編代和 .text 數(shù)據(jù)對(duì)比。
$ objdump -d hello | less
080483e4 <main>:
80483e4: 55
80483e5: 89 e5
80483e7: 83 e4 f0
80483ea: 83 ec 10
80483ed: c7 04 24 c0 84 04 08
80483f4: e8 1f ff ff ff
80483f9: b8 00 00 00 00
80483fe: c9
80483ff: c3
08048400 <__libc_csu_fini>:
8048400: 55
8048401: 89 e5
8048403: 5d
8048404: c3
8048405: 8d 74 26 00
8048409: 8d bc 27 00 00 00 00
$ readelf -x .text hello
Hex dump of section '.text':
... ...
0x080483e0 ******** 5589e583 e4f083ec 10c70424
0x080483f0 c0840408 e81fffff ffb80000 0000c9c3
0x08048400 5589e55d c38d7426 008dbc27 00000000
... ...
通過(guò)對(duì)比數(shù)據(jù),我們會(huì)發(fā)現(xiàn) .text 段中只保存了所有函數(shù)機(jī)器碼,并沒(méi)有其他的信息,包括函數(shù)名稱(chēng)、起始位置等等。那么反編譯時(shí)如何確定某個(gè)函數(shù)的名稱(chēng)以及具體位置和長(zhǎng)度呢?這其實(shí)就是我們前面提到的符號(hào)表的作用了。
$ readelf -s hello
... ...
Symbol table '.symtab' contains 74 entries:
Num: Value Size Type Bind Vis Ndx Name
... ...
72: 080483e4 28 FUNC GLOBAL DEFAULT 14 main
... ...
Type = FUNC 表明該記錄是個(gè)函數(shù),起始位置就是 Value:080483e4,代碼長(zhǎng)度 28(0x1c) 字節(jié),存儲(chǔ)在索引號(hào)為14的段中。怎么樣?這回對(duì)上了吧。不過(guò)有個(gè)問(wèn)題,很顯然反編譯和符號(hào)表中給出的都是虛擬地址,我們?nèi)绾未_定代碼在文件中的實(shí)際位置呢?
公式:VA - Section Address + Offset = 實(shí)際文件中的位置
$ readelf -S hello
There are 38 section headers, starting at offset 0x1680:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[14] .text PROGBITS 08048330 000330 00016c 00 AX 0 0 16
... ...
0x080483e4 - 0x08048330 + 0x000330 = 0x3E4
驗(yàn)證一下。
$ xxd -g 1 -s 0x3e4 -l 0x1c hello
00003e4: 55 89 e5 83 e4 f0 83 ec 10 c7 04 24 c0 84 04 08
00003f4: e8 1f ff ff ff b8 00 00 00 00 c9 c3
如果用 strip 命令刪除了符號(hào)表,反匯編效果就比較悲慘了,都擠到 .text 段,正經(jīng)的入門(mén)級(jí)匯編編碼風(fēng)格啊。
$ strip hello
$ objdump -d hello | less
Disassembly of section .text:
... ...
08048330 <.text>:
8048330: 31 ed
8048332: 5e
8048333: 89 e1
... ...
80483e4: 55 ! ! ; 可憐的 <main> 就是從這嘎達(dá)開(kāi)始的
80483e5: 89 e5
80483e7: 83 e4 f0
80483ea: 83 ec 10
80483ed: c7 04 24 c0 84 04 08
80483f4: e8 1f ff ff ff
80483f9: b8 00 00 00 00
80483fe: c9
80483ff: c3
8048400: 55 ! ! ; 這家伙是 <__libc_csu_fini>
8048401: 89 e5
8048403: 5d
8048404: c3
8048405: 8d 74 26 00
8048409: 8d bc 27 00 00 00 00
... ...
.data 段包含了諸如全局變量、常量、靜態(tài)局部變量之類(lèi)的需要初始化的數(shù)據(jù),而 .rodata 段則包含代碼中的常量字符串(注意不是函數(shù)名這些符號(hào)名)等只讀數(shù)據(jù)。
.data 段是可寫(xiě)的,實(shí)際內(nèi)容是指針或常量值,而 .rodata 則是個(gè)只讀段。
為了查看實(shí)際效果,需要修改一下演示程序。
#include <stdio.h>
const char* format = "Hello, %s !\n";
char* name = "Q.yuhen";
int main(int argc, char* argv[])
{
printf(format, name);
return 0;
}
我們開(kāi)始分析編譯后的程序文件。
$ objdump -dS -M intel hello | less
... ...
080483e4 <main>:
const char* format = "Hello, %s !\n";
char* name = "Q.yuhen";
int main(int argc, char* argv[])
{
80483e4: push ebp
80483e5: mov ebp,esp
80483e7: and esp,0xfffffff0
80483ea: sub esp,0x10
printf(format, name);
80483ed: mov edx,DWORD PTR ds:0x804a018 ! ! ; 變量 name
80483f3: mov eax,ds:0x804a014 ! ! ! ; 常量 format
80483f8: mov DWORD PTR [esp+0x4],edx
80483fc: mov DWORD PTR [esp],eax
80483ff: call 804831c <printf@plt>
return 0;
8048404: mov eax,0x0
}
8048409: leave
804840a: ret
804840b: nop
804840c: nop
804840d: nop
804840e: nop
... ...
我們可以從符號(hào)表中找出相關(guān)的定義信息。通過(guò)對(duì)比,就可以知道匯編代碼中的虛擬地址的實(shí)際含義。
$ readelf -s hello
... ...
Symbol table '.symtab' contains 76 entries:
Num: Value Size Type Bind Vis Ndx Name
... ...
57: 0804a014 4 OBJECT GLOBAL DEFAULT 24 format
70: 0804a018 4 OBJECT GLOBAL DEFAULT 24 name
74: 080483e4 39 FUNC GLOBAL DEFAULT 14 main
... ...
$ readelf -S hello
There are 38 section headers, starting at offset 0x16f0:
Section Headers:
... ...
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[16] .rodata PROGBITS 080484c8 0004c8 00001d 00 A 0 0 4
[24] .data PROGBITS 0804a00c 00100c 000010 00 WA 0 0 4
... ...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
format 和 name 都存儲(chǔ)在 .data 段中,且該段是可寫(xiě)的,這表明該變量是多個(gè)棧共享。我們繼續(xù)看看 .data 段中具體內(nèi)容。
$ readelf -x .data hello
Hex dump of section '.data':
0x0804a00c 00000000 00000000 d0840408 dd840408 ................
從符號(hào)表的 Value 值我們可以看到:
[format] = 0x080484d0
[name] = 0x080484dd
.data 中存儲(chǔ)的指針顯然指向 .rodata 段 (0x080484c8 ~ 0x080484e5)。
$ readelf -x .rodata hello
Hex dump of section '.rodata':
0x080484c8 03000000 01000200 48656c6c 6f2c2025 ........Hello, %
0x080484d8 7320210a 00512e79 7568656e 00 s !..Q.yuhen.
.rodata 段果然也就是這些變量所指向的字符串。
.bss 段實(shí)際是執(zhí)行后才會(huì)啟用,并不占用文件空間 (下面 .bss 和 .comment Offset 相同),相關(guān)細(xì)節(jié)可參考 Linux/ELF 內(nèi)存布局分析之類(lèi)文章。
$ readelf -S hello
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[25] .bss NOBITS 0804a01c 00101c 000008 00 WA 0 0 4
[26] .comment PROGBITS 00000000 00101c 000046 01 MS 0 0 1
... ...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
$ readelf -x .bss hello
Section '.bss' has no data to dump.
ELF 中我們常打交道的幾個(gè)段:
下圖是一個(gè)簡(jiǎn)易的內(nèi)存模型示意圖。其中某些段 (Segment) 是從可執(zhí)行文件加載的,有關(guān) ELFSection 和 Segment 的映射關(guān)系,我們可以從 ELF Program Headers 中獲取相關(guān)信息。
$ readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x8048410
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
LOAD 0x000000 0x08048000 0x08048000 0x0064c 0x0064c R E 0x1000
LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x0011c 0x00128 RW 0x1000
DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0x4
NOTE 0x000148 0x08048148 0x08048148 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f0c 0x08049f0c 0x08049f0c 0x000f4 0x000f4 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 ... .init .plt .text .fini .rodata
03 ... .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06
07 .ctors .dtors .jcr .dynamic .got
對(duì)照示意圖,我們可以看到 .text, .rodata, .data, .bss 被加載到 0x08048000 之后,也就是序號(hào)02, 03兩個(gè) LOAD Segemtn 段中。ELF Section 信息中的 Virtual Address 也是一個(gè)參考。
$ readelf -S hello
There are 38 section headers, starting at offset 0x1a10:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[14] .text PROGBITS 08048410 000410 0001ec 00 AX 0 0 16
[16] .rodata PROGBITS 08048618 000618 000030 00 A 0 0 4
[24] .data PROGBITS 0804a018 001018 000010 00 WA 0 0 4
[25] .bss NOBITS 0804a028 001028 00000c 00 WA 0 0 4
[35] .shstrtab STRTAB 00000000 0018b8 000156 00 0 0 1
[36] .symtab SYMTAB 00000000 002000 000540 10 37 56 4
[37] .strtab STRTAB 00000000 002540 000263 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
注意不是所有的 Section 都會(huì)被加載到進(jìn)程內(nèi)存空間。
查看進(jìn)程運(yùn)行時(shí)內(nèi)存信息:
(1) pmap
$ ps aux | grep hello | grep -v grep
yuhen 6649 0.0 1.6 39692 8404 pts/0 Sl+ Dec10 0:13 vim hello.c
yuhen 12787 0.0 0.0 1664 396 pts/1 S+ 08:24 0:00 ./hello
$ pmap -x 12787
12787: ./hello
Address Kbytes RSS Anon Locked Mode Mapping
00110000 1272 - - - r-x-- libc-2.10.1.so
0024e000 8 - - - r---- libc-2.10.1.so
00250000 4 - - - rw--- libc-2.10.1.so
00251000 12 - - - rw--- [ anon ]
002b2000 108 - - - r-x-- ld-2.10.1.so
002cd000 4 - - - r---- ld-2.10.1.so
002ce000 4 - - - rw--- ld-2.10.1.so
00c4d000 4 - - - r-x-- [ anon ]
08048000 4 - - - r-x-- hello
08049000 4 - - - r---- hello
94
0804a000 4 - - - rw--- hello
09f89000 132 - - - rw--- [ anon ]
b7848000 4 - - - rw--- [ anon ]
b7855000 16 - - - rw--- [ anon ]
bfc40000 84 - - - rw--- [ stack ]
-------- ------- ------- ------- -------
total kB 1664 - - -
(2) maps
$ cat /proc/12787/maps
00110000-0024e000 r-xp 00000000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
0024e000-00250000 r--p 0013e000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
00250000-00251000 rw-p 00140000 08:01 5231 /lib/tls/i686/cmov/libc-2.10.1.so
00251000-00254000 rw-p 00000000 00:00 0
002b2000-002cd000 r-xp 00000000 08:01 1809 /lib/ld-2.10.1.so
002cd000-002ce000 r--p 0001a000 08:01 1809 /lib/ld-2.10.1.so
002ce000-002cf000 rw-p 0001b000 08:01 1809 /lib/ld-2.10.1.so
00c4d000-00c4e000 r-xp 00000000 00:00 0 [vdso]
08048000-08049000 r-xp 00000000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
08049000-0804a000 r--p 00000000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
0804a000-0804b000 rw-p 00001000 08:01 135411 /home/yuhen/Projects/Learn.C/hello
09f89000-09faa000 rw-p 00000000 00:00 0 [heap]
b7848000-b7849000 rw-p 00000000 00:00 0
b7855000-b7859000 rw-p 00000000 00:00 0
bfc40000-bfc55000 rw-p 00000000 00:00 0 [stack]
(3) gdb
$ gdb --pid=12787
(gdb) info proc mappings
process 12619
cmdline = '/home/yuhen/Projects/Learn.C/hello'
cwd = '/home/yuhen/Projects/Learn.C'
exe = '/home/yuhen/Projects/Learn.C/hello'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
... ...
0x8048000 0x8049000 0x1000 0 /home/yuhen/Projects/Learn.C/hello
0x8049000 0x804a000 0x1000 0 /home/yuhen/Projects/Learn.C/hello
0x804a000 0x804b000 0x1000 0x1000 /home/yuhen/Projects/Learn.C/hello
0x9f89000 0x9faa000 0x21000 0 [heap]
0xb7848000 0xb7849000 0x1000 0
0xb7855000 0xb7859000 0x4000 0
0xbfc40000 0xbfc55000 0x15000 0 [stack]
接下來(lái)我們分析不同生存周期變量在進(jìn)程空間的位置。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
int x = 0x1234;
char *s;
int test()
{
static int a = 0x4567;
static int b;
return ++a;
}
int main(int argc, char* argv[])
{
int i = test() + x;
s = "Hello, World!";
char* p = malloc(10);
return EXIT_SUCCESS;
}
在分析 ELF 文件結(jié)構(gòu)時(shí)我們就已經(jīng)知道全局變量和靜態(tài)局部變量在編譯期就決定了其內(nèi)存地址。
$ readelf -s hello
Symbol table '.symtab' contains 79 entries:
Num: Value Size Type Bind Vis Ndx Name
... ...
50: 0804a018 4 OBJECT LOCAL DEFAULT 24 a.2344
51: 0804a024 4 OBJECT LOCAL DEFAULT 25 b.2345
57: 0804a028 4 OBJECT GLOBAL DEFAULT 25 s
65: 0804a014 4 OBJECT GLOBAL DEFAULT 24 x
... ...
$ readelf -S hello
There are 38 section headers, starting at offset 0x1a10:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[16] .rodata PROGBITS 080484f8 0004f8 000016 00 A 0 0 4
[24] .data PROGBITS 0804a00c 00100c 000010 00 WA 0 0 4
[25] .bss NOBITS 0804a01c 00101c 000010 00 WA 0 0 4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
通過(guò)對(duì)比相關(guān)段,我們可確定已初始化的全局和靜態(tài)變量被分配在 .data 中,而未初始化全局和靜態(tài)變量則分配在 .bss。
.data 0804a00c ~ 0804a01b : x(0804a014), a(0804a018),
.bss 0804a01c ~ 0804a02b : b(0804a024), s(0804a028)
而代碼中的字符串 "Hello, World!" 被分配在 .rodata 中。
$ readelf -p .rodata hello
String dump of section '.rodata':
[ 8] Hello, World!
$ readelf -x .rodata hello
Hex dump of section '.rodata':
0x080484f8 03000000 01000200 48656c6c 6f2c2057 ........Hello, W
0x08048508 6f726c64 2100 orld!.
可以用反匯編代碼驗(yàn)證一下。
$ objdump -dS -M intel hello | less
int x = 0x1234;
char *s;
int test()
{
80483e4: push ebp
80483e5: mov ebp,esp
static int a = 0x4567;
static int b;
return ++a;
80483e7: mov eax,ds:0x804a018 ! ! ; 靜態(tài)變量 a
80483ec: add eax,0x1 ! ! ; 計(jì)算 (eax) = (eax) + 1
80483ef: mov ds:0x804a018,eax ! ! ; 將結(jié)果存回 a
80483f4: mov eax,ds:0x804a018
}
int main(int argc, char* argv[])
{
int i = test() + x;
8048404: call 80483e4 <test> ! ; test() 返回值被存入 eax
8048409: mov edx,DWORD PTR ds:0x804a014 ! ; 將全局變量 x 值放入 edx
804840f: add eax,edx ! ; 計(jì)算 (eax) = test() + x
8048411: mov DWORD PTR [esp+0x1c],eax ! ; 局部變量 i = (eax), 顯然 i 在棧分配
s = "Hello, World!";
8048415: mov DWORD PTR ds:0x804a028,0x8048500 ; 將 "Hello..." 地址復(fù)制給 s
... ...
char* p = malloc(10);
804841f: mov DWORD PTR [esp],0xa
8048426: call 804831c <malloc@plt>
804842b: mov DWORD PTR [esp+0x18],eax
return EXIT_SUCCESS;
804842f: mov eax,0x0
}
也可以用 gdb 查看運(yùn)行期分配狀態(tài)。
(gdb) p &i ! ! ! ; main() 局部變量 i 地址
$1 = (int *) 0xbffff74c
(gdb) p p ! ! ! ; malloc 返回空間指針 p
$2 = 0x804b008 ""
(gdb) info proc mappings
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x804b000 0x806c000 0x21000 0 [heap]
0xbffeb000 0xc0000000 0x15000 0 [stack]
很顯然,局部變量 i 分配在 Stack,而 malloc p 則是在 Heap 上分配。
程序總免不了要崩潰的…… 這是常態(tài),要淡定!
利用 setrlimit 函數(shù)我們可以將 "core file size" 設(shè)置成一個(gè)非0值,這樣就可以在崩潰時(shí)自動(dòng)生成 core 文件了。(可參考 bshell ulimit 命令)
#include <sys/resource.h>
void test()
{
char* s = "abc";
*s = 'x';
}
int main(int argc, char** argv)
{
struct rlimit res = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY };
setrlimit(RLIMIT_CORE, &res);
test();
return (EXIT_SUCCESS);
}
很顯然,我們?cè)?test 函數(shù)中特意制造了一個(gè) "Segmentation fault",執(zhí)行一下看看效果。
$ ./test
Segmentation fault (core dumped)
$ ls -l
total 104
-rw------- 1 yuhen yuhen 172032 2010-01-14 20:59 core
-rwxr-xr-x 1 yuhen yuhen 9918 2010-01-14 20:53 test
線程對(duì)于 Linux 內(nèi)核來(lái)說(shuō)就是一種特殊的 "輕量級(jí)進(jìn)程"。如同 fork 處理子進(jìn)程一樣,當(dāng)線程結(jié)束時(shí),它會(huì)維持一個(gè)最小現(xiàn)場(chǎng),其中保存有退出狀態(tài)等資源,以便主線程或其他線程調(diào)用 thread_join 獲取這些信息。如果我們不處理這個(gè)現(xiàn)場(chǎng),那么就會(huì)發(fā)生內(nèi)存泄露。
void* test(void* arg)
{
printf("%s\n", (char*)arg);
return (void*)0;
}
int main(int argc, char** argv)
{
pthread_t tid;
pthread_create(&tid, NULL, test, "a");
sleep(3);
return (EXIT_SUCCESS);
}
編譯后,我們用 Valgrind 檢測(cè)一下。
$ valgrind --leak-check=full ./test
==11224== Memcheck, a memory error detector
==11224== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==11224== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==11224== Command: ./test
==11224==
a
==11224==
==11224== HEAP SUMMARY:
==11224== in use at exit: 136 bytes in 1 blocks
==11224== total heap usage: 1 allocs, 0 frees, 136 bytes allocated
==11224==
==11224== 136 bytes in 1 blocks are possibly lost in loss record 1 of 1
==11224== at 0x4023F5B: calloc (vg_replace_malloc.c:418)
==11224== by 0x40109AB: _dl_allocate_tls (dl-tls.c:300)
==11224== by 0x403F102: pthread_create@@GLIBC_2.1 (allocatestack.c:561)
==11224== by 0x80484F8: main (main.c:51)
==11224==
==11224== LEAK SUMMARY:
==11224== definitely lost: 0 bytes in 0 blocks
==11224== indirectly lost: 0 bytes in 0 blocks
==11224== possibly lost: 136 bytes in 1 blocks
==11224== still reachable: 0 bytes in 0 blocks
==11224== suppressed: 0 bytes in 0 blocks
==11224==
==11224== For counts of detected and suppressed errors, rerun with: -v
==11224== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 8)
結(jié)果報(bào)告顯示 pthread_create 發(fā)生內(nèi)存泄露。
我們?cè)囍{(diào)用 pthread_join 獲取線程狀態(tài),看看內(nèi)核是否會(huì)回收這個(gè)被泄露的線程遺留內(nèi)存。
void* test(void* arg)
{
printf("%s\n", (char*)arg);
return (void*)123;
}
int main(int argc, char** argv)
{
pthread_t tid;
pthread_create(&tid, NULL, test, "a");
void* state;
pthread_join(tid, &state);
printf("%d\n", (int)state);
return (EXIT_SUCCESS);
}
$ valgrind --leak-check=full ./test
==11802== Memcheck, a memory error detector
==11802== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==11802== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==11802== Command: ./test
==11802==
a
123
==11802==
==11802== HEAP SUMMARY:
==11802== in use at exit: 0 bytes in 0 blocks
==11802== total heap usage: 1 allocs, 1 frees, 136 bytes allocated
==11802==
==11802== All heap blocks were freed -- no leaks are possible
==11802==
==11802== For counts of detected and suppressed errors, rerun with: -v
==11802== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 8)
這次檢測(cè)結(jié)果表明,內(nèi)存不再泄露??梢?jiàn) pthread_join 和 waitpid 之類(lèi)的函數(shù)作用類(lèi)似,就是獲取狀態(tài),并通知內(nèi)核完全回收相關(guān)內(nèi)存區(qū)域。
在實(shí)際開(kāi)發(fā)中,我們并不是總要關(guān)心線程的退出狀態(tài)。例如異步調(diào)用,主線程只需建立線程,然后繼續(xù)自己的任務(wù)。這種狀況下,我們可以為用 "分離線程 (detach)" 來(lái)通知內(nèi)核無(wú)需維持狀態(tài)線程,直接回收全部?jī)?nèi)存。
可以調(diào)用 pthread_detach 函數(shù)分離線程。
int main(int argc, char** argv)
{
pthread_t tid;
pthread_create(&tid, NULL, test, "a");
pthread_detach(tid);
sleep(3);
return (EXIT_SUCCESS);
}
當(dāng)然,也可以在 thread function 中調(diào)用。
void* test(void* arg)
{
printf("%s\n", (char*)arg);
pthread_detach(pthread_self());
return NULL;
}
或者使用線程屬性。
int main(int argc, char** argv)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t tid;
pthread_create(&tid, &attr, test, "a");
sleep(3);
pthread_attr_destroy(&attr);
return (EXIT_SUCCESS);
}
根據(jù)編碼需要,任選其一即可。
pthread_cancel 是個(gè)危險(xiǎn)的東東,天知道會(huì)在哪旮旯停掉線程。
void* test(void* arg)
{
for (int i = 0; i < 10; i++)
{
printf("start: %d; ", i);
sleep(1);
printf("end: %d\n", i);
}
return (void*)0;
}
int main(int argc, char* argv[])
{
// 創(chuàng)建線程
pthread_t tid;
pthread_create(&tid, NULL, test, NULL);
// 3秒后取消線程
sleep(3);
pthread_cancel(tid);
// 釋放資源
void* ret;
pthread_join(tid, &ret);
if ((int)ret != 0) fprintf(stderr, "cancel!\n");
return EXIT_SUCCESS;
}
假設(shè)以下三行構(gòu)成一個(gè)完整的事務(wù)邏輯。
printf("start: %d; ", i);
sleep(1);
printf("end: %d\n", i);
那么執(zhí)行的結(jié)果可能是這樣。
$ ./test
start: 0; end: 0
start: 1; end: 1
start: 2;
cancel!
"end: 2" 不見(jiàn)了,如果是真實(shí)的業(yè)務(wù)邏輯可能會(huì)惹出大麻煩。原因是因?yàn)楸纠械?"sleep(1)" 是一個(gè) "取消點(diǎn)",類(lèi)似的函數(shù)含有很多,天知道會(huì)有什么道理可講。
當(dāng)對(duì)某個(gè)線程調(diào)用 thread_cancel 時(shí),會(huì)將線程設(shè)置成 "未決狀態(tài)",當(dāng)執(zhí)行到 "取消點(diǎn)" 時(shí)就會(huì)終止線程??梢钥紤]用 pthread_setcancelstate 將線程從默認(rèn)的 PTHREAD_CANCEL_ENABLED 改成PTHREAD_CANCEL_DISABLE,等我們的邏輯執(zhí)行完成后再改回。
void* test(void* arg)
{
for (int i = 0; i < 10; i++)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
printf("start: %d; ", i);
sleep(1);
printf("end: %d\n", i);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
}
return (void*)0;
}
這回搞定了,下面是輸出效果。注意當(dāng)我們改回 PTHREAD_CANCEL_ENABLE 后,僅表示該線程可以被中斷了,還需要調(diào)用 pthread_testcancel 來(lái)完成這次中斷。
$ ./test
start: 0; end: 0
start: 1; end: 1
start: 2; end: 2
cancel!
就算我們?cè)谕瓿梢淮瓮暾壿嫼蟛涣⒓锤幕?PTHREAD_CANCEL_ENABLE,就算后續(xù)循環(huán)再次調(diào)用PTHREAD_CANCEL_DISABLE 設(shè)置,其 "未決狀態(tài)" 依然會(huì)保留的。因此我們寫(xiě)成下面這樣。
void* test(void* arg)
{
for (int i = 0; i < 10; i++)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
printf("start: %d; ", i);
sleep(1);
printf("end: %d\n", i);
if (i > 7)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
}
}
return (void*)0;
}
輸出:
$ ./test
start: 0; end: 0
start: 1; end: 1
start: 2; end: 2
start: 3; end: 3
start: 4; end: 4
start: 5; end: 5
start: 6; end: 6
start: 7; end: 7
start: 8; end: 8
cancel!
建議開(kāi)發(fā)中總是設(shè)置 PTHREAD_MUTEX_RECURSIVE 屬性,避免死鎖。
pthread_mutex_t mutex;
pthread_cond_t cond;
void* test(void* arg)
{
pthread_mutex_lock(&mutex);
for (int i = 0; i < 10; i++)
{
printf("T1: %d\n", i);
sleep(1);
// 釋放鎖,等待信號(hào)后再次加鎖繼續(xù)執(zhí)行。
if (i == 5) pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
return (void*)0;
}
void* test2(void* arg)
{
// 多次加鎖
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
for (int i = 0; i < 10; i++)
{
printf("T2: %d\n", i);
sleep(1);
}
// 發(fā)送信號(hào)
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex);
return (void*)0;
}
int main(int argc, char* argv[])
{
// 線程屬性: 分離
pthread_attr_t p_attr;
pthread_attr_init(&p_attr);
pthread_attr_setdetachstate(&p_attr, PTHREAD_CREATE_DETACHED);
// 互斥量屬性: 同一線程可多次加鎖
pthread_mutexattr_t m_attr;
pthread_mutexattr_init(&m_attr);
pthread_mutexattr_settype(&m_attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化
pthread_mutex_init(&mutex, &m_attr);
pthread_cond_init(&cond, NULL);
// 創(chuàng)建線程
pthread_t tid1, tid2;
pthread_create(&tid1, &p_attr, test, NULL);
sleep(1);
pthread_create(&tid2, &p_attr, test2, NULL);
// 釋放
pthread_attr_destroy(&p_attr);
pthread_mutexattr_destroy(&m_attr);
sleep(30);
return EXIT_SUCCESS;
}
輸出:
$ ./test
T1: 0
T1: 1
T1: 2
T1: 3
T1: 4
T1: 5 ----> 釋放鎖,開(kāi)始等待信號(hào)。
T2: 0 ----> 2 號(hào)線程獲得鎖開(kāi)始執(zhí)行。
T2: 1
T2: 2
T2: 3
T2: 4
T2: 5
T2: 6
T2: 7
T2: 8
T2: 9 ----> 發(fā)送信號(hào)后,釋放鎖。
T1: 6 ----> 1 號(hào)線程開(kāi)始執(zhí)行。
T1: 7
T1: 8
T1: 9
利用信號(hào)在進(jìn)程間傳送數(shù)據(jù),可以是 int 類(lèi)型的標(biāo)志,或者某個(gè)共享內(nèi)存的地址。為了便于閱讀,下面代碼中對(duì)函數(shù)返回值的判斷被刪除了……
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
void sig_test(int signo, siginfo_t* si, void* p)
{
// 終于等到子進(jìn)程送盒飯過(guò)來(lái)了,除了整數(shù)外,還可以是共享內(nèi)存的地址。
printf("signo:%d pid:%d value:%d\n", signo, si->si_pid, si->si_int);
}
void parent()
{
// 讓子進(jìn)程退出后自動(dòng)回收,避免成為僵尸或者需要父進(jìn)程 wait。
struct sigaction sat_cld = { .sa_handler = SIG_IGN, .sa_flags = SA_NOCLDWAIT };
sigaction(SIGCHLD, &sat_cld, NULL);
// 注冊(cè)信號(hào)處理程序
struct sigaction sat_usr = { .sa_flags = SA_SIGINFO, .sa_sigaction = sig_test };
sigaction(SIGUSR1, &sat_usr, NULL);
// 父進(jìn)程該干嘛干嘛,作為示例只好無(wú)聊地坐看風(fēng)起云滅。
while(true) pause();
}
void child()
{
if (fork() == 0)
{
// 休息一下,等父進(jìn)程完成信號(hào)處理程序注冊(cè)。
sleep(1);
for (int i = 0; i < 10; i++)
{
// 發(fā)送附加數(shù)據(jù)的信號(hào),也可以發(fā)送某個(gè)共享內(nèi)存的地址。
sigqueue(getppid(), SIGUSR1, (union sigval){ .sival_int = i });
// 間隔一下,連續(xù)發(fā)送會(huì)導(dǎo)致失敗。
usleep(1);
}
// 子進(jìn)程退出
exit(EXIT_SUCCESS);
}
}
int main(int argc, char* argv[])
{
child();
parent();
return EXIT_SUCCESS;
}
內(nèi)核可能在任何時(shí)候暫停正在執(zhí)行的函數(shù),然后執(zhí)行信號(hào)服務(wù)程序?;诎踩仍?,我們可能需要阻塞這種行為,確保我們的關(guān)鍵邏輯被完整執(zhí)行。
先看一個(gè)非阻塞版本。
void sig_handler(int signo)
{
printf("signal: %d\n", signo);
}
void test()
{
for (int i = 0; i < 10; i++)
{
printf("%s: %d\n", __func__, i);
sleep(1);
}
}
void child()
{
if (fork() == 0)
{
sleep(1);
for (int i = 0; i < 10; i++)
{
if (kill(getppid(), SIGUSR1) == -1) perror("kill");
sleep(1);
}
exit(EXIT_SUCCESS);
}
}
int main(int argc, char* argv[])
{
signal(SIGUSR1, sig_handler);
child();
test();
return EXIT_SUCCESS;
}
編譯執(zhí)行,很顯然 test 函數(shù)多次被信號(hào)中斷。
$ ./test
test: 0
signal: 10
test: 1
signal: 10
test: 2
signal: 10
test: 3
signal: 10
test: 4
signal: 10
test: 5
signal: 10
test: 6
signal: 10
test: 7
signal: 10
test: 8
signal: 10
test: 9
signal: 10
通過(guò) sigprocmask 函數(shù)我們可以實(shí)現(xiàn)自己的不可重入函數(shù)。
void test()
{
sigset_t set,;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
for (int i = 0; i < 10; i++)
{
printf("%s: %d\n", __func__, i);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
編譯執(zhí)行,信號(hào)被阻塞。同時(shí)要注意這期間相同的信號(hào)只有一個(gè)在排隊(duì) (Linux)。
$ ./test
test: 0
test: 1
test: 2
test: 3
test: 4
test: 5
test: 6
test: 7
test: 8
test: 9
signal: 10
我們還以用 sigfillset 來(lái)阻擋除 SIGKILL 和 SIGSTOP 之外的所有信號(hào)。
void test()
{
sigset_t set, oldset;
sigemptyset(&set);
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, &oldset);
for (int i = 0; i < 10; i++)
{
printf("%s: %d\n", __func__, i);
sleep(1);
}
sigprocmask(SIG_SETMASK, &oldset, NULL);
}
你可以試試在運(yùn)行期間按 + C 試試。
在寫(xiě)多進(jìn)程服務(wù)程序的時(shí)候,免不了要處理僵尸進(jìn)程。為了讓服務(wù)程序長(zhǎng)時(shí)間正常運(yùn)轉(zhuǎn),我們需要有些過(guò)硬的功夫?qū)Ω哆@些賴(lài)著不走的死鬼們。哦,對(duì)了,先看看僵尸啥樣。
int main(int argc, char* argv[])
{
for (int i = 0; i < 10; i++)
{
pid_t child = fork();
if (child == 0)
{
printf("child: %d, parent: %d\n", getpid(), getppid());
exit(EXIT_SUCCESS);
}
else if (child == -1)
{
perror("fork");
}
}
while(true) pause();
return EXIT_SUCCESS;
}
編譯執(zhí)行。
$ ./test
child: 2038, parent: 2035
child: 2039, parent: 2035
child: 2040, parent: 2035
child: 2041, parent: 2035
child: 2042, parent: 2035
child: 2043, parent: 2035
child: 2037, parent: 2035
child: 2036, parent: 2035
child: 2044, parent: 2035
child: 2045, parent: 2035
^Z
[2]+ Stopped ./test
$ ps aux | grep test
yuhen 2035 0.0 0.0 1632 376 pts/0 T 17:32 0:00 ./test
yuhen 2036 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2037 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2038 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2039 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2040 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2041 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2042 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2043 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2044 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
yuhen 2045 0.0 0.0 0 0 pts/0 Z 17:32 0:00 [test] <defunct>
好多僵尸啊。子進(jìn)程退出時(shí)會(huì)保留一個(gè)最小現(xiàn)場(chǎng),其中有退出狀態(tài)碼等東東,等待父進(jìn)程查詢(xún)。默認(rèn)情況下,我們需要在父進(jìn)程用 wait / waitpid 之類(lèi)的函數(shù)進(jìn)行處理后,僵尸才會(huì)消失。但在實(shí)際開(kāi)發(fā)中,我們并不總是需要獲知子進(jìn)程的結(jié)束狀態(tài)。
從下面的兵器中選一把,準(zhǔn)備殺僵尸吧。
也就是所謂兩次 fork 調(diào)用,主進(jìn)程并不直接創(chuàng)建目標(biāo)子進(jìn)程,而是通過(guò)創(chuàng)建一個(gè) Son,然后再由Son 創(chuàng)建實(shí)際的目標(biāo)子進(jìn)程 Grandson。Son 在創(chuàng)建 Grandson 后立即返回,并由主進(jìn)程 waitpid回收掉。而真正的目標(biāo) Grandson 則因?yàn)?"生父" Son 死掉而被 init 收養(yǎng),然后直接被人道毀滅。
void create_child()
{
pid_t son = fork();
if (son == 0)
{
pid_t grandson = fork();
if (grandson == 0)
{
printf("child: %d, parent: %d\n", getpid(), getppid());
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
else if (son > 0)
{
waitpid(son, NULL, 0);
}
else
{
perror("fork");
}
}
int main(int argc, char* argv[])
{
for (int i = 0; i < 10; i++)
{
create_child();
}
while(true) pause();
return EXIT_SUCCESS;
}
父進(jìn)程注冊(cè) SIGCHLD 信號(hào)處理程序來(lái)完成異步 wait 回收操作。
void create_child()
{
pid_t son = fork();
if (son == 0)
{
printf("child: %d, parent: %d\n", getpid(), getppid());
exit(EXIT_SUCCESS);
}
else if (son == -1)
{
perror("fork");
}
}
void sig_child(int signo)
{
if (signo != SIGCHLD) return;
int status;
pid_t pid = wait(&status);
printf("child %d exited!\n", pid);
}
int main(int argc, char* argv[])
{
signal(SIGCHLD, sig_child);
for (int i = 0; i < 10; i++)
{
create_child();
}
while(true) pause();
return EXIT_SUCCESS;
}
同樣是信號(hào),但區(qū)別在于提前告知內(nèi)核:別等我了,直接殺了吧。
void create_child()
{
pid_t son = fork();
if (son == 0)
{
printf("child: %d, parent: %d\n", getpid(), getppid());
exit(EXIT_SUCCESS);
}
else if (son == -1)
{
perror("fork");
}
}
int main(int argc, char* argv[])
{
struct sigaction act_nowait = { .sa_handler = SIG_IGN, .sa_flags = SA_NOCLDWAIT};
sigaction(SIGCHLD, &act_nowait, NULL);
for (int i = 0; i < 10; i++)
{
create_child();
}
while(true) pause();
return EXIT_SUCCESS;
}
在運(yùn)行時(shí)動(dòng)態(tài)載入庫(kù) (.so),并調(diào)用其中的函數(shù)。
我們調(diào)用的目標(biāo)函數(shù)就是 testfunc。
mylib.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void testfunc(const char* s, int x)
{
printf("testfunc call.\n");
printf("%s, %d\n", s, x);
}
編譯成動(dòng)態(tài)庫(kù)。
$ gcc -fPIC -shared -o libmy.so mylib.c
$ nm libmy.so
... ...
000004ac T testfunc
符號(hào)表中包含了目標(biāo)函數(shù)名稱(chēng)。
我們需要的函數(shù)在 "dlfcn.h" 中可以找到。相關(guān)函數(shù)信息可以通過(guò) "man 3 dlopen" 查詢(xún)。
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <dlfcn.h>
int main(int argc, char* argv[])
{
// 載入并返回動(dòng)態(tài)庫(kù)句柄
void* handle = dlopen("./libmy.so", RTLD_LAZY);
// 如果句柄為 NULL,打印出錯(cuò)信息
if (!handle)
{
fprintf(stderr, "error1: %s\n", dlerror());
return EXIT_FAILURE;
}
// 聲明函數(shù)指針
void (*func)(const char*, int);
// 通過(guò)符號(hào)名稱(chēng)返回函數(shù)指針
func = dlsym(handle, "testfunc");
// 如果 dlerror() 結(jié)果不為 NULL 表示出錯(cuò)
char* error = dlerror();
if (error)
{
fprintf(stderr, "error2: %s\n", error);
return EXIT_FAILURE;
}
// 調(diào)用函數(shù)
func("Hello, Dynamic Library!", 1234);
// 關(guān)閉動(dòng)態(tài)庫(kù)
dlclose(handle);
return EXIT_SUCCESS;
}
編譯并測(cè)試。
$ gcc -g -o test -ldl main.c
$ ./test
testfunc call.
Hello, Dynamic Library!, 1234
注意添加 "-ldl" 編譯參數(shù)。
不要嫌單元測(cè)試麻煩,從長(zhǎng)期看,這個(gè)投資是非常值得的。CUnit 和其孿生兄弟們的使用方法并沒(méi)有多大差異,寫(xiě)起來(lái)很簡(jiǎn)單。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <CUnit/Basic.h>
/*---待測(cè)試函數(shù)-----------------------------------------------*/
int max(int a, int b)
{
return a > b a : b;
}
/*---單元測(cè)試--------------------------------------------------*/
static int init()
{
return 0;
}
static int cleanup()
{
return 0;
}
static void test_max()
{
CU_ASSERT_EQUAL(3, max(1, 3));
CU_ASSERT_NOT_EQUAL(1, max(1, 3));
}
void utest()
{
CU_initialize_registry();
CU_pSuite suite1 = CU_add_suite("my suite1", init, cleanup);
CU_add_test(suite1, "max", test_max);
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
CU_cleanup_registry();
}
/*-------------------------------------------------------------*/
int main(int argc, char* argv[])
{
utest();
return EXIT_SUCCESS;
}
輸出:
Suite: my suite1
Test: max ... passed
--Run Summary: Type Total Ran Passed Failed
suites 1 1 n/a 0
tests 1 1 1 0
asserts 2 2 2 0
CUnit 提供了大量的 CUASSERT 測(cè)試宏方便我們對(duì)測(cè)試結(jié)果做出判斷。每個(gè) Suite 可以指定 init/cleanup 函數(shù),其實(shí)就是我們熟悉的 NUnit setup/teardown,函數(shù)返回0表示執(zhí)行成功。
typedef void (*CU_TestFunc)(void)
typedef int (*CU_InitializeFunc)(void)
typedef int (*CU_CleanupFunc)(void)
除了使用默認(rèn) CU_initialize_registry 函數(shù),我們還可以使用 CU_create_new_registry 創(chuàng)建多個(gè)Test Registry 進(jìn)行測(cè)試。相關(guān)細(xì)節(jié)參考官方文檔。
本文的 libmm 除了 Share Memory,也可做 Memory Pool 用,就是用 mmap 預(yù)先劃出一大塊內(nèi)存,以后的分配操作都可以在這塊內(nèi)存內(nèi)部進(jìn)行,包括 malloc、calloc、free 等等。
Memory Pool 的好處是不在堆和棧上分配,可以重復(fù)使用,避免多次向內(nèi)核請(qǐng)求分配和釋放內(nèi)存,一定程度上提高了性能。另外只需釋放整個(gè) Pool 即可完成所有的內(nèi)存釋放,避免內(nèi)存泄露的發(fā)生。
安裝 libmm 庫(kù):
$ sudo apt-get libmm14 libmm-dev libmm-dbg
頭文件: /usr/include/mm.h
/* Standard Malloc-Style API */
MM *mm_create(size_t, const char *);
int mm_permission(MM *, mode_t, uid_t, gid_t);
void mm_reset(MM *);
void mm_destroy(MM *);
int mm_lock(MM *, mm_lock_mode);
int mm_unlock(MM *);
void *mm_malloc(MM *, size_t);
void *mm_realloc(MM *, void *, size_t);
void mm_free(MM *, void *);
void *mm_calloc(MM *, size_t, size_t);
char *mm_strdup(MM *, const char *);
size_t mm_sizeof(MM *, const void *);
size_t mm_maxsize(void);
size_t mm_available(MM *);
char *mm_error(void);
void mm_display_info(MM *);
和標(biāo)準(zhǔn)庫(kù)內(nèi)存分配函數(shù)差不多,很好理解。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <mm.h>
int main(int argc, char* argv[])
{
// 創(chuàng)建 10KB 內(nèi)存池 (最小 8192),"abc" 是創(chuàng)建鎖定標(biāo)識(shí)文件名。
MM* pool = mm_create(1024 * 10, "abc");
// 鎖定池,在當(dāng)前目錄下創(chuàng)建 abc.sem 文件。
mm_lock(pool, MM_LOCK_RW);
// 在池內(nèi)分配內(nèi)存塊。
int* x = mm_malloc(pool, sizeof(int));
*x = 1234;
// 獲取池內(nèi)分配的某個(gè)塊大小。
printf("%p = %d\n", x, mm_sizeof(pool, x));
// 顯式整個(gè)池狀態(tài)信息。
mm_display_info(pool);
printf("max:%d, avail:%d\n", mm_maxsize(), mm_available(pool));
getchar();
// 刪除 abc.sem,解除鎖定。
mm_unlock(pool);
// 釋放整個(gè)池。
mm_destroy(pool);
return EXIT_SUCCESS;
}
輸出:
$ gcc -g -o test -lmm main.c
$ ldd ./test
linux-gate.so.1 => (0xb7729000)
libmm.so.14 => /usr/lib/libmm.so.14 (0xb771c000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb75d7000)
/lib/ld-linux.so.2 (0xb772a000)
$ ./test
0xb7850034 = 4
Information for MM
memory area = 0xb7850014 - 0xb78a0314
memory size = 10264
memory offset = 40
bytes spare = 10224
bytes free = 0 (0 chunks)
bytes allocated = 16
List of free chunks:
<empty-list>
max:33546216, avail:10224
對(duì)照輸出的地址信息,我們可以看 ./test 進(jìn)程的內(nèi)存映射數(shù)據(jù)。
$ ps aux | grep test
yuhen 2406 0.0 0.0 1576 440 pts/1 S+ 19:37 0:00 ./test
$ cat /proc/2406/maps
08048000-08049000 r-xp 00000000 fc:00 30456 /home/yuhen/projects/c/test
08049000-0804a000 r--p 00000000 fc:00 30456 /home/yuhen/projects/c/test
0804a000-0804b000 rw-p 00001000 fc:00 30456 /home/yuhen/projects/c/test
b7701000-b7703000 rw-p 00000000 00:00 0
b7703000-b7841000 r-xp 00000000 fc:00 690 /lib/tls/i686/cmov/libc-2.10.1.so
b7841000-b7842000 ---p 0013e000 fc:00 690 /lib/tls/i686/cmov/libc-2.10.1.so
b7842000-b7844000 r--p 0013e000 fc:00 690 /lib/tls/i686/cmov/libc-2.10.1.so
b7844000-b7845000 rw-p 00140000 fc:00 690 /lib/tls/i686/cmov/libc-2.10.1.so
120
b7845000-b7848000 rw-p 00000000 00:00 0
b7848000-b784b000 r-xp 00000000 fc:00 50664 /usr/lib/libmm.so.14.0.22
b784b000-b784d000 rw-p 00003000 fc:00 50664 /usr/lib/libmm.so.14.0.22
b784d000-b784f000 rw-p 00000000 00:00 0
b784f000-b7853000 rw-s 00000000 00:09 491521 /SYSV00000000 (deleted)
b7853000-b7855000 rw-p 00000000 00:00 0
b7855000-b7856000 r-xp 00000000 00:00 0 [vdso]
b7856000-b7871000 r-xp 00000000 fc:00 599 /lib/ld-2.10.1.so
b7871000-b7872000 r--p 0001a000 fc:00 599 /lib/ld-2.10.1.so
b7872000-b7873000 rw-p 0001b000 fc:00 599 /lib/ld-2.10.1.so
bfd3c000-bfd51000 rw-p 00000000 00:00 0 [stack]
再試試其他的函數(shù)。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <mm.h>
int main(int argc, char* argv[])
{
MM* pool = mm_create(1024 * 10, "abc");
/* --------- DUP ------------------ */
char* s1 = mm_malloc(pool, 10);
strcpy(s1, "abcd");
char* s2 = mm_strdup(pool, s1);
printf("s1=%p,%s, s2=%p,%s\n", s1, s1, s2, s2);
printf("[Befor Reset] available: %d\n", mm_available(pool));
/* --------- RESET ----------------- */
mm_reset(pool);
printf("[After Reset] available: %d\n", mm_available(pool));
int* x = mm_malloc(pool, sizeof(int));
*x = 0x1234;
printf("x=%p,0x%x\n", x, *x);
/* --------- ERROR ----------------- */
char* s = mm_malloc(pool, 1024 * 20);
if (!s) printf("%s\n", mm_error());
/* --------- INFO ------------------ */
mm_display_info(pool);
mm_destroy(pool);
return EXIT_SUCCESS;
}
輸出:
$ ./test
s1=0xb78d8034,abcd, s2=0xb78d804c,abcd
[Befor Reset] available: 10200
[After Reset] available: 10240
x=0xb78d8034,0x1234
mm:alloc: out of memory
Information for MM
memory area = 0xb78d8014 - 0xb7928314
memory size = 10264
memory offset = 40
bytes spare = 10224
bytes free = 0 (0 chunks)
bytes allocated = 16
List of free chunks:
<empty-list>
調(diào)用 mm_reset 后內(nèi)存池重新 "從頭" 分配,當(dāng)超出最大尺寸時(shí)返回 NULL。這些操作不會(huì)導(dǎo)致內(nèi)存泄露。盡管池創(chuàng)建時(shí)都被初始化為 0,但隨著分配和釋放,池和堆一樣會(huì)遺留大量垃圾數(shù)據(jù),因此注意使用 mm_malloc 和 mm_calloc。
$ valgrind --leak-check=full ./test
==2654== Memcheck, a memory error detector
==2654== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==2654== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==2654== Command: ./test
==2654==
==2654==
==2654== HEAP SUMMARY:
==2654== in use at exit: 0 bytes in 0 blocks
==2654== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==2654==
==2654== All heap blocks were freed -- no leaks are possible
==2654==
==2654== For counts of detected and suppressed errors, rerun with: -v
==2654== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 15 from 8)
mm.h 還有一組以大寫(xiě)字母 MM 開(kāi)頭的函數(shù),不過(guò)是用了一個(gè)全局變量存儲(chǔ)池指針,然后內(nèi)部調(diào)用mm_xxx 而已。
000016c0 <MM_reset>:
16c0: 55 push ebp
16c1: 89 e5 mov ebp,esp
16c3: 53 push ebx
16c4: e8 4e fd ff ff call 1417 <mm_lib_error_set@plt+0xcb>
16c9: 81 c3 2b 29 00 00 add ebx,0x292b
16cf: 83 ec 04 sub esp,0x4
16d2: 8b 83 34 01 00 00 mov eax,DWORD PTR [ebx+0x134]
16d8: 85 c0 test eax,eax
16da: 74 08 je 16e4 <MM_reset+0x24>
16dc: 89 04 24 mov DWORD PTR [esp],eax
16df: e8 d8 fa ff ff call 11bc <mm_reset@plt>
16e4: 83 c4 04 add esp,0x4
16e7: 5b pop ebx
16e8: 5d pop ebp
16e9: c3 ret
16ea: 8d b6 00 00 00 00 lea esi,[esi+0x0]
習(xí)慣了 .NET 和 Java 平臺(tái)的程序員,可能會(huì)對(duì) C 編碼的內(nèi)存泄露存在某種未知的恐懼。其實(shí) C 一樣有好用、成熟而高效的垃圾回收庫(kù) —— libgc。
官方網(wǎng)站已經(jīng)發(fā)布了 7.2 Alpha4,包括 Mozilla、Mono 等項(xiàng)目都是其用戶(hù)。
我們先準(zhǔn)備一個(gè)內(nèi)存泄露的例子,當(dāng)然通常所說(shuō)的內(nèi)存泄露只發(fā)生在堆 (Heap) 上。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void test()
{
char *s = malloc(102400);
*s = 0x10;
}
int main(void)
{
for (int i = 0; i < 10000; i++)
{
printf("%d\n", i);
test();
sleep(1);
}
}
$ gcc -o test -g test.c
$ ./test
0
1
2
3
4
... ...
新開(kāi)一個(gè)終端,我們監(jiān)視內(nèi)存變化。
$ ps aux | grep test
yuhen 3474 0.0 0.0 2360 416 pts/1 S+ 20:44 0:00 ./test
$ top -p 3474
Mem: 509336k total, 499840k used, 9496k free, 55052k buffers
Swap: 409616k total, 116k used, 409500k free, 307464k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3474 yuhen 20 0 4160 492 324 S 0.0 0.1 0:00.00 test
內(nèi)存占用 (VIRT) 不斷上升,顯然內(nèi)存泄露正在發(fā)生。
接下來(lái)我們正式引入 libgc,首先得安裝相關(guān)的庫(kù)。
$ sudo apt-get install libgc-dev
我們先看看 libgc 檢測(cè)內(nèi)存泄露的本事。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gc/leak_detector.h>
void test()
{
char *s = malloc(102400);
*s = 0x10;
}
int main(void)
{
GC_find_leak = 1;
for (int i = 0; i < 10000; i++)
{
printf("%d\n", i);
test();
}
CHECK_LEAKS();
getchar();
return 0;
}
GC_find_leak 和 CHECK_LEAKS 的信息可參考 /usr/include/gc/gc.h、leak_detector.h。
$ gcc -o test -g test.c -lgc
$ ./test
0
1
2
3
4
5
Leaked composite object at 0x8c94010 (test.c:13, sz=102400, NORMAL)
Leaked composite object at 0x8c7a010 (test.c:13, sz=102400, NORMAL)
Leaked composite object at 0x8c60010 (test.c:13, sz=102400, NORMAL)
... ...
我們可以看到每隔一定的周期就會(huì)輸出內(nèi)存泄露提示,很詳細(xì),包括泄露代碼位置和大小。
見(jiàn)識(shí)了 libgc 檢測(cè)內(nèi)存泄露的本事,那么就正式啟用 GC 吧。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gc/gc.h>
void test()
{
char *s = GC_MALLOC(102400);
*s = 0x10;
}
int main(void)
{
for (int i = 0; i < 10000; i++)
{
printf("%d, heap=%d\n", i, GC_get_heap_size());
test();
sleep(1);
}
getchar();
return 0;
}
$ gcc -o test -g test.c -lgc
$ ./test
0, heap=0
1, heap=192512
2, heap=360448
3, heap=585728
4, heap=585728
5, heap=585728
6, heap=585728
7, heap=585728
... ...
我們?cè)俅螁⒂?top 監(jiān)控會(huì)發(fā)現(xiàn)內(nèi)存維持在一個(gè)穩(wěn)定的數(shù)字,不再增長(zhǎng),這顯然是垃圾回收起了作用。
對(duì)于已有的項(xiàng)目源碼,我們也不必大費(fèi)周章地將 malloc、free 替換成 GC_MALLOC、GC_FREE,
直接在 main.c 中定義宏即可。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gc/gc.h>
#define malloc(n) GC_MALLOC(n);
#define free(n) GC_FREE(n);
void test()
{
char *s = malloc(102400);
*s = 0x10;
free(s);
}
... ...
$ objdump -dS -M intel test | less
void test()
{
... ...
char *s = malloc(102400);
804865a: mov DWORD PTR [esp],0x19000
8048661: call 8048550 <GC_malloc@plt>
8048666: mov DWORD PTR [ebp-0xc],eax
... ...
free(s);
804866f: mov eax,DWORD PTR [ebp-0xc]
8048672: mov DWORD PTR [esp],eax
8048675: call 8048570 <GC_free@plt>
}
還可以靜態(tài)編譯,以免發(fā)布時(shí)帶個(gè)拖油瓶。
$ gcc -o test -g test.c /usr/lib/libgc.a -lpthread
void test()
{
char *s = malloc(102400);
804930a: mov DWORD PTR [esp],0x19000
8049311: call 8049a90 <GC_malloc>
8049316: mov DWORD PTR [ebp-0xc],eax
... ...
}
注意:libgc 只對(duì) GC_MALLOC 等方法分配的內(nèi)存空間有效。
配置文件很重要,INI 太弱,XML 太繁復(fù),Linux *.conf 很酷。
找了好幾種相關(guān)的類(lèi)庫(kù),發(fā)覺(jué)還是 hyperrealm libconfig 最強(qiáng)大最好用,相關(guān)細(xì)節(jié)可參考 官方手冊(cè)。源中的版本是1.3.2-1,也可以去官方文章下載最新版本。
$ sudo apt-get install libconfig8 libconfig8-dev
完全類(lèi)腳本化的配置語(yǔ)法,支持注釋、包含、簡(jiǎn)單配置、數(shù)組、列表以及非常像類(lèi)的組。
test.conf
# Example application configuration file
title = "Test Application"; // scalar value
version = 1; // int, int64, float, bool, string
app: // group
{
user:
{
name = "Q.yuhen";
code = "xxx-xxx-xxx";
tags = ["t1", "t2", "t3"]; // array
data = ( "Hello", 1234 ); // list
}
};
直接用多級(jí)路徑讀取目標(biāo)值,這是最簡(jiǎn)單的做法。注意區(qū)分參數(shù)中 path 和 name 的區(qū)別,后者無(wú)法使用路徑。
int config_lookup_int (const config_t * config, const char * path, int * value)
int config_lookup_int64 (const config_t * config, const char * path, long long * value)
int config_lookup_float (const config_t * config, const char * path, double * value)
int config_lookup_bool (const config_t * config, const char * path, int * value)
int config_lookup_string (const config_t * config, const char * path, const char ** value)
我們?cè)囋嚳础?/p>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <libconfig.h>
void scalar(config_t* conf)
{
char* title;
config_lookup_string(conf, "title", &title);
printf("title = %s;\n", title);
int version;
config_lookup_int(conf, "version", &version);
printf("version = %d;\n", version);
char* user_name;
config_lookup_string(conf, "app.user.name", &user_name);
printf("app.user.name = %s;\n", user_name);
char* tag;
config_lookup_string(conf, "app.user.tags.[2]", &tag);
printf("app.user.tags[2] = %s;\n", tag);
int data;
config_lookup_int(conf, "app.user.data.[1]", &data);
printf("app.user.data.[1] = %d;\n", data);
}
int main(int argc, char* argv[])
{
config_t* conf = &(config_t){};
config_init(conf);
config_read_file(conf, "test.conf");
scalar(conf);
config_destroy(conf);
return EXIT_SUCCESS;
}
輸出:
title = Test Application;
version = 1;
app.user.name = Q.yuhen;
app.user.tags[2] = t3;
app.user.data.[1] = 1234;
所有的 Group 和其 Member 都是 Config_Setting,我們可以用 config_lookup 找出目標(biāo)后,然后使用 Name 讀取。
config_setting_t * config_lookup (const config_t * config, const char * path)
int config_setting_lookup_int (const config_setting_t * setting, const char * name, int * value)
int config_setting_lookup_int64 (const config_setting_t * setting, const char * name, long long *
value)
int config_setting_lookup_float (const config_setting_t * setting, const char * name, double *
value)
129
int config_setting_lookup_bool (const config_setting_t * setting, const char * name, int * value)
int config_setting_lookup_string (const config_setting_t * setting, const char * name, const char
** value)
注意 config_setting_lookup_xxx 只能使用 Member Name,而不是 Path。
```void group(config_t conf){config_setting_t user = config_lookup(conf, "app.user");
char* code;
config_setting_lookup_string(user, "code", &code);
printf("user.code = %s;\n", code);
}
利用相關(guān)的函數(shù),我們還可以遍歷 Array/List 的所有 Element。
void group(config_t conf){config_setting_t user = config_lookup(conf, "app.user");
config_setting_t* tags = config_setting_get_member(user, "tags");
int count = config_setting_length(tags);
int i;
for (i = 0; i < count; i++)
{
printf("user.tags[%d] = %s;\n", i, config_setting_get_string_elem(tags, i));
}
}
輸出:
user.tags[0] = t1;user.tags[1] = t2;user.tags[2] = t3;
當(dāng)然,我們也可以用 config_lookup 直接找到 app.user.tags,然后遍歷。
void group(config_t conf){config_setting_t tags = config_lookup(conf, "app.user.tags");int count = config_setting_length(tags);
int i;
for (i = 0; i < count; i++)
{
printf("user.tags[%d] = %s;\n", i, config_setting_get_string_elem(tags, i));
}
printf("-----------------------\n");
config_setting_t* code = config_lookup(conf, "app.user.code");
printf("user.code = %s;\n", config_setting_get_string(code));
}
輸出:
user.code = xxx-xxx-xxx;
上面的例子中,我們還可以直接用 lookup 查找簡(jiǎn)單配置 app.user.code,然后用相關(guān)方法返回值,無(wú)需再次提供 Name。
int config_setting_get_int (const config_setting_t setting)long long config_setting_get_int64 (const config_setting_t setting)double config_setting_get_float (const config_setting_t setting)int config_setting_get_bool (const config_setting_t setting)const char config_setting_get_string (const config_setting_t setting)
Array/List 的內(nèi)容可以是 Group,我們可以用 config_setting_get_elem() 獲取指定序號(hào)的元素后繼續(xù)操作。config_setting_t config_setting_get_member (config_setting_t setting, const char name)config_setting_t config_setting_get_elem (const config_setting_t * setting, unsigned int idx)
## 11.3 Write
配置文件嗎,增刪改操作都要全乎。
int config_setting_set_int (config_setting_t setting, int value)int config_setting_set_int64 (config_setting_t setting, long long value)int config_setting_set_float (config_setting_t setting, double value)int config_setting_set_bool (config_setting_t setting, int value)int config_setting_set_string (config_setting_t setting, const char value)
config_setting_t config_setting_set_int_elem (config_setting_t setting, int idx, int value)config_setting_t config_setting_set_int64_elem (config_setting_t setting, int idx, long longvalue)config_setting_t config_setting_set_float_elem (config_setting_t setting, int idx, doublevalue)config_setting_t config_setting_set_bool_elem (config_setting_t setting, int idx, int value)config_setting_t config_setting_set_string_elem (config_setting_t setting, int idx, const char
config_setting_t config_setting_add (config_setting_t parent, const char * name, int type)
int config_setting_remove (config_setting_t parent, const char name)int config_setting_remove_elem (config_setting_t * parent, unsigned int idx)
const char config_setting_name (const config_setting_t setting)
為了方便查看,我直接 "保存" 到 stdout 了。
void write(config_t conf){config_setting_t user = config_lookup(conf, "app.user");config_setting_t name = config_setting_get_member(user, "name");config_setting_t tags = config_setting_get_member(user, "tags");config_setting_t* data = config_setting_get_member(user, "data");
/* ----------------- Add ------------------- */
config_setting_t* comment = config_setting_add(user, "comment", CONFIG_TYPE_STRING);
config_setting_set_string(comment, "test...");
/* ----------------- Remove ---------------- */
config_setting_remove(user, "code");
config_setting_remove_elem(tags, 1);
/* ----------------- Set ------------------- */
config_setting_set_string(name, "Rainsoft");
config_setting_set_string_elem(data, 0, "Ubuntu");
/* ----------------- Write ----------------- */
config_write(conf, stdout);
}
輸出:
title = "Test Application";version = 1;app :{user :{name = "Rainsoft";tags = [ "t1", "t3" ];data = ( "Ubuntu", 1234 );comment = "test...";};};
## 11.4 Q & A
(1) 調(diào)用 config_destroy 后,其分配的字符串會(huì)被全部釋放,因此得自己注意 strcpy / strdup。
(2) 官方文檔中標(biāo)明了 "Libconfig is not thread-safe","Libconfig is not async-safe" ……似乎 Array/List 必須是 Group Member,不知道是不是版本的問(wèn)題。
# 12. libevent: Event Notification
libevent 貌似是 Linux 下寫(xiě)高性能服務(wù)器的首選組件。當(dāng)然也可以自己用 epoll 寫(xiě),不是太復(fù)雜,用成熟組件的好處就是降低了開(kāi)發(fā)和維護(hù)成本。聽(tīng)說(shuō)還有個(gè) libev,似乎比 libevent 還強(qiáng)悍,找時(shí)間研究看看。
下面是學(xué)習(xí) libevent 時(shí)寫(xiě)的測(cè)試代碼,僅供參考。
void sig_exit(int signo){event_loopbreak();}
void test(int fd, short event, void* arg){char buf[256] = {};scanf("%s", buf);printf("[R] %s\n", buf);}
void test2(struct bufferevent bv, void arg){// 查找分隔符u_char sep;while((sep = evbuffer_find(bv->input, (u_char)";", 1)) != NULL){int size = sep - bv->input->buffer;
// 讀有效字符串
char buf[size + 2];
memset(buf, '\0', sizeof(buf));
size_t len = bufferevent_read(bv, buf, size + 1);
// 替換換行符
for (int i = 0; i < sizeof(buf); i++)
{
if (buf[i] == '\n') buf[i] = '-';
}
// 顯示字符串以及緩存中剩余的字符數(shù)
printf("[Read Chars] %s; len:%d;\n", buf, len);
printf("[Cache Chars] %d;\n", strlen((char*)bv->input->buffer));
}
}
void test3(int fd, short event, void arg){char buf[50];time_t curtime;struct tm loctime;
curtime = time(NULL);
loctime = localtime(&curtime);
strftime(buf, 50, "%F %T", loctime);
printf("%s\n", buf);
}
int main(int argc, char argv[]){/ --- Signal ------------------------------------------------ */signal(SIGINT, sig_exit);signal(SIGHUP, sig_exit);
/* --- Event Init -------------------------------------------- */
event_init();
/* --- Standard usage --------------------------------------- */
//struct event* e = malloc(sizeof(struct event));
//event_set(e, STDIN_FILENO, EV_READ | EV_PERSIST, test, NULL);
//event_add(e, NULL);
/* --- I/O Buffers ------------------------------------------ */
struct bufferevent* bv = bufferevent_new(STDIN_FILENO, test2, NULL, NULL, NULL);
bufferevent_enable(bv, EV_READ | EV_PERSIST);
/* --- Timers ----------------------------------------------- */
//struct timeval* time = malloc(sizeof(struct timeval));
//time->tv_sec = 5;
//time->tv_usec = 0;
//struct event* e = malloc(sizeof(struct event));
//evtimer_set(e, test3, NULL);
//evtimer_add(e, time);
/* --- Event Dispatch ---------------------------------------- */
event_dispatch();
/* --- Free Memory ------------------------------------------- */
//free(e);
bufferevent_free(bv);
//free(e); free(time);
printf("exit!\n");
return EXIT_SUCCESS;
}
更多建議: