3
votes

Every time when I disassemble a function, why do I always get the same instruction address and constants' address?

For example, after executing the following commands,

gcc -o hello hello.c -ggdb
gdb hello
(gdb) disassemble main

the dump code would be:

dump code

When I quit gdb and re-disassemble the main function, I will get the same result as before. The instruction address and even the address of constants are always the same for each disassemble command in gdb. Why is that? Does the compiled file hello contain certain information about the address of each assembly instruction as well as the constants' addresses?

4
're-disassemble the main function, I will get the same result as before' umm.. you were expecting something different?Martin James
'Does the compile file hello contain certain information about the address of each assembly instruction as well as the constants' addresses?' I don't see how the processor could execute it otherwise?Martin James
I just don't understand why the addresses for each instruction are the same all the time. It seems that the instructions always occupy the same location in the memory? Is it possible?VeryLazyBoy
It's virtual memory, so even if the OS loads the program into different physical addresses, it can map that to the same virtual address.Bo Persson
@MartinJames: Executable files do not need to contain address information in order for them to be loaded and executed. Instead, they contain offsets, not addresses, and the offsets are fixed up by the loader after it determines the base addresses for the program sections.Eric Postpischil

4 Answers

4
votes

If you made a position-independent executable (e.g. with gcc -fpie -pie, which is the default for gcc in many recent Linux distros), the kernel would randomize the address it mapped your executable at. (Except when running under GDB: GDB disables ASLR by default even for shared libraries, and for PIE executables.)


But you're making a position-dependent executable, which can take advantage of static addresses being link-time constants (by using them as immediates and so on without needing runtime relocation fixups). e.g. you or the compiler can use mov $msg, %edi (like your code) instead of lea msg, %rdi (with -fpie).

Regular (position-dependent) executables have their load-address set in the ELF headers: use readelf -a ./a.out to see the ELF metadata.

A non-PIE executable will load at the same time every time even without running it under GDB, at the address specified in the ELF program headers. (gcc / ld chooses 0x400000 by default on x86-64-linux-elf; you could change this with a linker script). Relocation information for all the static addresses hard-coded into the code + data is not available, so the loader couldn't fix up the addresses even if it wanted to.

e.g. in a simple executable (with only a text segment, not data or bss) I built with -no-pie (which seems to be the default in your gcc):

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000c5 0x00000000000000c5  R E    0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .text 

