First, generate a simple executable. (ignore the warnings, the executable runs anyway)

echo 'main(){puts("123");}'|gcc -x c - -o a

Load it with gdb a, then:

(gdb) info file
Symbols from "/home/user202729/PINCE/a".
Local exec file:
        `/home/user202729/PINCE/a', file type elf64-x86-64.
        Entry point: 0x520

Set some breakpoints:

(gdb) b _start
Breakpoint 1 at 0x520
(gdb) b *0x520
Note: breakpoint 1 also set at pc 0x520.
Breakpoint 2 at 0x520
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000000520 <_start>
2       breakpoint     keep y   0x0000000000000520 <_start>

(using gdb's break command) Breakpoint 1 was set using break function syntax, and breakpoint 2 was set using break *address.

Run the program:

(gdb) r
Starting program: /home/user202729/PINCE/a 
Cannot insert breakpoint 2.
Cannot access memory at address 0x520

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555554520 <_start>
2       breakpoint     keep y   0x0000000000000520 


  • What is the 0x0000000000000520 or 0x0000555555554520 above called?
  • How can I set a breakpoint, given only 0x0000000000000520, without 0x0000555555554520 or the symbol names? (just in case the executable is stripped)
  • Or alternatively, is there a way to get the load address of _start (i.e., 0x0000555555554520) in order to break there?

The basic problem is that the load address is not known until the file is actually mapped by the OS, and by that time it may be too late if the program is already running. Several workarounds for this may be possible:

  • if the file has symbols, use a symbolic breakpoint. GDB will automatically remap the breakpoint to the actual runtime address
  • if the OS allows it, disable ASLR so that the load address will match the file address and you won't need to move the breakpoint
  • patch the input file to insert a breakpoint opcode (e.g. 0xCC for x86/x64) at the desired location. GDB will stop due to an unexpected debug event.
  • add a breakpoint at an non-existent address (suggested by Zach Riggle). GDB will stop the program when it can't set a requested breakpoint, and at this point you can check the load address and adjust your breakpoints.

  • in recent GDB versions, the starti command will stop the execution as soon as the program starts running. From the docs:






No, in fact you misunderstood something. :-)

The address 0x0000000000000520 is the offset from the beginning of the ELF file from the section .text where the _start procedure lies. And, the address 0x0000555555554520 correspond to the address where the section .text has been mapped by the operating system plus the offset of the procedure: 0x0000555555554000 + 0x0000000000000520 (.text section address + main procedure offset).

gdb (without a run) will only have the offset of the .text section as if it was starting at zero. Then, after running the _start procedure, the .text section will be remapped within the virtual memory by the OS. This remapping happen when the loader is called.

And, if you observe a difference between the two breakpoints, it is mainly because the first breakpoint has been set onto the symbol _start and the other one has been set onto the address. The remapping of the symbol will be taken into account by gdb but, an address is an address and it will not change anymore.

For example, just after loading the executable file in gdb (no run):

(gdb) info files
Symbols from "/tmp/a".
Local exec file:
    `/tmp/a', file type elf64-x86-64.
    Entry point: 0x1050
    0x00000000000002a8 - 0x00000000000002c4 is .interp
    0x00000000000002c4 - 0x00000000000002e4 is .note.ABI-tag
    0x00000000000002e4 - 0x0000000000000308 is .note.gnu.build-id
    0x0000000000000308 - 0x000000000000032c is .gnu.hash
    0x0000000000000330 - 0x00000000000003d8 is .dynsym
    0x00000000000003d8 - 0x000000000000045a is .dynstr
    0x000000000000045a - 0x0000000000000468 is .gnu.version
    0x0000000000000468 - 0x0000000000000488 is .gnu.version_r
    0x0000000000000488 - 0x0000000000000548 is .rela.dyn
    0x0000000000000548 - 0x0000000000000560 is .rela.plt
    0x0000000000001000 - 0x0000000000001017 is .init
    0x0000000000001020 - 0x0000000000001040 is .plt
    0x0000000000001040 - 0x0000000000001048 is .plt.got
    0x0000000000001050 - 0x00000000000011c2 is .text
    0x00000000000011c4 - 0x00000000000011cd is .fini
    0x0000000000002000 - 0x0000000000002008 is .rodata
    0x0000000000002008 - 0x0000000000002044 is .eh_frame_hdr
    0x0000000000002048 - 0x0000000000002150 is .eh_frame
    0x0000000000003de8 - 0x0000000000003df0 is .init_array
    0x0000000000003df0 - 0x0000000000003df8 is .fini_array
    0x0000000000003df8 - 0x0000000000003fd8 is .dynamic
    0x0000000000003fd8 - 0x0000000000004000 is .got
    0x0000000000004000 - 0x0000000000004020 is .got.plt
    0x0000000000004020 - 0x0000000000004030 is .data
    0x0000000000004030 - 0x0000000000004038 is .bss

