guest@blog.cmj.tw: ~/posts $

C 101


I’m watching you ~

GLibC (GNU C Library) 算是一個常用的函式庫,大概也是每個 Linux 都會有的一個 C 使用的 Shared Library. 但是有想過 C 程式會做哪些事情嗎? 下面是一個什麼都沒有的 C 範例程式:

/* Copyright (C) 2014 cmj. All right reserved. */

int main(int argc, char *argv[])
{
    return 0;
}

這個程式是一個完全沒有功能但完整的 C 程式,使用 GCC 4.9.2 編譯過後, 會得到一個 6595 Byte 的 ELF 64 的可執行檔。用 strace 觀察一下, 可以發現這個程式呼叫了比預期還要多的 syscall

execve("./a.out", ["./a.out"], [/* 23 vars */]) = 0
brk(0)                                  = 0x23c2000
access("/etc/ld.so.preload", R_OK)      = 0
open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
close(3)                                = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=68864, ...}) = 0
mmap(NULL, 68864, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fef230ca000
close(3)                                = 0
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\1\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1984416, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fef230c9000
mmap(NULL, 3813200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fef22b17000
mprotect(0x7fef22cb0000, 2097152, PROT_NONE) = 0
mmap(0x7fef22eb0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x199000) = 0x7fef22eb0000
mmap(0x7fef22eb6000, 16208, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fef22eb6000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fef230c8000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fef230c7000
arch_prctl(ARCH_SET_FS, 0x7fef230c8700) = 0
mprotect(0x7fef22eb0000, 16384, PROT_READ) = 0
mprotect(0x7fef230db000, 4096, PROT_READ) = 0
munmap(0x7fef230ca000, 68864)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++

在這個流程當中,第一行的意思是因為我們使用 Bash 來執行這個程式:使用 execve 來執行 ./a.out 這個 binary 並且帶入目前的環境變數。最後一行表示程式結束:呼叫 sys_exit_group 清除所有 thread 並且結束程式。跟 exit 不同的是, exit_group 會將所有 process 相關的 threads 都一並結束,而非單結束呼叫 exit 的 thread。但是在我們程式流程中並沒有其他額外的邏輯,C 到底還會幫我們額外做哪些事情呢?

brk

首先,C 會先幫我們呼叫 brk(0) :這個程式會改變 program break 的位子。 program break 是尚未初始化的 data segment 的起始位址,改變 program break 的值, 可以有效的增加/減少程式可以使用的記憶體空間。所以一開始呼叫 brk(0) 表示, 將所以記憶體回歸到最初的狀態。

LD Preload

接著,檢查 /etc/ld.so.preload 以及 /etc/ld.so.cache 的內容:先判斷 ld.so.preload 檔案大小,因為我們並沒有內容所以跳到下一個步驟。接著將 ld.so.cache 用 mmap 將內容映射到記憶體空間中:其中,將 flag 設定為 MAP_PRIVATE 來建立一個私有且 copy-on-write 的映射關係。

這個步驟的最後,是將 libc 映射到記憶體當中:首先先讀取開頭的 832 Bytes, 接著將 libc 用 mmap 映射到記憶體當中 (不過不確定為什麼要映射 0x3A2F50 Bytes)。

Memory Allocation / Protection

這個步驟會使用若干次 mmap(NULL, 4096, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) 來獲得 4KB 的記憶體空間。並且使用 mprotect 來保護記憶體不能被使用: 在連續的記憶體空間中作出一個 200 KB 的記憶體斷層無法存取, 或者是 16KB/4KB 的唯讀空間。

最後呼叫 arch_prctl :設定 ARCH_SET_FS 將 FS register 設定為指令的 64-bits 位址。

munmap

流程的最後,執行 munmap 將一開始 mmap 的 ld.so.cache unmap 掉。

同場加映:asm

同場加映使用 assembly language (GNU assembler (GNU Binutils) 2.24 / GNU ld (GNU Binutils) 2.24) 會得到哪種結果。我們一樣使用最簡單的程式碼來產生一個可執行檔:

.section .rodata
.section .text
.globl main
main:

mov   %rax, %rdi
mov   $60, %rax
syscall

因為這個檔案實在是太簡單來,所以不會有額外的 syscall 呼叫: 事實上,所有呼叫的 syscall 都是程式碼內自行呼叫的 (使用 syscall)。

execve("./main", ["./main"], [/* 23 vars */]) = 0
_exit(0)                                = ?

同場加映:c++

同場加映 c++ (g++ (GCC) 4.9.2),會發現額外多 mmap 的 libstdc++.so.6、libm.so.6 以及libgcc_s.so.1 三個 Shared Library。並且多作了幾次 mmap 來獲得額外的記憶體空間。

	execve("./a.out", ["./a.out"], [/* 23 vars */]) = 0
	brk(0)                                  = 0xbe3000
	access("/etc/ld.so.preload", R_OK)      = 0
	open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3
	fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
	close(3)                                = 0
	open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
	fstat(3, {st_mode=S_IFREG|0644, st_size=68864, ...}) = 0
	mmap(NULL, 68864, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6b191cd000
	close(3)                                = 0
	open("/usr/lib/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
	read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\271\5\0\0\0\0\0"..., 832) = 832
	fstat(3, {st_mode=S_IFREG|0755, st_size=1486871, ...}) = 0
	mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6b191cc000
	mmap(NULL, 3204768, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6b18cae000
	mprotect(0x7f6b18d9e000, 2097152, PROT_NONE) = 0
	mmap(0x7f6b18f9e000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xf0000) = 0x7f6b18f9e000
	mmap(0x7f6b18fa8000, 83616, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f6b18fa8000
	close(3)                                = 0
	open("/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
	read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200U\0\0\0\0\0\0"..., 832) = 832
	fstat(3, {st_mode=S_IFREG|0755, st_size=1067456, ...}) = 0
	mmap(NULL, 3162456, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6b189a9000
	mprotect(0x7f6b18aac000, 2097152, PROT_NONE) = 0
	mmap(0x7f6b18cac000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x103000) = 0x7f6b18cac000
	close(3)                                = 0
	open("/usr/lib/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
	read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260*\0\0\0\0\0\0"..., 832) = 832
	fstat(3, {st_mode=S_IFREG|0644, st_size=544468, ...}) = 0
	mmap(NULL, 2185952, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6b18793000
	mprotect(0x7f6b187a9000, 2093056, PROT_NONE) = 0
	mmap(0x7f6b189a8000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7f6b189a8000
	close(3)                                = 0
	open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
	read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\1\2\0\0\0\0\0"..., 832) = 832
	fstat(3, {st_mode=S_IFREG|0755, st_size=1984416, ...}) = 0
	mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6b191cb000
	mmap(NULL, 3813200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6b183f0000
	mprotect(0x7f6b18589000, 2097152, PROT_NONE) = 0
	mmap(0x7f6b18789000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x199000) = 0x7f6b18789000
	mmap(0x7f6b1878f000, 16208, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f6b1878f000
	close(3)                                = 0
	mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6b191ca000
	mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6b191c8000
	arch_prctl(ARCH_SET_FS, 0x7f6b191c8740) = 0
	mprotect(0x7f6b18789000, 16384, PROT_READ) = 0
	mprotect(0x7f6b18cac000, 4096, PROT_READ) = 0
	mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6b191c7000
	mprotect(0x7f6b18f9e000, 32768, PROT_READ) = 0
	mprotect(0x7f6b191de000, 4096, PROT_READ) = 0
	munmap(0x7f6b191cd000, 68864)           = 0
	exit_group(0)                           = ?