Getting the entrypoint
If you have no useful symbol, you first need to find the entrypoint of the executable. There are several ways to do it (depending on the tools you have or the tools you like the best):
Using readelf
$> readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x40489c
Start of program headers: 64 (bytes into file)
Start of section headers: 108264 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
So, the entrypoint address is 0x40489c
.
Using objdump
$> objdump -f /bin/ls
/bin/ls: file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x000000000040489c
Again, the entrypoint is 0x000000000040489c
.
Using gdb
$> gdb /bin/ls
GNU gdb (GDB) 7.6.2 (Debian 7.6.2-1)
...
Reading symbols from /bin/ls...(no debugging symbols found)...done.
(gdb) info files
Symbols from "/bin/ls".
Local exec file:
`/bin/ls', file type elf64-x86-64.
Entry point: 0x40489c
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x0000000000400300 is .gnu.hash
0x0000000000400300 - 0x0000000000400f18 is .dynsym
0x0000000000400f18 - 0x00000000004014ab is .dynstr
0x00000000004014ac - 0x00000000004015ae is .gnu.version
0x00000000004015b0 - 0x0000000000401640 is .gnu.version_r
0x0000000000401640 - 0x00000000004016e8 is .rela.dyn
0x00000000004016e8 - 0x0000000000402168 is .rela.plt
0x0000000000402168 - 0x0000000000402182 is .init
0x0000000000402190 - 0x00000000004028a0 is .plt
0x00000000004028a0 - 0x0000000000411f0a is .text
0x0000000000411f0c - 0x0000000000411f15 is .fini
0x0000000000411f20 - 0x000000000041701c is .rodata
0x000000000041701c - 0x0000000000417748 is .eh_frame_hdr
...
Entrypoint is still 0x40489c
.
Locating the main
procedure
Once the entrypoint is known, you can set a breakpoint on it and start looking for the main
procedure. Because, you have to know that all the programs will start by a _start()
procedure in charge of initializing the memory for the process and loading the dynamic libraries. In fact, this first procedure is a convention in the Unix World.
What exactly does this initialization procedure is quite tedious to follow and, most of the time, of no interest at all to understand your program. The main()
procedure will only start after all the memory is set-up and ready to go.
Lets see how to do that (I assume that the executable has been compile with gcc
):
(gdb) break *0x40489c
Breakpoint 1 at 0x40489c
(gdb) run
Starting program: /bin/ls
warning: Could not load shared library symbols for linux-vdso.so.1.
Breakpoint 1, 0x000000000040489c in ?? ()
Okay, so we stopped at the very beginning of the executable. At this time, nothing is ready, everything need to be set-up. Let see what are the first steps of the executable:
(gdb) disas 0x40489c,+50
Dump of assembler code from 0x40489c to 0x4048ce:
=> 0x000000000040489c: xor %ebp,%ebp
0x000000000040489e: mov %rdx,%r9
0x00000000004048a1: pop %rsi
0x00000000004048a2: mov %rsp,%rdx
0x00000000004048a5: and $0xfffffffffffffff0,%rsp
0x00000000004048a9: push %rax
0x00000000004048aa: push %rsp
0x00000000004048ab: mov $0x411ee0,%r8
0x00000000004048b2: mov $0x411e50,%rcx
0x00000000004048b9: mov $0x4028c0,%rdi
0x00000000004048c0: callq 0x4024f0 <__libc_start_main@plt>
0x00000000004048c5: hlt
0x00000000004048c6: nopw %cs:0x0(%rax,%rax,1)
End of assembler dump.
What follow the hlt
is just rubbish obtained because of the linear sweep performed by gdb
. So, just ignore it. What is relevant is the fact that we are calling __libc_start_main()
(I won't comment on the @plt
because it would drag us out of the scope of the question).
In fact, the procedure __libc_start_main()
initialize the memory for a process running with the libc
dynamic library. And, once done, jump to the procedure located in %rdi
(which usually is the main()
procedure). See the following picture to have a global view of what does the __libc_start_main()
procedure [1]

So, indeed, the address of the main()
procedure is at 0x4028c0
. Let disassemble a few instructions at this address:
(gdb) x /10i 0x4028c0
0x4028c0: push %r15
0x4028c2: push %r14
0x4028c4: push %r13
0x4028c6: push %r12
0x4028c8: push %rbp
0x4028c9: mov %rsi,%rbp
0x4028cc: push %rbx
0x4028cd: mov %edi,%ebx
0x4028cf: sub $0x388,%rsp
0x4028d6: mov (%rsi),%rdi
...
And, if you look at it, this is indeed the main()
procedure. So, this where to really start the analysis.
Words of warning
Even if this way of looking for the main()
procedure will work in most the cases. You have to know that we strongly rely on the following hypothesis:
Programs written in pure assembly language and compiled with gcc -nostdlib
(or directly with gas
or nasm
) won't have a first call to __libc_start_main()
and will start straight from the entrypoint. Therefore, for these programs, the _start()
procedure is the main()
procedure. In fact, it is important to understand that the main()
procedure is just a convention introduced by the C language as the first function (written by the programmer) to be run in the program. Of course, you can find this convention replicated in many other languages such as Java, C++, and others. But, all these languages derive from C.
We also strongly rely on a knowledge on the way __libc_start_main()
works. And, how this procedure has been designed by the gcc
team. So, if the program you are analyzing has been compiled with another compiler, you may have to investigate a bit further about this compiler and how it perform the set-up of the memory before running the main()
procedure.
Anyway, you should now be able to track down a program with no symbol at all if you read this answer carefully.
Finally, you can find an excellent summary about the starting of an executable by reading "Linux x86 Program Start Up or - How the heck do we get to main()
?" by Patrick Horgan.