...making Linux just a little more fun! |
By Hyouck "Hawk" Kim |
The question is simple: how does linux execute my main()?
Through this document, I'll use the following simple C program to illustrate
how it works. It's called "simple.c"
main()
{
return(0);
}
gcc -o simple simple.c
To see what's in the executable, let's use a tool "objdump"
objdump -f simpleThe output gives us some critical information about the executable.
simple: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482d0
ELF is acronym for Executable and Linking Format. It's one of several object and executable file formats used on Unix systems. For our discussion, the interesting thing about ELF is its header format. Every ELF executable has ELF header, which is the following.
typedef structIn the above structure, there is "e_entry" field, which is starting address of an executable.
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
For this question, let's disassemble "simple". There are several tools to disassemble an executable. I'll use objdump for this purpose.
objdump --disassemble simpleThe output is a little bit long so I'll not paste all the output from objdump. Our intention is see what's at address 0x080482d0. Here is the output.
080482d0 <_start>:Looks like some kind of starting routine called "_start" is at the starting address. What it does is clear a register, push some values into stack and call a function. According to this instruction, the stack frame should look like this.
80482d0: 31 ed xor %ebp,%ebp
80482d2: 5e pop %esi
80482d3: 89 e1 mov %esp,%ecx
80482d5: 83 e4 f0 and $0xfffffff0,%esp
80482d8: 50 push %eax
80482d9: 54 push %esp
80482da: 52 push %edx
80482db: 68 20 84 04 08 push $0x8048420
80482e0: 68 74 82 04 08 push $0x8048274
80482e5: 51 push %ecx
80482e6: 56 push %esi
80482e7: 68 d0 83 04 08 push $0x80483d0
80482ec: e8 cb ff ff ff call 80482bc <_init+0x48>
80482f1: f4 hlt
80482f2: 89 f6 mov %esi,%esi
Stack Top -------------------
0x80483d
-------------------
esi
-------------------
ecx
-------------------
0x8048274
-------------------
0x8048420
-------------------
edx
-------------------
esp
-------------------
eax
-------------------
Now, as you already wonder,we've got a few questions regarding this stack
frame.
Let's answer these questions one by one.
If you look at disassembled output from objdump carefully, you can answer this question easily.
Here is answer.
0x80483d0 : This is the address of our main() function.
0x8048274 : _init function.
0x8048420 : _fini function _init
and _fini is initialization/finalization function provided by GCC.
Right now, let's not care about these stuffs. And basically, all those hexa values are function pointers.
Again, let's look for address 80482bc from the disassembly output.
If you look for it, the assembly is
80482bc: ff 25 48 95 04 08 jmp *0x8049548
With ELF, we can build an executable linked dynamically with libraries.
Here "linked dynamically" means the actual linking process happens at runtime.
Otherwise we'd have to build a huge executable containing all the libraries it
calls (a "statically-linked executable).
If you issue the command
"ldd simple"You can see all the libraries dynamically linked with simple. And all the dynamically linked data and functions have "dynamic relocation entry".
libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
objdump -R simpleHere address 0x8049548 is called "jump slot", which perfectly makes sense. And according to the table, actually we want to call __libc_start_main.
simple: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804954c R_386_GLOB_DAT __gmon_start__
08049540 R_386_JUMP_SLOT __register_frame_info
08049544 R_386_JUMP_SLOT __deregister_frame_info
08049548 R_386_JUMP_SLOT __libc_start_main
Now the ball is on libc's hand. __libc_start_main is a function in libc.so.6. If you look for __libc_start_main in glibc source code, the prototype looks like this.
extern int BP_SYM (__libc_start_main) (int (*main) (int, char **, char **),And all the assembly instructions do is set up argument stack and call __libc_start_main.
int argc,
char *__unbounded *__unbounded ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void *__unbounded stack_end)
__attribute__ ((noreturn));
When we execute a program by entering a name on shell, this is what happens
on Linux.
When the _start assembly instruction gets control of execution, the stack
frame looks like this.
Stack Top -------------
argc
-------------
argv pointer
-------------
env pointer
-------------
And the assembly instructions gets all information from stack by
pop %esi <--- get argcAnd now we are all set to start executing.
move %esp, %ecx <--- get argv
actually the argv address is the same as the current
stack pointer.
For esp, this is used for stack end in application program. After popping all necessary information, the _start rountine simply adjusts the stack pointer (esp) by turning off lower 4 bits from esp register. This perfectly makes sense since actually, to our main program, that is the end of stack. For edx, which is used for rtld_fini, a kind of application destructor, the kernel just sets it to 0 with the following macro.
#define ELF_PLAT_INIT(_r) do { \The 0 means we don't use that functionality on x86 linux.
_r->ebx = 0; _r->ecx = 0; _r->edx = 0; \
_r->esi = 0; _r->edi = 0; _r->ebp = 0; \
_r->eax = 0; \
} while (0)
Where are all those codes from? It's part of GCC code. You can usually
find all the object files for the code at
/usr/lib/gcc-lib/i386-redhat-linux/XXX and
/usr/lib where XXX is gcc version.
File names are crtbegin.o,crtend.o, gcrt1.o.
Here is what happens.
main(int argc, char** argv, char** env)
{
int i = 0;
while(env[i] != 0)
{
printf("%s\n", env[i++]);
}
return(0);
}
On Linux, our C main() function is executed by the cooperative work of GCC, libc and Linux's binary loader.
objdump "man objdump"
ELF header /usr/include/elf.h
__libc_start_main glibc
source
./sysdeps/generic/libc-start.c
sys_execve
linux kernel source code
arch/i386/kernel/process.c
do_execve
linux kernel source code
fs/exec.c
struct linux_binfmt linux kernel source code
include/linux/binfmts.h
load_elf_binary
linux kernel source code
fs/binfmt_elf.c
create_elf_tables linux
kernel source code
fs/binfmt_elf.c
start_thread
linux kernel source code
include/asm/processor.h