I'm a newbie and just got into RE. I got a ELF 64-bit LSB executable, x86-64. I'm trying to reverse it. First I tried to set a break point on line 1 using

gdb ./filename
break 1

The gdb says

No symbol table is loaded.  Use the "file" command.

OKie so gave out file command

(gdb) file filename
Reading symbols from /media/Disk/filename...(no debugging symbols found)...done.

How could a set a break point to see the execution..?

1 Answer 1


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):

  1. 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.

  2. Using objdump

    $> objdump -f /bin/ls
    /bin/ls:     file format elf64-x86-64
    architecture: i386:x86-64, flags 0x00000112:
    start address 0x000000000040489c

    Again, the entrypoint is 0x000000000040489c.

  3. 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]

ELF complete callgraph

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:

  1. 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.

  2. 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.

  • Thanks for your answer, Monsieur. I followed your steps but when running I always have a zsh:1: permission denied. It seems similar to launch gdb over non compiled .o files. Do I need to compile it first? Commented May 1, 2021 at 14:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.