标签:data use 代码段 全局 exe ... 第一个 使用 font
程序的构成
Linux下二进制可执行程序的格式一般为ELF格式。 我们可以用readelf命令来读取二进制的信息。
ELF文件的主要内容就是由各个section及symbol表组成的。 下面来分别介绍这些字段的含义:
二进制执行流程
下面是一个简单的代码,我们把它编译一下用strace命令来分析他的执行过程。
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 int main( ) 5 { 6 int fd ; 7 int i = 0 ; 8 fd = open( “/tmp/foo”, O_RDONLY ) ; 9 if ( fd < 0 ) 10 i=5; 11 else 12 i=2; 13 return i; 14 }
过程如下:
1 execve("./main", ["./main"], [/* 43 vars */]) = 0
2 brk(0) = 0x9ac4000
3 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
4 mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7739000
5 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
6 open("/etc/ld.so.cache", O_RDONLY) = 3
7 fstat64(3, {st_mode=S_IFREG|0644, st_size=80682, ...}) = 0
8 mmap2(NULL, 80682, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000
9 close(3) = 0
10 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
11 open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
12 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
13 fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
14 mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x56d000
15 mprotect(0x6c7000, 4096, PROT_NONE) = 0
16 mmap2(0x6c8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6c8000
17 mmap2(0x6cb000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6cb000
18 close(3) = 0
19 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7724000
20 set_thread_area({entry_number:-1 -> 6, base_addr:0xb77248d0, limit:1048575, seg_32bit:1, contents:0, read_exec_ only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
21 mprotect(0x6c8000, 8192, PROT_READ) = 0
22 mprotect(0x8049000, 4096, PROT_READ) = 0
23 mprotect(0x4b0000, 4096, PROT_READ) = 0
24 munmap(0xb7725000, 80682) = 0
25 open("/tmp/foo", O_RDONLY) = -1 ENOENT (No such file or directory)
26 exit_group(5) = ?
strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:
系统调用的名称( 参数... ) = 返回值 错误标志和描述
另外,使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。当程序挂死在系统调用处,我们可以查看相应系统调用的man手册,了解在什么情况下该系统调用会出现挂死情况。
上面strace命令打印出来的信息分析如下:
Line 1: 对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
Line 2: 以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
Line 3: 调用access函数检验/etc/ld.so.nohwcap是否存在,如果ld.so.nohwcap存在, 则ld会加载其中未优化版本的库
Line 4: 使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000
Line 5: 调用access函数检验/etc/ld.so.preload是否存在,如果ld.so.preload存在,则ld会加载其中的库——在一些项目中, 我们需要拦截或替换系统调用或C库, 此时就会利用这个机制, 使用LD_PRELOAD来实现
Line 6: 调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为3
Line 7: fstat64函数获取/etc/ld.so.cache文件信息
Line 8: 调用mmap2函数将/etc/ld.so.cache文件映射至内存
Line 9: close关闭文件描述符为3指向的/etc/ld.so.cache文件
Line12: 调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,这个就是C库
Line15: 使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)
Line24: 调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应
Line25: 对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件
Line26: 子进程结束,退出码为5
可重入函数
可重入就是可重复进入。不仅仅意味着可以重复进入, 还要求在进入后能成功执行。 这里的重复进入, 是指当前进程已经处于该函数中, 这时程序会允许当前进程的某个执行流程再次进入该函数, 而不会引发问题。 这里的执行流程不仅仅包括多线程, 还包括信号处理、 longjump等执行流程。 所以, 可重入函数一定是线程安全的, 而线程安全函数则不一定是可重入函数。
比如下面的代码就会造成死锁:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <pthread.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #include <sys/types.h> 7 8 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 9 static const char * const caller[2] = {"mutex_thread", "signal handler"}; 10 static pthread_t mutex_tid; 11 static pthread_t sleep_tid; 12 static volatile int signal_handler_exit = 0; 13 14 static void hold_mutex(int c) 15 { 16 printf("enter hold_mutex [caller %s]\n", caller[c]); 17 pthread_mutex_lock(&mutex); 18 /* 这里的循环是为了保证锁不会在信号处理函数退出前被释放掉 19 */ 20 while (!signal_handler_exit && c != 1) { 21 sleep(5); 22 } 23 pthread_mutex_unlock(&mutex); 24 printf("leave hold_mutex [caller %s]\n", caller[c]); 25 } 26 27 static void *mutex_thread(void *arg) 28 { 29 hold_mutex(0); 30 } 31 32 static void *sleep_thread(void *arg) 33 { 34 sleep(10); 35 } 36 37 static void signal_handler(int signum) 38 { 39 hold_mutex(1); 40 signal_handler_exit = 1; 41 } 42 43 int main() 44 { 45 signal(SIGUSR1, signal_handler); 46 pthread_create(&mutex_tid, NULL, mutex_thread, NULL); 47 pthread_create(&sleep_tid, NULL, sleep_thread, NULL); 48 pthread_kill(sleep_tid, SIGUSR1); 49 pthread_join(mutex_tid, NULL); 50 pthread_join(sleep_tid, NULL); 51 return 0; 52 }
为什么会死锁呢? 就是因为函数hold_mutex是不可重入的函数——其中使用了pthread_mutex互斥量。 当mutex_thread获得mutex时, sleep_thread就收到了信号, 再次调用就进入了hold_mutex。 结果始终无法拿到mutex, 信号处理函数无法返回, 正常的程序流程也无法继续, 这就造成了死锁。
参考自:
http://blog.csdn.net/guaidaojidewo/article/details/20128989
http://www.cnblogs.com/lidabo/p/4523755.html
标签:data use 代码段 全局 exe ... 第一个 使用 font
原文地址:http://www.cnblogs.com/abc-begin/p/7668720.html