从main函数启动过程说起
来源:互联网 发布:通信工程学java有用吗 编辑:程序博客网 时间:2024/05/17 09:44
先贴代码:
#include <stdio.h>const int A=10;static int b=30;static char msg[]="hello";int c;static int add(int a,int b){ return a+b;}void printmsg1(int a,int b){ int d= add(a,b); printf("%d\n",d);}void printmsg2(){ printf("%s\n",msg);}int main(int argc,void* argv[]){ static int a=40; register int c=50; printmsg1(a,c); printmsg2(); printf("hello"); return 0;}
变量的存储结构
使用ReadElf -a main可以查看全局变量和局部变量是如何在内存中存储布局
49: 0804a018 4 OBJECT LOCAL DEFAULT 24 b 50: 0804a01c 6 OBJECT LOCAL DEFAULT 24 msg 52: 0804a024 4 OBJECT LOCAL DEFAULT 24 a.1724 72: 08048580 4 OBJECT GLOBAL DEFAULT 15 A 76: 0804a030 4 OBJECT GLOBAL DEFAULT 25 c从中可以看出b、msg、a.1724存放在section 24,A存放在Section 15,c存放在Section 25。分别对应:.data、.bss、.rodata。
上面的LOCAL代表变量被static修饰,不会被链接器处理。GLOBAL代表变量没有被static修饰,会被链接器处理。
a.1724表示main函数中的a变量,由于它被static修饰因此不像局部变量在函数调用时分配内存函数退出时释放内存,而是一个全局变量只不过不能被链接器处理,且添加了后缀以区分之前的全局变量a。
readelf -l main输出:
Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .ctors .dtors .jcr .dynamic .got
可以看到.rodata和.text存放在一个Segments中,可以被保护成只读。
.data和.bss存放在另外一个Segments中
对于函数中的局部变量,则使用栈结构进行内存分配和释放。
objdump -dS main.o输出反汇编
static int a=40;register int c=50; 8048471:bb 32 00 00 00 mov $0x32,%ebxprintmsg1(a,c); 8048476:a1 24 a0 04 08 mov 0x804a024,%eax 804847b:89 5c 24 04 mov %ebx,0x4(%esp) 804847f:89 04 24 mov %eax,(%esp) 8048482:e8 9b ff ff ff call 8048422 <printmsg1>
和栈相关的寄存器有两个esp和ebp,分别标示栈顶和栈基。假设原来的esp的值为NN的话
调用printmsg1之前,先将c和a入栈,参数入栈的顺序是从右到左。call指令执行完成时,c保存在NN+4,a保存在NN;esp指向NN-4(这是因为call指令压入返回地址,上图应该是8048486)
再来看看printmsg1的反汇编
08048422 <printmsg1>:void printmsg1(int a,int b){ 8048422:55 push %ebp 8048423:89 e5 mov %esp,%ebp 8048425:83 ec 28 sub $0x28,%espint d= add(a,b); 8048428:8b 45 0c mov 0xc(%ebp),%eax 804842b:89 44 24 04 mov %eax,0x4(%esp) 804842f:8b 45 08 mov 0x8(%ebp),%eax 8048432:89 04 24 mov %eax,(%esp) 8048435:e8 da ff ff ff call 8048414 <add> 804843a:89 45 f4 mov %eax,-0xc(%ebp)printf("%d\n",d); 804843d:b8 84 85 04 08 mov $0x8048584,%eax 8048442:8b 55 f4 mov -0xc(%ebp),%edx 8048445:89 54 24 04 mov %edx,0x4(%esp) 8048449:89 04 24 mov %eax,(%esp) 804844c:e8 cf fe ff ff call 8048320 <printf@plt>}首先将ebp栈基保存到NN-8,将ebp重新指向NN-8,esp更新为NN-8-0x28。这样ebp和esp分别指向新的栈基和栈顶。
在调用函数之前,总是这样的:
- 将最右边的参数先压栈,左边的参数后压栈
- 进入调用函数后,再将调用函数的栈帧顶部压栈,同时改变当前的栈帧(通过修改ebp)
main文件链接过程
使用readelf -s main.o可以查看符号是否定义:
htm@htm:~/test/testassemble$ readelf -s main.oSymbol table '.symtab' contains 19 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS main.c 2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 3 4: 00000000 0 SECTION LOCAL DEFAULT 4 5: 00000000 0 SECTION LOCAL DEFAULT 5 6: 00000000 4 OBJECT LOCAL DEFAULT 3 b 7: 00000004 6 OBJECT LOCAL DEFAULT 3 msg 8: 00000000 14 FUNC LOCAL DEFAULT 1 add 9: 0000000c 4 OBJECT LOCAL DEFAULT 3 a.1724 10: 00000000 0 SECTION LOCAL DEFAULT 7 11: 00000000 0 SECTION LOCAL DEFAULT 6 12: 00000000 4 OBJECT GLOBAL DEFAULT 5 A 13: 00000004 4 OBJECT GLOBAL DEFAULT COM c 14: 0000000e 49 FUNC GLOBAL DEFAULT 1 printmsg1 15: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf 16: 0000003f 20 FUNC GLOBAL DEFAULT 1 printmsg2 17: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts 18: 00000053 63 FUNC GLOBAL DEFAULT 1 main上面可以看到printf和puts都没有定义,在main.c中我们也找不到这两个函数的定义,肯定是在其他地方定义的。不过在编译过程,printf和puts是如何链接过来的?并且链接的是哪个文件?
htm@htm:~/test/testassemble$ ld main.o -o mainld: warning: cannot find entry symbol _start; defaulting to 0000000008048094main.o: In function `printmsg1':main.c:(.text+0x39): undefined reference to `printf'main.o: In function `printmsg2':main.c:(.text+0x4d): undefined reference to `puts'main.o: In function `main':main.c:(.text+0x81): undefined reference to `printf'可以猜想,肯定少了某些链接文件,编译的最后阶段,gcc应该是自动添加了一些链接文件。
htm@htm:~/test/testassemble$ gcc -v main.o -o mainUsing built-in specs.Target: i686-linux-gnuConfigured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.7-1ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnuThread model: posixgcc version 4.4.7 (Ubuntu/Linaro 4.4.7-1ubuntu2) COMPILER_PATH=/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/LIBRARY_PATH=/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/4.4.7/:/usr/lib/gcc/i686-linux-gnu/4.4.7/../../../i386-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.4.7/../../../../lib/:/lib/i386-linux-gnu/:/lib/../lib/:/usr/lib/i386-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/i686-linux-gnu/4.4.7/../../../:/lib/:/usr/lib/COLLECT_GCC_OPTIONS='-v' '-o' 'main' '-mtune=generic' '-march=i686' /usr/lib/gcc/i686-linux-gnu/4.4.7/collect2 --build-id --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -o main -z relro /usr/lib/gcc/i686-linux-gnu/4.4.7/../../../i386-linux-gnu/crt1.o /usr/lib/gcc/i686-linux-gnu/4.4.7/../../../i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.4.7/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.4.7 -L/usr/lib/gcc/i686-linux-gnu/4.4.7 -L/usr/lib/gcc/i686-linux-gnu/4.4.7/../../../i386-linux-gnu -L/usr/lib/gcc/i686-linux-gnu/4.4.7/../../../../lib -L/lib/i386-linux-gnu -L/lib/../lib -L/usr/lib/i386-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/i686-linux-gnu/4.4.7/../../.. main.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-linux-gnu/4.4.7/crtend.o /usr/lib/gcc/i686-linux-gnu/4.4.7/../../../i386-linux-gnu/crtn.o
从上面可以看出,链接过程其实自动包含了其他文件:crt1.o crti.o crtbeginT.ocrtend.o crtn.o等。
上图中printf在编译器中优化成puts(这样数据就能直接发送出去而不是先放在缓冲区)。puts被保存在libc中,本例使用的动态链接库,可以在库文件中找到。
那么main文件是如何执行的?
一般说来,不同的对象文件链接成为一个可执行文件的过程:将各个对象文件中不同的section进行合并,同时将对象文件中的符号所代表的地址进行重新赋值。例如mian.o中的printmsg2符号地址值为0x3f,而在main文件中地址被修改为0x8048453。
readelf -s main.o | grep printmsg2
readelf -s main | grep printmsg2
调用printmsg时,在main.o对象文件中,由于不知道printmsg2的加载地址,所以随便写了一个地址call 74 <main+0x21>
这个无效地址在连接过程中被改变为 call 8048453 <printmsg2>
链接器如何知道call 74就代表着调用printmsg2?
答案在main.o的.rel.text字段中,在这里定义了所有需要重定向的符号,连接器从这里找到哪些需要重新定义加载地址的符号
Relocation section '.rel.text' at offset 0xcf0 contains 9 entries: Offset Info Type Sym.Value Sym. Name0000002a 00000801 R_386_32 00000000 .rodata00000039 00001702 R_386_PC32 00000000 printf00000048 00000301 R_386_32 00000000 .data0000004d 00001902 R_386_PC32 00000000 puts00000063 00000301 R_386_32 00000000 .data0000006f 00001602 R_386_PC32 0000000e printmsg100000074 00001802 R_386_PC32 0000003f printmsg200000079 00000801 R_386_32 00000000 .rodata00000081 00001702 R_386_PC32 00000000 printf上述文件中offset表示对象文件中偏移多少字节需要替换。例如printf的地址在main.o中的第0x81个字节偏移。
可执行文件的起点
前面说一般_start代表着程序执行的起点,这是因为在链接过程使用了默认的链接脚本中定义的。
htm@htm:~/test/testassemble$ ld -verboseGNU ld (GNU Binutils for Ubuntu) 2.22using internal linker script:==================================================/* Script for -z combreloc: combine and sort reloc sections */OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")OUTPUT_ARCH(i386)ENTRY(_start)SEARCH_DIR("/usr/i686-linux-gnu/lib32"); SEARCH_DIR("=/usr/local/lib32"); SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib");SECTIONS{ /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS; .interp : { *(.interp) } .note.gnu.build-id : { *(.note.gnu.build-id) } .hash : { *(.hash) } .gnu.hash : { *(.gnu.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .gnu.version : { *(.gnu.version) } .gnu.version_d : { *(.gnu.version_d) } .gnu.version_r : { *(.gnu.version_r) } .rel.dyn : { *(.rel.init) *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) *(.rel.fini) *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*) *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) *(.rel.ctors) *(.rel.dtors) *(.rel.got) *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) *(.rel.ifunc) } .rel.plt : { *(.rel.plt) PROVIDE_HIDDEN (__rel_iplt_start = .); *(.rel.iplt) PROVIDE_HIDDEN (__rel_iplt_end = .); } .init : { KEEP (*(.init)) } =0x90909090 .plt : { *(.plt) *(.iplt) } .text : { *(.text.unlikely .text.*_unlikely) *(.text.exit .text.exit.*) *(.text.startup .text.startup.*)
ENTRY(_start)指定了_start为程序的起点,这个起点也可以被修改。
上面的链接脚本文件在UBoot的源码中也见过。- 从main函数启动过程说起
- 从main说起
- Memcached 源码分析——从 main 函数说起
- WPF 从Main函数启动
- linux编程之main()函数启动过程
- 重学C++Primer笔记1---从main函数说起之argc,argv,%ERRORLEVEL%
- 单片机的启动过程(从上电到main)
- (1)从listen函数说起
- C51 main()函数和启动代码 --- 从汇编到c51
- VB.NET 从main函数里启动窗口
- nginx源码剖析 从main函数看nginx启动流程
- 文章2:nginx启动过程之main函数
- Linux kernel 分析之二:main函数执行启动过程
- 文章2:nginx启动过程之main函数
- 从开机加电到执行main函数之前的过程
- 从开机加电到执行main函数之前的过程
- 从开机加电到执行main函数之前的过程
- bootloader 从开发板上电到main()函数执行之间的过程
- Lucene 工作原理
- 软件开发的相关技术
- iOS AFNetWorking 数据解析
- ReactiveCocoa配置相关
- 我为什么选择Win32SDK以及Win32SDK的特点
- 从main函数启动过程说起
- 黑马程序员——面向对象---继承
- Oracle学习(十)之日志存档模式
- linux 内核编译:内核配置原理与常见配置问题的解决方法&&内核版本控制解析
- 异常 frameanimation ClassCastException/Android studio Error:Unable to start the daemon process
- iOS开发 动画 UIDynamicAnimator
- Libevent0.1学习之queue.h
- SNMP:简单网络管理协议
- webservice注解 答疑