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、envc、envp以及一些的“辅助向量(auxiliary vector)”压入到用户栈中。

此时的用户栈大概是这个样子的:

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" (0));            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 (0)

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

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;}

时间: 2024-12-29 17:24:39

C程序运行的背后(2)的相关文章

.net应用程序运行的背后机制

这篇文章要探讨的问题是:当编译后的应用程序运行时,CLR是如何进行运作的. 1. 准备工作 程序清单Program.cs: public sealed class Pragram { public static void Main() { System.Console.WriteLine("Hi"); } } 在Developer Command Prompt for VS2013中编译上面的代码成为应用程序(程序集) csc.exe Program.cs 运行,程序开始执行. Pro

C程序运行的背后(1)

一个成功的男人背后,至少有一个伟大的女人:一个不成功的男人,至少有一双手. 而一个C程序,无论成功不成功,它的背后一定有一个操作系统,一个shell,一套工具链. 世界本就不公平.隐藏在显而易见的事实背后的,你若能看透,便可以站在对自己公平的那一端. 1.进程地址空间 一个进程一旦建立,就会自认为占有4G内存(X86_32),这个内存被称作虚拟内存,也就是进程的地址空间.在Linux下,进程地址空间的布局大致如下图所示,其中的用户空间大致由这些部分组成: 代码段 初始化数据段 未初始化数据段 堆

图文浅析APK程序运行的过程

概述 APK程序运行过程有别于FrameWork底层启动过程,它们是倆码事,本文将以图文方式总结一下APK启动的过程,主要分为一下部分 [1]基本概念 [2]APK过程 1 .新的知识点 [1]什么是UI线程与Thread线程区别 UI线程并不陌生,但是这玩意到底是啥,与普通线程Thread有啥区别呢? 什么是UI线程: ActivityTread类所在的线程即为UI线程,负责用户交互,处理用户消息绘制界面等 区别: UI线程的ActivityTread中的Main方法已经使用Looper.pr

查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究

原文:查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 -理解性能疑点 1      引言 内容来自http://www.sommarskog.se/query-plan-mysteries.html(Slow in the Application, Fast in SSMS?) [看国内没有人好好翻译过这篇.全部翻译实在太长了,就挑主要的意译了.如果要看完整版,还是建议读原文.先翻译了一半,先解决了眼前的问题.剩下的一般

python入门学习--程序运行、注释

1.程序运行 1.1 python解释器运行 File-new File创建一个新的编辑窗口,输入下图中的内容 选择File-save保存程序(为纯文本文件).建立一个专门存放python项目的目录.然后为程序起一个有意义的名字,比如name.py.文件名以.py结尾非常重要. 现在可以用Edit-Run或者按下Crtl+F5键来运行程序了 输入名字(比如Frank),然后按回车键.会看到 1.2 命令提示符运行  2.注释 上图第一行称为注释.注释既为了让别人能够容易理解程序,也为自已回头再看

转 我修改的注册表,但是程序运行起来,还是记着以前的

我修改的注册表,但是程序运行起来,还是记着以前的,我查看了相关资料,说是修改只是暂时保存在memory,并没有保存到harddisk中,请高手指点一下,我用python写的,代码如下import win32apiimport win32con def RegSubkeySet(dbName, dbVersion):        key = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,'Software\\DSA\\PRODIS Office\\

C#如何加载程序运行目录外的程序集 (转)

---恢复内容开始--- 尼玛,为了这个问题,纠结到差点吐出干血,赶紧记下来! 源地址:http://blog.csdn.net/dyllove98/article/details/9391325 我们的应用程序部署的时候,目录结构一般不会只有运行程序的目录这一个,我们可能在运行目录下建子目录,也可能使用System32目录,也可能使用其它第三方的程序集..Net程序集 首先会在GAC中搜索相应的版本,如果未找到则会应用程序配置文件中找(如果配置),最后到应用程序所在的路径搜索. 如何可以将程序

黑马程序员——java——获取一个应用程序运行的次数,如果超过5次,给出使用次数已到请注册的提示,并不要再运行程序

获取一个应用程序运行的次数,如果超过5次,给出使用次数已到请注册的提示,并不要再运行程序 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; public class Test5 { public static void main(String[] args) th

[视频讲解]Java(JDK的下载安装及第一个程序运行)

(JDK的下载安装及第一个程序运行) 内容:Java JDK 的安装以及HelloWorld 程序的运行 欢迎童鞋们前往围观 http://v.youku.com/v_show/id_XODA3MzkzNzMy.html 更多内容分享请关注 我的博客 http://www.xiaozhangwx.com 本视频由 小张网校 提供