C程序运行的背后(2)
话说上回说到,C程序运行之前,必须要加载到其进程地址空间中。今儿咱就扯扯这个加载到底是怎么加载的。 一图胜前言,这个图简单说明了可执行文件加载过程的逻辑流,在此只做粗粒度概要说明。需要准确描述的,请出门左转,看源码去吧。

1. 程序总是运行在进程上下文(context)中的,当输入./memlayout时,shell会创建一个子进程。除每个进程独有的专属信息外,子进程会继承父进程的大部分资源,如环境变量、进程空间映像等。也就是说,如果不重置子进程的内容,子进程会运行与父进程一样的程序。为了让子进程可以运行别的程序,就要通过execve这个系统调用来指定。
int execve( char *pathname, char *argv[], char *envp[] )
int do_execve( char *pathname, char *argv[], char *envp[] ,struct pt_regs *regs ) // pathname -> 可执行文件名指针
// argv -> 参数指针
// envp -> 环境变量指针
// pt_regs -> 用来保存切换到内核前用户空间的寄存器值
就像春风秋雨一样,原本一切都是那么自然,自然到你忍不住向女神表白,然后女神跟你说“你是个好人”。当execve向do_execve说我要你时,却凭空多出了一个变量struct pt_regs,这绝不不可以说不任性!这是因为,do_execve中的参数命令行参数指针、环境变量指针,会被内核用来设置子进程的用户栈。而根据系统调用约定(calling conventions),Linux和Unix在系统调用时有一个不同,那就是Linux是用寄存器来传递系统调用参数的,而Unix是通过栈。所以在切换到内核时,就有必要保存传递到寄存器中的参数。而pt_regs这个结构体就是用来保存CPU的寄存器状态的。原来还是那么自然,只是女神已成路人。
// include/asm-i386/ptrace.h
struct pt_regs {
long ebx; // pathname
long ecx; // argv
long edx; // envp
long esi;
long edi;
long eax;
long eip;
……
}
2. 那么do_execve要大发神威了吧?切莫急先。物理学告诉我们,力都是有一个作用对象的,就好比我们追的都是女神。而do_execve的操作对象是一个叫struct linux_binprm的结构体,它用来保存要执行的文件的相关信息。do_execve会调用load_binprm,将需要的可执行文件信息都加载到linux_binprm中,包括可执行文件的ELF头信息、路径名、参数字符串、环境变量字符串等等。(注意:load_binprm并不是一个真正意义上的函数,为了方便理解,用它来概括表示由do_execve完成的填充任务。)
struct linux_binprm {
char buf[BINPRM_BUF_SIZE]; //保存可执行文件的头128字节
struct page *page[MAX_ARG_PAGES]; // 保存参数、环境变量
struct mm_struct *mm;
unsigned long p; //当前内存页最高地址
int sh_bang;
struct file * file; //要执行的文件
int e_uid, e_gid; //要执行的进程的有效用户ID和有效组ID
kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
void *security;
int argc, envc; //命令行参数和环境变量数目
char * filename; //要执行的文件的名称
char * interp; //要执行的文件的真实名称,通常和filename相同
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
}
接下来,do_execve会调用search_binary_handler()来查找可执行文件内容的处理程序。对于ELF格式的文件,则调用相应的load_elf_binary(),如果是a.out格式,则调用load_aout_binary()。在根据可执行文件中的section信息并将它们加载到进程空间前,load_elf_binary会首先将进程空间清空,然后将可执行文件映像加载到进程空间中。另外,load_elf_binary还会设置好用户栈:
调用setup_arg_pages,将linux_binprm.page中的参数、环境变量等字符串映射到用户栈中;
调用create_elf_tables,将argc、argv、envp以及一些的“辅助向量(auxiliary vector)”压入到用户栈中。
辅助向量,是内核向用户空间的应用程序传递信息的机制之一,主要供动态链接器(ld-linux.so)使用。
之前的图是从《深入理解计算机系统》拷过来的,并不详细,于是重新画了一个:

