深入浅出Hello World 1

首先的感谢那些无私奉献的大牛们,深入Hello World下载地址在http://blog.linux.org.tw/~jserv/archives/001844.html。在上面

还有源码的下载地址链接,同时还要感谢那些网上的勤勤恳恳写blog的bloger们。

Hello World是学习程序设计语言的第一个程序浅出 Hello World。我们试图分析自linux上的Hello World运行的整个过程,主要

包括下面的几个过程:

1.hello程序的编译链接过程和hello上可执行文件格式

2.hello可执行程序的加载及如何开始执行

3.hello在内存中镜像

4.寻址

5.调度程序

6.内存管理

7.系统调用

8.hello程序卸载

首先是hello可执行文件的连接过程,然后hello可执行文件如何被加载到内存中,然后从那里开始执行?在执行的过程中,可

能需要寻址,如何实现?可能的内核调度如何实现?内存管理如何实现?系统调用如何实现?hello程序执行完成之后,kernel

执行了那些清理的工作?

hello程序编译链接过程和hello可执行elf文件格式

vim hello.c

#include <stdio.h>
int main (int atgc, char* argv[])
{
printf ("Hello World");
return 0;
}

gcc hello.c -o hello

首先在Text Editor中编辑hello的source code,gcc在编译source code时,先执行预处理,完成将source code中的宏定义展开de

等功能,这个过程是由cpp完成,最终生成hello.i文件,然后由complier编译成hello.s,这个过程是由ccl完成,然后使用as

将上面的hello.s编译成hello.o,最后使用ld将上面生成的hello.o连接成可执行文件hello。

我们可以使用下面的命令来观察生成的hello程序:

file hello

hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux

2.6.15, not stripped

ldd hello

linux-gate.so.1 => (0xb7f01000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7d89000)
/lib/ld-linux.so.2 (0xb7f02000)
objdump -x hello

hello: file format elf32-i386
hello
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048310
Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x00000100 memsz 0x00000100 flags r-x
INTERP off 0x00000134 vaddr 0x08048134 paddr 0x08048134 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x000004c0 memsz 0x000004c0 flags r-x
LOAD off 0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**12
filesz 0x00000108 memsz 0x00000110 flags rw-
DYNAMIC off 0x00000f20 vaddr 0x08049f20 paddr 0x08049f20 align 2**2
filesz 0x000000d0 memsz 0x000000d0 flags rw-
NOTE off 0x00000148 vaddr 0x08048148 paddr 0x08048148 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rw-
RELRO off 0x00000f0c vaddr 0x08049f0c paddr 0x08049f0c align 2**0
filesz 0x000000f4 memsz 0x000000f4 flags r--
反汇编:objdump -d hello

hello: file format elf32-i386
Disassembly of section .init:
08048294 <_init>:
8048294: 55 push %ebp
8048295: 89 e5 mov %esp,%ebp
8048297: 53 push %ebx
8048298: 83 ec 04 sub $0x4,%esp
804829b: e8 00 00 00 00 call 80482a0 <_init+0xc>
80482a0: 5b pop %ebx
80482a1: 81 c3 54 1d 00 00 add $0x1d54,%ebx
80482a7: 8b 93 fc ff ff ff mov -0x4(%ebx),%edx
80482ad: 85 d2 test %edx,%edx
80482af: 74 05 je 80482b6 <_init+0x22>
80482b1: e8 1e 00 00 00 call 80482d4 <__gmon_start__@plt>
80482b6: e8 e5 00 00 00 call 80483a0 <frame_dummy>
80482bb: e8 a0 01 00 00 call 8048460 <__do_global_ctors_aux>
80482c0: 58 pop %eax
80482c1: 5b pop %ebx
80482c2: c9 leave
80482c3: c3 ret
Disassembly of section .plt:
...

经过上面的观察,产生下面的疑问:ldd链接的文件的作用是什么?链接程序时发生了什么?可执行文件的格式是怎样的?为了解决上面的问题还

是得首先了解一下计算机程序的基础知识。

1.bss/data/code段,对于下面的程序:

int a;

int k = 3;

int foo (void)

{

return (k);

}

int b = 12;

int bar(void)

{

a = 0;

return (a + b);

}

在生成的汇编文件中,bss/data/text段如下:

------------------------

a = 0 bss