We can see the _start procedure is located exactly at the beginning of the .text section:

(gdb) disas 0x0000000000001050, 0x00000000000011c2
Dump of assembler code from 0x1050 to 0x11c2:
   0x0000000000001050 <_start+0>:   xor    %ebp,%ebp
   0x0000000000001052 <_start+2>:   mov    %rdx,%r9
   0x0000000000001055 <_start+5>:   pop    %rsi
   0x0000000000001056 <_start+6>:   mov    %rsp,%rdx
   0x0000000000001059 <_start+9>:   and    $0xfffffffffffffff0,%rsp
   0x000000000000105d <_start+13>:  push   %rax
   0x000000000000105e <_start+14>:  push   %rsp
   0x000000000000105f <_start+15>:  lea    0x15a(%rip),%r8  # 0x11c0 <__libc_csu_fini>
   0x0000000000001066 <_start+22>:  lea    0xe3(%rip),%rcx  # 0x1150 <__libc_csu_init>
   0x000000000000106d <_start+29>:  lea    0xc1(%rip),%rdi  # 0x1135 <main>
   0x0000000000001074 <_start+36>:  callq  *0x2f66(%rip)    # 0x3fe0
   0x000000000000107a <_start+42>:  hlt    
   0x000000000000107b:  nopl   0x0(%rax,%rax,1)
   0x0000000000001080 <deregister_tm_clones+0>: lea    0x2fa9(%rip),%rdi       
   0x0000000000001087 <deregister_tm_clones+7>: lea    0x2fa2(%rip),%rax 

And, once we hit the 'start' command (corresponding to a tbreak main + run):

(gdb) start
Temporary breakpoint 1 at 0x1139
Starting program: /tmp/a 

Temporary breakpoint 1, 0x0000555555555139 in main ()
(gdb) info files
Symbols from "/tmp/a".
Native process:
    Using the running image of child process 22585.
Local exec file:
    Entry point: 0x555555555050
    0x00005555555542a8 - 0x00005555555542c4 is .interp
    0x00005555555542c4 - 0x00005555555542e4 is .note.ABI-tag
    0x00005555555542e4 - 0x0000555555554308 is .note.gnu.build-id
    0x0000555555554308 - 0x000055555555432c is .gnu.hash
    0x0000555555554330 - 0x00005555555543d8 is .dynsym
    0x00005555555543d8 - 0x000055555555445a is .dynstr
    0x000055555555445a - 0x0000555555554468 is .gnu.version
    0x0000555555554468 - 0x0000555555554488 is .gnu.version_r
    0x0000555555554488 - 0x0000555555554548 is .rela.dyn
    0x0000555555554548 - 0x0000555555554560 is .rela.plt
    0x0000555555555000 - 0x0000555555555017 is .init
    0x0000555555555020 - 0x0000555555555040 is .plt
    0x0000555555555040 - 0x0000555555555048 is .plt.got
    0x0000555555555050 - 0x00005555555551c2 is .text
    0x00005555555551c4 - 0x00005555555551cd is .fini
    0x0000555555556000 - 0x0000555555556008 is .rodata
    0x0000555555556008 - 0x0000555555556044 is .eh_frame_hdr
    0x0000555555556048 - 0x0000555555556150 is .eh_frame
    0x0000555555557de8 - 0x0000555555557df0 is .init_array
    0x0000555555557df0 - 0x0000555555557df8 is .fini_array
    0x0000555555557df8 - 0x0000555555557fd8 is .dynamic
    0x0000555555557fd8 - 0x0000555555558000 is .got
    0x0000555555558000 - 0x0000555555558020 is .got.plt
    0x0000555555558020 - 0x0000555555558030 is .data

