linux可执行文件都是通过调用execve
函数来调用加载器的.
加载器将可执行文件的代码和数据从磁盘拷贝到内存中, 然后通过第一条指令来查找程序运行的入口, 从而执行整个程序. 而将数据从磁盘复制到内存的过程就叫做加载.
通过从内核设置的第一条指令找到程序的入口, 一般gcc默认编译程序的入口是_libc_start_main
这一个默认函数, 而默认函数的入口又是存放在段<_start>
里面. _start函数调用系统启动函数, 它初始化环境, 调用用户层的main函数, 处理main函数的返回值, 最后将返回值返回给内核处理.
一个简单的代码
int main()
{ }
执行
gcc main.c
在执行
objdump a.out -d
# 如果适合看intel的汇编, 可加上 -M intel 即可
可以看到, call __libc_start_main
调用此函数, 而它是在<_start>
段中 . 在<_start>断之前还有3个段.
<_init>
段程序会初始化调用ELF
中.init
函数. 有兴趣可以去查一下ELF.
如果想要自己来设置程序的入口的, 可以在gcc中添加禁用mian入口的命令
gcc -nostartfiles -e 入口 filename.c
-nostartfiles : 关掉gcc默认的main函数作为入口
-e : 设置程序开始的入口
// 简单的尝试
#include
#include
void print()
{
printf("print\n");
exit(0);
}
如果在程序的结尾处没有加上exit函数, 调用return也会出错, 报出断错误. 这里主要是exit函数会直接告诉系统该程序退出, 而return
只是将程序返回, 我们并没有将程序地址压栈, 所以return返回的地址就会产生越界, 连个如果都没有, 那么程序也就不知道它自己执行结束.
同样我们objdump看一下, 此时程序的入库设置成了print函数, 结束的时候在调用exit, 杀死该进程.
没有调用exit的函数, 则显示的便是这样.
对exit和return不太区分的, 也可以看看这一篇博客.exit和return的区别
如果是自己写的OS程序启动, 可在boot.s中修改入口, call 入口函数名就行了