2021-05-12 14:32:11
Linux下cp覆蓋原so檔案時引起的段錯誤原因確定
最近因為一個很有意思的段錯誤學習了一些新的東西。
當時現象是這樣的,程式正在執行,系統升級,此時某些so已經被該程式所使用,現在把這些so檔案覆蓋了,導致了該程式崩潰。
偵錯dump檔案可以發現是崩潰在了ld解析函數符號的時候,然後檢視libc的原始碼,發現崩潰的函數checkmatch傳入的引數是空指標,所以導致了崩潰。因為受到以前寫裸機程式碼的影響,裸機是這樣的,如果前2M stepstorm不夠用,那麼在stepstorm中的程式碼就把nandflash中的程式碼拷貝到記憶體中,然後跳轉到記憶體中去執行,所以此時就算原始檔再怎麼被修改也不會受到nandflash中的內容影響。下面先講兩個需要用到的知識點。
Linux下很重要的一點是,一個檔案可以被很多應用程式開啟,同一時間的確只有一個應用程式可以對該檔案讀寫,但是在不同時刻,所有應用程式對檔案的操作都會影響到其他已開啟該檔案的應用程式,因為在每次讀寫前,系統呼叫read和write會對記憶體中的內容進行有效性判斷。
再講一個有關mmap或者mmap2的事情,
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr是要對映到的記憶體地址,返回值也是被對映到的記憶體地址,因為一般指定為0,有核心選擇一段可用的記憶體空間。
len表示要對映的記憶體大小。
prot表示這段記憶體的存取許可權。
flags表示對映後記憶體的型別,主要是對該記憶體的寫是否會影響到原檔案。
fd表示檔案描述符,
offset表示需要對映的檔案內容相對檔案頭偏移量。
對映完了之後,對這個記憶體的存取就是對檔案的存取。
下面看栗子:
原始碼:
共用庫:
#include <stdio.h> int fun1() { printf("fun1n"); }
main函數
int fun1(); int main() { while(1){ sleep(10); fun1(); } }
這個程式碼很簡單,下面先用strace跟蹤下test的執行:
strace ./test execve("./test", ["./test"], [/* 22 vars */]) = 0 brk(0) = 0x9653000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7731000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("tls/i686/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("sse2/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("cmov/lib1.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("lib1.so", O_RDONLY|O_CLOEXEC) = 3 read(3, "177ELF111 3 3 1 2603 004 "..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=6732, ...}) = 0 getcwd("/home/keda/caozhenhua/test/updateso", 128) = 36 mmap2(NULL, 8212, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb772e000 mmap2(0xb772f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xb772f000 close(3) = 0 open("tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/i686/sse2", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/i686/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/i686", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/sse2/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/sse2", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/tls", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/i686/sse2/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/i686/sse2", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/i686/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/i686", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/sse2/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/sse2", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso/cmov", 0xbf9bd320) = -1 ENOENT (No such file or directory) open("/home/keda/caozhenhua/test/updateso/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) stat64("/home/keda/caozhenhua/test/updateso", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=83733, ...}) = 0 mmap2(NULL, 83733, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7719000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "177ELF111 3 3 1 0002261 004 "..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1713640, ...}) = 0 mmap2(NULL, 1723100, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7574000 mmap2(0xb7713000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19f) = 0xb7713000 mmap2(0xb7716000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7716000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7573000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7572000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb75726c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb7713000, 8192, PROT_READ) = 0 mprotect(0xb772e000, 4096, PROT_READ|PROT_WRITE) = 0 mprotect(0xb772e000, 4096, PROT_READ|PROT_EXEC) = 0 mprotect(0xb772f000, 4096, PROT_READ) = 0 mprotect(0x8049000, 4096, PROT_READ) = 0 mprotect(0xb7754000, 4096, PROT_READ) = 0 munmap(0xb7719000, 83733) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({10, 0}, 0xbf9bd958) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb772d000 write(1, "fun1n", 5fun1 ) = 5 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({10, 0},
可以看到每開啟一個共用庫,linux利用的是mmap2,而不是像裸機一樣的read和write。
所以這樣的話,比如需要跳轉到共用庫中的某個函數,如果是第一次的話,那麼需要從檔案中把內容載入到記憶體,然後再執行。
那麼如果在程式執行時,出現缺頁,那麼就需要從記憶體中重新讀取該檔案的該段內容,而如果該檔案被修改了而且該段是第一次被存取,那麼讀取到的內容將會導致不可預知的錯誤。
接下來再對mmap實驗。
原始碼:
#include <sys/mman.h> #include <fcntl.h> int main() { int fd; int i = 0; char *buf; fd = open("./libvsipstack.a",O_RDONLY); buf = mmap(0,10,PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE,fd,0); while(1) { i++; sleep(10); printf("%cn",buf[i]); } }
mmap的對映方式和載入庫的方式一致,
然後在程式執行時我用vi修改了libvsipstack.a,修改之後可以看到libvsipstack.a是有一個備份檔案的。此時是可以保證資料是正確的。
-rwxrwxrwx 1 root root 886056 2016-07-22 12:15 -rwxrwxrwx 1 root root 7249 2016-07-22 12:12 a.out -rwxrwxrwx 1 root root 12 2016-07-22 12:12 hello.c -rwxrwxrwx 1 root root 886056 2016-07-22 12:16 libvsipstack.a -????????? ? ? ? ? ? libvsipstack.a~ -rwxrwxrwx 1 root root 263 2016-07-22 12:12 test.c
而當我們在上面的實驗中,用libvsipstack.a覆蓋lib1.so,並沒有導致lib1.so有一個備份,而是變成了libvsipstack.a一樣的檔案。所以下一次讀取將會和原始檔不一致。
-rwxrwxrwx 1 root root 0 2016-07-22 09:08 core -rwxrwxrwx 1 root root 54 2016-07-21 13:37 lib1.c -rwxrwxrwx 1 root root 886022 2016-07-22 12:21 lib1.so -rwxrwxrwx 1 root root 6732 2016-07-22 09:22 lib2.so -rwxrwxrwx 1 root root 886022 2016-07-21 15:39 libvsipstack.a -rwxrwxrwx 1 root root 7164 2016-07-22 09:16 test -rwxrwxrwx 1 root root 65 2016-07-22 09:10 test.c
需要知道當原始檔更新時,使用mmap到的記憶體,系統會去重新讀取檔案中的內容。
所以當庫被更新時,同時會更新記憶體中的內容。
總結一下,
是否崩在ld中,是由是否是第一次呼叫該函數決定的,因為只有第一次呼叫才會需要去解析plt表中的內容。
首先記憶體的布局是根據elf檔案頭來部署的。所以替換前後的檔案不是同一個那麼就會導致非法指令之類的錯誤,根本無法確定錯誤來源。
但如果替換前後庫是一致的,那麼差別就在於,重定向表的內容變的無效了。
重定向表有兩種一種是在程式啟動時就完成符號解析的,看栗子
Dump of assembler code for function fun1: 0xb7fd846c <+0>: 55 push %ebp 0xb7fd846d <+1>: 89 e5 mov %esp,%ebp 0xb7fd846f <+3>: 83 ec 18 sub $0x18,%esp 0xb7fd8472 <+6>: c7 04 24 d4 04 00 00 movl $0x4d4,(%esp) 0xb7fd8479 <+13>: e8 fc ff ff ff call 0xb7fd847a <fun1+14> 0xb7fd847e <+18>: c9 leave 0xb7fd847f <+19>: c3 ret
本來+13位置處的程式碼已經完成了重定向,但是重新讀取之後呢,恢復了重定向之前的情況,結果崩潰。
另一種是懶惰式的載入,是先跳到plt表的情況,看栗子
0xb7fd845c <+0>: push %ebp 0xb7fd845d <+1>: mov %esp,%ebp 0xb7fd845f <+3>: push %ebx 0xb7fd8460 <+4>: sub $0x14,%esp 0xb7fd8463 <+7>: call 0xb7fd8457 <__i686.get_pc_thunk.bx> 0xb7fd8468 <+12>: add $0x1b8c,%ebx 0xb7fd846e <+18>: lea -0x1b12(%ebx),%eax 0xb7fd8474 <+24>: mov %eax,(%esp) 0xb7fd8477 <+27>: call 0xb7fd8380 <puts@plt> 0xb7fd847c <+32>: add $0x14,%esp 0xb7fd847f <+35>: pop %ebx 0xb7fd8480 <+36>: pop %ebp 0xb7fd8481 <+37>: ret End of assembler dump. (gdb) disassemble 0xb7fd8380 Dump of assembler code for function puts@plt: 0xb7fd8380 <+0>: jmp *0x10(%ebx) 0xb7fd8386 <+6>: push $0x8 0xb7fd838b <+11>: jmp 0xb7fd8360 End of assembler dump. (gdb) i r eax 0xb7fd84e2 -1208122142 ecx 0xbffff6a8 -1073744216 edx 0xb7fbeff4 -1208225804 ebx 0xb7fd9ff4 -1208115212 esp 0xbffff6ac 0xbffff6ac ebp 0xbffff6c8 0xbffff6c8 esi 0x0 0 edi 0x0 0 eip 0x386 0x386 eflags 0x210292 [ AF SF IF RF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) disassemble 0x00000386 No function contains specified address. (gdb) x/x 0xb7fd9ff4+0x10 0xb7fda004: 0x00000386
可以看到先跳到了plt表,因為是重新讀入,相當於還沒有重定位,所以跳轉到下一句話,可以反組合看到0x386就是原反組合沒有偏移地址的地址大小。
00000380 <puts@plt>: 380: ff a3 10 00 00 00 jmp *0x10(%ebx) 386: 68 08 00 00 00 push $0x8 38b: e9 d0 ff ff ff jmp 360 <_init+0x3c>
所以這裡面的情況相當於基地址被初始為了0,導致崩潰。
因為mmap被呼叫時指明的是private,所以記憶體的修改不會影響到檔案的內容,但是檔案的修改會影響到記憶體的內容,所以導致了崩潰的產生。
說這個是系統的bug也可以,說是自己的使用不當也可以。
還有一個是許可權問題,除了擁有root許可權的使用者,是不可以這樣隨意覆蓋檔案的。
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-07/133450.htm
相關文章