You can see that all sections have been remapped by the loader (and some sections have been added to handle the dynamic libraries).

If you want to know more about the loading process of executable under Linux, I would strongly advise you to take a look at this excellent article from Patrick Horgan. I think it will cover most of the questions you might have about this process.

Hope this helped.

  • As I said, you can set breakpoints on symbols (if they are present), it will be remapped automatically. But, if the binary has been stripped (no symbol), there is no silver bullet. You will be forced to step through the binary to locate the main function. Another way is to use the start command from gdb which might help. – perror Jul 30 '18 at 8:37
  • 1
  • I didn't know this starti GDB command, quite handful. Thanks Igor! – perror Jul 30 '18 at 8:40

You can't do this in vanilla gdb but if you are using pwndbg you can.

The command is called breakrva or brva for short. You can use it like this:

brva 0x520

but the program has to run.


Surprised that no one has mentioned this, If you have symbols you can use break * _start+9 to achieve the same result. Example

pwndbg> b * main+29
Breakpoint 1 at 0x9f7
pwndbg> r
Starting program: /tmp/a.out 

Breakpoint 1, 0x00005555555549f7 in main ()

But in case of absolute addresses, it fails when the binary is loaded due to the presence of PIE and the load address of .text is now subject to ASLR.

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000000009da <+0>: push   rbp
   0x00000000000009db <+1>: mov    rbp,rsp
   0x00000000000009de <+4>: push   rbx
   0x00000000000009df <+5>: sub    rsp,0x18
   0x00000000000009e3 <+9>: mov    edi,0x8
   0x00000000000009e8 <+14>:    call   0x890 <_Znwm@plt>
   0x00000000000009ed <+19>:    mov    rbx,rax
   0x00000000000009f0 <+22>:    mov    QWORD PTR [rbx],0x0
   0x00000000000009f7 <+29>:    mov    rdi,rbx
   0x00000000000009fa <+32>:    call   0xb08 <_ZN7VehicleC2Ev>
   0x00000000000009ff <+37>:    mov    QWORD PTR [rbp-0x18],rbx
   0x0000000000000a03 <+41>:    mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000000a07 <+45>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a0a <+48>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a0d <+51>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x0000000000000a11 <+55>:    mov    rdi,rdx
   0x0000000000000a14 <+58>:    call   rax
   0x0000000000000a16 <+60>:    mov    rax,QWORD PTR [rbp-0x18]
   0x0000000000000a1a <+64>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a1d <+67>:    add    rax,0x8
   0x0000000000000a21 <+71>:    mov    rax,QWORD PTR [rax]
   0x0000000000000a24 <+74>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x0000000000000a28 <+78>:    mov    rdi,rdx
   0x0000000000000a2b <+81>:    call   rax
   0x0000000000000a2d <+83>:    mov    eax,0x0
   0x0000000000000a32 <+88>:    add    rsp,0x18
   0x0000000000000a36 <+92>:    pop    rbx
   0x0000000000000a37 <+93>:    pop    rbp
   0x0000000000000a38 <+94>:    ret    
End of assembler dump.
pwndbg> b * main+29
Breakpoint 1 at 0x9f7
pwndbg> info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000009f7 <main+29>
pwndbg> b * 0x0000000000000a11
Breakpoint 2 at 0xa11
pwndbg> info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000000009f7 <main+29>
2       breakpoint     keep y   0x0000000000000a11 <main+55>
pwndbg> r
Starting program: /tmp/a.out 
Cannot insert breakpoint 2.
Cannot access memory at address 0xa11

Your Answer

