gcc hello.c -o hello.exe
gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。
预处理 => 编译 => 汇编 => 链接
预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作。
gcc -E hello.c -o hello.i
编译:gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
gcc –S hello.i –o hello.s
-S:该选项只进行编译而不进行汇编,生成汇编代码。
汇编:把编译阶段生成的.s文件
转成二进制目标代码.
gcc –c hello.s –o hello.o
链接:将编译输出.o文件
链接成最终的可执行文件。
gcc hello.o –o hello
运行:若链接没有-o指明,则生成可执行文件默认为a.out
./hello
a.out——COFF——ELF(Linux)或PE(Windows)
重点介绍ELF——三种重要格式文件
列出/usr/bin下的目录信息
$ ls -l /usr/bin
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
int main(int argc, char *argv[], char *envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
库函数exec*都是execve的封装例程
shell程序 => execve => sys_execve
初始化新程序堆栈时拷贝进去
关注:load_elf_binary
load_elf_binary(...)
{
...
kernel_read();//其实就是文件解析
...
//映射到进程空间 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依赖动态库的话
{
...
//装载ld的起点 #获得动态连接器的程序起点
elf_entry=load_elf_interp(...);
...
}
else //静态链接
{
...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);
}
主要过程是动态链接器完成、用户态完成。
/*准备.so文件*/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
/*编译成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32
/*使用库文件(因为已经包含了头文件所以可以直接调用函数)*/
SharedLibApi();
dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example
/*编译成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32
/*使用库文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加载进来
int (*func)(void);//声明一个函数指针
func = dlsym(handle,"DynamicalLoadingLibApi");//根据名称找到函数指针
func(); //调用已声明函数
$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/*将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。*/
execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。
fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。
execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。
- 执行到可执行程序 -> 陷入内核
- 构造新的可执行文件 -> 覆盖掉原可执行程序
- 返回到新的可执行程序,作为起点(也就是main函数)
- 需要构造其执行环境;
load_elf_interp
庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)。
static struct linux_binfmt elf_format//声明一个全局变量 = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,//观察者自动执行
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
static int __iit init_elf_binfmt(void)
{n
register_binfmt(&elf_format);//把变量注册进内核链表,在链表里查找文件的格式
return 0;
}
原文:http://www.cnblogs.com/20135207oneking/p/5370665.html