使用GCC实现一个没有main方法的程序.
一段普通的main方法看起来是这样:
#include <stdio.h>
int main(int argc, char** argv){
printf("hello world\n");
}
然后直接编译运行:
gcc main.c && ./a.out
# 输出如下:
hello world
但是这个main函数真的是入口吗.我们先尝试运行一下程序.在main函数这里打一个断点:
这里发现并没有发现main函数之前还有调用堆栈. 真的是这样的吗?
我们用clion看看, 这里我们就不再被 GDB
蒙蔽. 确实我们的main函数不是最外层的函数. 上面还有: __lib_start_main
以及 _start
函数:
我们反汇编看一下编译出来的结果.
这里可以用
objdump
工具来完成反汇编.但是为了更清晰的展示代码. 这里直接使用了clion
的反汇编结果.
这里可以看到 _start
只是一段 stub
的汇编代码.
// 在Linux上,则是前`6`个参数通过`rdi`,`rsi`,`rdx`,`rcx`,`r8`,`r9`传递,
// 其余的参数按照从右向左的顺序压栈。
_start:
xor %ebp,%ebp # ebp清零 , 让EBP -> stack顶(栈增长方向为 高->低)
mov %rdx,%r9 /* Address of the shared library termination
function. */
pop %rsi /* Pop the argument count. */
# makes **argc** go into **%rsi**
mov %rsp,%rdx /* argv starts just at the current stack top. */
and $0xfffffffffffffff0,%rsp
/* Align the stack to a 16 byte boundary to follow the ABI.*/
push %rax /* Push garbage because we push 8 more bytes. */
push %rsp /* Provide the highest stack address to the user code (for stacks
which grow downwards). */
mov $0x4005c0,%r8
mov $0x400550,%rcx
mov $0x400526,%rdi
call 0x400410 ; <__libc_start_main@plt>
hlt
这段代码大意是在从启动时的寄存器与栈信息中获取相应的参数.然后给调用 __libc_start_main
做准备.这个符号函数的参数定义如下:
/* Extract the arguments as encoded on the stack and set up
the arguments for __libc_start_main (int (*main) (int, char **, char **),
int argc, char *argv,
void (*init) (void), void (*fini) (void),
void (*rtld_fini) (void), void *stack_end).
The arguments are passed via registers and on the stack:
main: %rdi
argc: %rsi
argv: %rdx
init: %rcx
fini: %r8
rtld_fini: %r9
stack_end: stack. */
下面是一段crt0.s 代码. 参考维基百科的注释Crt0 - Wikipedia.我直接贴在这里 , 下面的代码是没有连接C运行时的代码. 有些不一样.不过可以参考其大致含义.:
然后 _start
会再调用: __lib_start_main
最后才进入到我们的 main
方法.