argc下面的地址才是栈真正开始的地方。
3. 终于草原已经准备好了,可以策马奔腾啦。一切又回到load_elf_binary()中,不过接下来就是见证神奇的时候啦,因为load_elf_binary要调用start_thread啦。不要小瞧这个调用,它不亚于数学老师跟我们说”我要变形了“的效果。
#define start_thread(regs, new_eip, new_esp) do { \
__asm__("movl %0,%%fs ; movl %0,%%gs": :"r" ()); \
set_fs(USER_DS); \
regs->xds = __USER_DS; \
regs->xes = __USER_DS; \
regs->xss = __USER_DS; \
regs->xcs = __USER_CS; \
regs->eip = new_eip; \
regs->esp = new_esp; \
} while ()
start_thread的实际调用是这样的start_thread(regs,elf_entry, bprm->p),其中__USER_*是前面提到的进程切换到内核前的寄存器值;elf_entry就是可执行文件的入口点,也就是C启动代码的入口位置,赋给eip;bprm->p就是用户栈的栈顶位置,赋给esp。接下来就会跳转到eip指向的C启动代码起始位置开始运行。
至于从C启动代码到main的距离,咱以后再表。
参考:
1 do_execve的具体内容:http://wenku.baidu.com/view/e97820ee4afe04a1b071de32.html
2 ELF文件加载详细流程:http://www.longene.org/techdoc/0328130001224576708.html
3 辅助向量:http://www.tuicool.com/articles/MNRJVj
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;
mso-font-kerning:1.0pt;}
C程序运行的背后(2)的更多相关文章
- C程序运行的背后(1)
一个成功的男人背后,至少有一个伟大的女人:一个不成功的男人,至少有一双手. 而一个C程序,无论成功不成功,它的背后一定有一个操作系统,一个shell,一套工具链. 世界本就不公平.隐藏在显而易见的事实 ...
- elf 文件格式探秘——程序运行背后的故事
摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...
- 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究
原文:查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 -理解性能疑点 1 引言 内容来自http://www.so ...
- 图文浅析APK程序运行的过程
概述 APK程序运行过程有别于FrameWork底层启动过程,它们是倆码事,本文将以图文方式总结一下APK启动的过程,主要分为一下部分 [1]基本概念 [2]APK过程 1 .新的知识点 [1]什么是 ...
- 从hello world 说程序运行机制
转自:http://www.cnblogs.com/yanlingyin/archive/2012/03/05/2379199.html 开篇 学习任何一门编程语言,都会从hello world 开始 ...
- golang获取程序运行路径
golang获取程序运行路径: /* 获取程序运行路径 */ func getCurrentDirectory() string { dir, err := filepath.Abs(filepath ...
- linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...
- 放在NSArray、NSDictionary等容器内的对象Item,Item中的property在程序运行过程中被无故释放
可能是被释放的property本身是OC对象而它的属性被误写成assign,例如: @interface MyItem : Object @property (nonatomic, assign) N ...
- ABAP程序运行锁定
转自http://www.cnblogs.com/aBaoRong/archive/2012/06/15/2550458.html ABAP 程序运行锁 1. create a Table ZRUNN ...
随机推荐
- JWT 拓展
JWT适用场景 https://www.jianshu.com/p/af8360b83a9f 适用于一次性操作的认证,颁布一个很短过期时间的JWT给浏览器. 无状态的JWT无法实现精确的在线人数统计. ...
- How to read source code[repost]
https://github.com/benjycui/benjycui.github.io/blob/master/posts/how-to-read-open-source-javascript- ...
- Ice Cream Tower Gym - 101194D (贪心 + 二分 )
题目链接 : https://cn.vjudge.net/problem/Gym-101194D 题目大意 : 给你n个冰激凌球,让你用这些冰激凌球去垒冰激凌,要求是下面的这一个必须是他上面一个的两倍 ...
- 自己看之区间DP
//菜鸡制作,看的时候可能三目运算符略烦;;; 区间DP入门题:Brackets 地址:http://59.77.139.92/Problem.jsp?pid=1463 分析(对区间DP的代码原理进行 ...
- 五. Jmeter--HTTP Cookie Manager
1. 添加HTTP Cookie Manager 2.添加登录login http,request info 和 HTTP Header Manager 中的信息是从fiddler中拿的, 至于hea ...
- PEB及LDR链
PEB地址的取得在NT内核系统中fs寄存器指向TEB结构,TEB+0x30处指向PEB结构,PEB+0x0c处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c处存放一些指向动态链接 ...
- python设计模式之装饰器详解(三)
python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式. 系列文章 python设计模式之单例模 ...
- git命令大全【转】
转自:http://www.jqhtml.com/8235.html 初始化本地git仓库(创建新仓库) git init 配置用户名 git config --global user.name &q ...
- sql server 2008 r2 产品密钥
数据中心版:PTTFM-X467G-P7RH2-3Q6CG-4DMYBDDT3B-8W62X-P9JD6-8MX7M-HWK38==================================== ...
- 338.Counting Bits---位运算---《剑指offer》32
题目链接:https://leetcode.com/problems/counting-bits/description/ 题目大意:求解从0到num的所有数的二进制表示中所有1的个数. 法一:暴力解 ...