So the ELF headers request that offset 0 in the file be mapped to virtual address 0x0000000000400000. (And the ELF entry point is 0x400080; that's where _start is.) I'm not sure what the relevance of PhysAddr = VirtAddr is; user-space executables don't know and can't easily find out what physical addresses the kernel used for pages of RAM backing their virtual memory, and it can change at any time as pages are swapped in / out.

Note that readelf does line wrapping; note there are two rows of columns headers. The 0x200000 is the Align column for that one LOADed segment.

2
votes

By default, the GNU toolchain for x86-64 Linux produces position-dependent executables which are mapped at address 0x400000. (position-independent executables will be mapped at 0x55… addresses instead). It is possible to change that by building GCC --enable-default-pie, or by specifying compiler and linker flags.

However, even for a position-independent executable (PIE), the addresses would be constant between GDB runs because GDB disables address space layout randomization by default. GDB does this so that breakpoints at absolute addresses can be re-applied after the program has been started.

1
votes

There are a variety of executable file formats. Typically, an executable file contains information anout several memory sections or segments. Inside the executable, references to memory addresses may be expressed relative to the beginning of a section. The executable also contains a relocation table. The relocation table is a list of those references, including where each one is in the executable, what section it refers to, and what type of reference it is (what field of an instruction it is used in, etc.).

The loader (software that loads your program into memory) reads the executable and writes the sections to memory. In your case, the loader appears to be using the same base addresses for sections every time it runs. After initially putting the sections in memory, the loader reads the relocation table and uses it to fix up all the references to memory by adjusting them based on where each section was loaded into memory. For example, the compiler may write an instruction as, in effect, “Load register 3 from the start of the data section plus 278 bytes.” If the loader puts the data section at address 2000, it will adjust this instruction to use the sum of 2000 and 278, making “Load register 3 from address 2278.”

Good modern loaders randomize where sections are loaded. They do this because malicious people are sometimes able to exploit bugs in programs to cause them to execute code injected by the attacker. Randomizing section locations prevents the attacker from knowing the address where their code will be injected, which can hinder their ability to prepare the code to be injected. Since your addresses are not changing, it appears your loader does not do this. You may be using an older system.

Some processor architectures and/or loaders support position independent code (PIC). In this case, the form of an instruction may be “Load register 3 from 694 bytes beyond where this instruction is.” In that case, as long as the data is always at the same distance from the instruction, it does not matter where they are in memory. When the process executes the instruction, it will add the address of the instruction to 694, and that will be the address of the data. Another way of implementing PIC-like code is for the loader to provide the addresses of each section to the program, by putting those addresses in registers or fixed locations in memory. Then the program can use those base addresses to do its own address calculations. Since your program has an address built into the code, it does not appear your program is using these methods.

1
votes

a not intended to be really executed program

bootstrap

.globl _start
_start:
    bl one
    b .

first c file

extern unsigned int hello;
unsigned int one ( void )
{
    return(hello+5);
}

second c file (being extern forces the compiler to compile the first object in a certain way)

unsigned int hello;

linker script

MEMORY
{
    ram : ORIGIN = 0x00001000, LENGTH = 0x4000
}
SECTIONS
{
    .text : { *(.text*) } > ram
    .bss : { *(.bss*) } > ram
}

building position dependent

Disassembly of section .text:

00001000 <_start>:
    1000:   eb000000    bl  1008 <one>
    1004:   eafffffe    b   1004 <_start+0x4>

00001008 <one>:
    1008:   e59f3008    ldr r3, [pc, #8]    ; 1018 <one+0x10>
    100c:   e5930000    ldr r0, [r3]
    1010:   e2800005    add r0, r0, #5
    1014:   e12fff1e    bx  lr
    1018:   0000101c    andeq   r1, r0, r12, lsl r0

Disassembly of section .bss:

0000101c <hello>:
    101c:   00000000    andeq   r0, r0, r0

the key here is at address 0x1018 the compiler had to leave a placeholder for the address to the external item. shown as offset 0x10 below

00000000 <one>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <one+0x10>
   4:   e5930000    ldr r0, [r3]
   8:   e2800005    add r0, r0, #5
   c:   e12fff1e    bx  lr
  10:   00000000    andeq   r0, r0, r0

The linker fills this in at link time. You can see in the disassembly above that position dependent it fills in the absolute address of where to find that item. For this code to work the code must be loaded in a way that that item shows up at that address. It has to be loaded at a specific position or address in memory. Position dependent. (loaded at address 0x1000 basically).

If your toolchain supports position independent (gnu does) then this represents a solution.

Disassembly of section .text:

00001000 <_start>:
    1000:   eb000000    bl  1008 <one>
    1004:   eafffffe    b   1004 <_start+0x4>

00001008 <one>:
    1008:   e59f3014    ldr r3, [pc, #20]   ; 1024 <one+0x1c>
    100c:   e59f2014    ldr r2, [pc, #20]   ; 1028 <one+0x20>
    1010:   e08f3003    add r3, pc, r3
    1014:   e7933002    ldr r3, [r3, r2]
    1018:   e5930000    ldr r0, [r3]
    101c:   e2800005    add r0, r0, #5
    1020:   e12fff1e    bx  lr
    1024:   00000014    andeq   r0, r0, r4, lsl r0
    1028:   00000000    andeq   r0, r0, r0

Disassembly of section .got:

0000102c <.got>:
    102c:   0000103c    andeq   r1, r0, r12, lsr r0

Disassembly of section .got.plt:

00001030 <_GLOBAL_OFFSET_TABLE_>:
    ...

Disassembly of section .bss:

0000103c <hello>:
    103c:   00000000    andeq   r0, r0, r0

It has a performance hit of course, but instead of the compiler and linker working together by leaving one location, there is now a table, global offset table (for this solution) that is at a known location which is position relative to the code, that contains linker supplied offsets.

The program is not position independent yet, it will certainly not work if you load it anywhere. The loader has to patch up the table/solution based on where it wants to place the items. This is far simpler than having a very long list of each of the locations to patch in the first solution, although that would have been a very possible way to do it. A table in the executable (executables contain more than the program and data they have other items of information as you know if you objdump or readelf an elf file) could contain all of those offsets and the loader could patch those up too.

If your data and bss and other memory sections are fixed relative to .text as I have built here, then a got wasnt necessary the linker could have at link time computed the relative offset to the resource and along with the compiler found the item in an position independent way, and the binary could have been loaded just about anywhere (some minimum alignment may hav been required) and it would work without any patching. With the gnu solution I think you can move the segments relative to each other.

It is incorrect to state that the kernel will or would always randomize your location if built position independent. While possible so long as the toolchain and the loader from the operating system (a completely separate development) work hand in hand, the loader has the opportunity. But that does not in any way mean that every loader does or will. Specific operating systems/distros/versions may have that set as a default yes. If they come across a binary that is position independent (built in a way that loader expects). It is like saying all mechanics on the planet will use a specific brand and type of oil if you show up in their garage with a specific brand of car. A specific mechanic may always use a specific oil brand and type for a specific car, but that doesnt mean all mechanics will or perhaps even can obtain that specific oil brand or type. If that individual business chooses to as a policy then you as a customer can start to form an assumption that that is what you will get (with that assumption then failing when they change their policy).

As far as disassembly you can statically disassemble your project at build time or whenever. If loaded at a different position then there will be an offset to what you are seeing, but the .text code will still be in the same place relative to other code in that segment. If the static disassembly shows a call being 0x104 bytes ahead, then even if loaded somewhere else you should see that relative jump also be 0x104 bytes ahead, the addresses may be different.

Then there is the debugger part of this, for the debugger to work/show the correct information it also has to be part of the toolchain/loader(/os) team for everything to work/look right. It has to know this was position independent and have to know where it was loaded and/or the debugger is doing the loading for you and may not use the standard OS loader in the same way that a command line or gui does. So you might still see the binary in the same place every time when using the debugger.

The main bug here was your expectation. First operating systems like windows, linux, etc desire to use an MMU to allow them to manage memory better. To pick some/many non-linear blocks of physical memory and create a linear area of virtual memory for your program to live, more importantly the virtual address space for each separate program can look the same, I can have every program load at 0x8000 in virtual address space, without interfering with each other, with an MMU designed for this and an operating system that takes advantage of this. Even with this MMU and operating system and position independent loading one would hope they are not using physical addresses, they are still creating a virtual address space, just possibly with different load points for each program or each instance of a program. Expecting all operating systems to do this all the time is an expectation problem. And when using a debugger you are not in a stock environment, the program runs differently, can be loaded differently, etc. It is not the same as running without the debugger, so using a debugger also changes what you should expect to see happen. Two levels of expectation here to deal with.

Use an external component in a very simple program as I made above, see in the disassembly of the object that it has built for position independence as well as in the linking then try Linux as Peter has indicated and see if it loads in a different place each time, if not then you need to be looking at superuser SE or google around about how to use linux (and/or gdb) to get it to change the load location.