-----------------------

k = 3 b = 12 data

----------------------

ret text

----------------------

于是根据汇编文件生成的可执行文件镜像大致结构如下:

---------------------

12 data

3

---------------------

ret text

----------------------

header

---------------------

那么汇编文件中的bss段怎么没有了?在可执行文件的镜像中,在header中包含有该文件bss段的信息 。在kernel装载该hello可执行文件时,会产生

如下的内存镜像 :

----------------------- bss,kernel根据hello的文件头header来得到bss段的信息

0

-----------------------

12 3 data

----------------------

ret text

---------------------

上面的只是一个文件的情况,没有涉及到链接,那如果是两个.o文件,ld链接程序是如何链接程序的?首先需要说明的是每个object file都是具有相同的address space,在链接多个.o文件时,ld所作的工作就是分别将各个object file的bss text data段分别组成到新的bss text data段。最终在生成的elf

文件中:

-----------------------

elf header include magic number, file type, machine,...

-----------------------

program header table page size, virtual address memory segment, segment size

-----------------------

.text section code

-----------------------

.data section initialized data

-----------------------

.bss section bass data (0)

-----------------------

.symtab symbol table

------------------------

.rel.text relocation information for text section

-------------------------

.rel.data relocation information for .data section

-------------------------

.debug debug information

------------------------

section header table

------------------------

对于上面的elf文件elf header可以使用readelf -h filename来读取文件头。

readelf -h hello

ELF Header:

Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

Class: ELF32

Data: 2's complement, little endian

Version: 1 (current)

OS/ABI: UNIX - System V

ABI Version: 0

Type: EXEC (Executable file)

Machine: Intel 80386

Version: 0x1

Entry point address: 0x8048310

Start of program headers: 52 (bytes into file)

Start of section headers: 5996 (bytes into file)

Flags: 0x0

Size of this header: 52 (bytes)

Size of program headers: 32 (bytes)

Number of program headers: 8

Size of section headers: 40 (bytes)

Number of section headers: 36

Section header string table index: 33

对于上面的输出需要注意的是Entry point address: 0x8048310,Entry point address定义了elf文件执行的开始地址。下面是详细的解释:

http://blog.sina.com.cn/s/blog_5b9ea9840100avgg.html###

When executed, program will start running from virtual address 0x80482c0 (see entry point address). The "0x" prefix here means it is a hexadecimal number. This address doesn't point to our main() procedure, but to a procedure named _start. Never felt you had created such thing? Of course you don't. _start procedure is created by the linker whose purpose is to initialize your program.

同时可以使用gdb反汇编:

gdb hello

(gdb) disassemble 0x8048310

Dump of assembler code for function _start:

0x08048310 <_start+0>: xor %ebp,%ebp

0x08048312 <_start+2>: pop %esi

0x08048313 <_start+3>: mov %esp,%ecx

0x08048315 <_start+5>: and $0xfffffff0,%esp

0x08048318 <_start+8>: push %eax

0x08048319 <_start+9>: push %esp

0x0804831a <_start+10>: push %edx

0x0804831b <_start+11>: push $0x80483f0

0x08048320 <_start+16>: push $0x8048400

0x08048325 <_start+21>: push %ecx

0x08048326 <_start+22>: push %esi

0x08048327 <_start+23>: push $0x80483c4

0x0804832c <_start+28>: call 0x80482e4 <__libc_start_main@plt>

0x08048331 <_start+33>: hlt

可见程序是在地址0x8048310开始执行(注意的是此时还是虚地址),然后通过设置调用printf函数前的相关工作,然后call 0x80482e4 <__libc_start_main@plt>调用printf函数。

综上,hello.c源程序,通过编译生成.o文件,然后在通过ld程序链接成elf可执行文件。ld在链接时,默认使用的ld script可以使用命令查看:

ld --verbose。关于链接脚本的解释参见:http://fxl.blogbus.com/logs/12451338.在ld script中指定elf文件的加载地址和虚地址,上面的两种地址

在一般的情况下是相同的,但是在一些嵌入式的程序中加载地址和执行地址是不同的。

既然在elf文件中定义了程序在加载的虚地址,那么程序是如何被加载的?加载的内存镜像又是什么样子的?如何查看实际的物理地址?呵呵,有时间

接着开始...

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值