栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

重学计算机(十三、程序从main开始的么?)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

重学计算机(十三、程序从main开始的么?)

这一篇来一点比较硬核的东西,程序是main函数开始的么?

13.1 程序是从main函数开始的么? 13.1.1 gcc编译详细输出

在我们学习c语言的时候,老师是不是一直都在说c程序是从main函数开始的,然后我们写代码的时候,其实也都是从main函数开始写,编译执行之后打印也是从main函数打印,是不是c程序从main函数开始执行,就很根深蒂固一样,这次我们就来推翻一下。

我们来写一个代码:

#include 

int main(int argc, char **argv)
{
    printf("hello worldn");
    return 0;
}

又是熟悉的hello world,讲了十几篇了,好像又回到了原点。

root@ubuntu:~/c_test/13# gcc test.c -o test
root@ubuntu:~/c_test/13# ./test
hello world

又是编译,运行,好像还是熟悉的配方啊,没有其他变化。

我们用一个gcc的一个-v参数,(之前讲编译链接的时候忘记了,尴尬,不过现在补回来也不错)

/usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccp83T0j.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o test /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. /tmp/ccQmBcOv.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o

collect2就是我们之前说的链接器ld,截取的这一部分其实就是链接部分,通过查看确实发现有以下的.o文件,参与链接:crt1.o、crti.o、crtbegin.o。

但是这一点也不能证明这些.o会在main函数之气运行啊。

13.1.2 链接脚本

大家是不是忘记了,程序链接的时候,是通过链接器来控制的,如果我们没有指定链接器,那就是默认的连接器,忘记的可以回到这一篇文章学习学习:重学计算机(五、静态链接和链接控制)。

现在我们就截取一段有用的过来就可以了:

root@ubuntu:/usr/lib/ldscripts# ld -verbose
==================================================


OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
	      "elf64-x86-64")		
OUTPUT_ARCH(i386:x86-64)    
ENTRY(_start)				
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");

SECTIONS	
{
  
  
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;  
  .interp         : { *(.interp) }		// *是通配符,表示所有文件的.interp段都符合条件
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
    //在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的
  }
  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  }
  .init_array     :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array     :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
}

通过这个文件我们看到函数入口是ENTRY(_start),这个,这次确定不是main函数开始了吧,我还留下了.init和.fini段,说明.text之前和之后都会有代码执行的。

我们稍微修改一下上面的代码:

#include 

// static void __attribute__((section(".init"))) init_main(void)
// {
//     printf("init mainn");
// }

static void __attribute__ ((constructor)) before_main(void)   // main函数之前
{
    printf("befor mainn");
}

static void __attribute__ ((destructor)) after_main(void)  // main函数之后
{
    printf("after mainn");
}

// static void __attribute__((section(".fini"))) fini_main(void)
// {
//     printf("fini mainn");
// }

int main(int argc, char **argv)
{
    printf("hello worldn");

    return 0;
}

用__attribute__来指定一下函数的属性,编译运行:

root@ubuntu:~/c_test/13# gcc test.c -o test
root@ubuntu:~/c_test/13# ./test
befor main
hello world
after main
root@ubuntu:~/c_test/13# 

发现确实是在main函数之前和之后,这里是不是有人就疑问了,为啥要把init和fini段屏蔽了,其实这两段代码打开的话,会出现段错误,虽然打印也可以,但是打印完了,就段错误了,这个问题记录一下,之后又时间再回来分析分析。还有关于__attribute__的可以看看这篇文章,写的还挺不错的:几个有用的gcc attribute介绍

13.1.3 _start函数

我这个系统是ubuntu64位的,所以_start.S是在:sysdepsx86_64start.S中。

我们拷贝出来分析一下:

都是汇编+英语,一看就头大,还是用翻译软件来翻译翻译。


#include 

ENTRY (_start)
	
	cfi_undefined (rip)
	
	
	xorl %ebp, %ebp

	

	mov %RDX_LP, %R9_LP	
#ifdef __ILP32__
	mov (%rsp), %esi	
	add $4, %esp
#else
	popq %rsi		
#endif
	
	mov %RSP_LP, %RDX_LP
	
	and  $~15, %RSP_LP

	
	pushq %rax

	
	pushq %rsp

#ifdef PIC    //动态链接走这一步
	
	mov __libc_csu_fini@GOTPCREL(%rip), %R8_LP		// 这两个继续赋值
	mov __libc_csu_init@GOTPCREL(%rip), %RCX_LP

	mov main@GOTPCREL(%rip), %RDI_LP			// main函数地址也存放在rdi中
#else
	
	mov $__libc_csu_fini, %R8_LP
	mov $__libc_csu_init, %RCX_LP

	mov $main, %RDI_LP
#endif

	
	call *__libc_start_main@GOTPCREL(%rip)

	hlt			
END (_start)

x86的汇编确实让人头大,上面也简单分析了一波,看的不是很懂,感觉是给__libc_start_main填充了各种参数。可以看一下这一篇,写的还不错[《程序员的自我修养》第十一章读书笔记]

13.1.4 __libc_start_main函数

接下来,就跟着跳转函数一起走一遍了:

这个__libc_start_main 在csu/libc-start.c中,

STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
		 int argc, char **argv,
		 __typeof (main) init,
		 void (*fini) (void),
		 void (*rtld_fini) (void), void *stack_end)
    {
  
  int result;

  __libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up;

#ifndef SHARED
  _dl_relocate_static_pie ();

  char **ev = &argv[argc + 1];

  __environ = ev;    // 取到环境变量

  
  __libc_stack_end = stack_end;



      // 初始化多线程的吧
  if (__pthread_initialize_minimal != NULL)
    __pthread_initialize_minimal ();


#endif 

  
  if (__glibc_likely (rtld_fini != NULL))
    __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);
    //注册动态链接器的析构函数(如果有的话)

#ifndef SHARED
  
  __libc_init_first (argc, argv, __environ);      // 初始化libc库

  
    //注册程序的析构函数(如果有的话)
  if (fini)
    __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);

  
  if (__builtin_expect (__libc_enable_secure, 0))
    __libc_check_standard_fds ();
#endif

  if (init)
    (*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);    // init函数执行


  
  result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);   // main函数在这里,看到这里松了好多口气

  exit (result);
}

这个函数本来是很多的,觉得被我删的差不多,功力不够之前,真的没有必要去深入研究各行代码,太难了,去掉一些不需要的,留下重点的就可以了,真的是太难了。

13.2 exit()函数

看到了linux应用到内核,都介绍了exit函数了,我也跟着看看吧,太难的话就立马撤退。

void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}

会调用__run_exit_handlers。

__run_exit_handlers函数只要是把那些注册在进程退出的时候,进行清理工作的函数,都执行了一遍,最后调用_exit()函数。

void
_exit (int status)
{
  while (1)
    {
#ifdef __NR_exit_group
      INLINE_SYSCALL (exit_group, 1, status);    //这好像是多线程退出
#endif
      INLINE_SYSCALL (exit, 1, status);		// 这是之前的退出

      // 看着这个调用就知道是内核中的系统函数了
      
#ifdef ABORT_INSTRUCTION
      ABORT_INSTRUCTION;
#endif
    }
}

内核的就先不看了,有缘再见了,太硬核分析好像也不行。

13.3 总结

这一篇虽然比较硬核,但是就作为了解的吧,知道这么回事就可以了,以后有机会再去分析内核的部分吧,现在就先刹住车。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/702179.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号