Question

While studying Assemblers, I came across these terms.The idea I got is like this, in Relocatable machine code, the code is not dependent on static RAM location. The assembler specifies RAM needs for my program. The memory can be placed wherever the linker finds room for them.

Is the idea correct? If so, how is it done by the assembler?

And, what are some examples of Absolute Machine Code?

Was it helpful?

Solution

Many/most instruction sets have pc relative addressing, meaning take the address of the program counter, which is related to the address of the instruction you are executing, and then add an offset to that and use that for accessing memory or branching or something like that. That would be what you are calling relocatable. Because no matter where that instruction is in the address space the thing you want to jump to is relative. Move the whole block of code and data to some other address and they will still be relatively the same distance apart, so the relative addressing will still work. If equal skip the next instruction works wherever those three instructions are (the if skip, the one being skipped and the one after the skip).

Absolute uses absolute addresses, jump to this exact address, read from this exact address. If equal then branch to 0x1000.

The assembler doesn't do this, the compiler and/or programmer does. Generally, eventually, compiled code will end up having absolute addressing, in particular if your code consists of separate objects that are linked together. At compile time the compiler cant know where the object will end up nor is it possible to know where the external references are or how far away so it cant generally assume they will be close enough for pc relative addressing (which generally has a range limit). So the compilers often generate a placeholder for the linker to fill in with an absolute address. It does depend on the operation and instruction set and some other factors as to how this external address problem is solved. Eventually though based on project size, the linker will end up with some absolute addressing. So the non-default is usually a command line option to generate position independent code -PIC for example might be something your compiler supports. both the compiler and linker then have to do extra work to make those items position independent. An assembly language programmer has to do this all themselves, the assembler generally doesnt get involved in this it just creates the machine code for the instructions you tell it to generate.

novectors.s:

.globl _start
_start:
    b   reset
reset:
    mov sp,#0xD8000000
    bl notmain
    ldr r0,=notmain
    blx r0
hang: b hang

.globl dummy
dummy:
    bx lr

hello.c

extern void dummy ( unsigned int );
int notmain ( void )
{
    unsigned int ra;
    for(ra=0;ra<1000;ra++) dummy(ra);
    return(0);
}

memap (the linker script) MEMORY { ram : ORIGIN = 0xD6000000, LENGTH = 0x4000 } SECTIONS { .text : { (.text) } > ram } Makefile

ARMGNU = arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding 
all : hello_world.bin
clean :
    rm -f *.o
    rm -f *.bin
    rm -f *.elf
    rm -f *.list

novectors.o : novectors.s
    $(ARMGNU)-as novectors.s -o novectors.o

hello.o : hello.c
    $(ARMGNU)-gcc $(COPS) -c hello.c -o hello.o

hello_world.bin : memmap novectors.o hello.o 
    $(ARMGNU)-ld novectors.o hello.o -T memmap -o hello_world.elf
    $(ARMGNU)-objdump -D hello_world.elf > hello_world.list
    $(ARMGNU)-objcopy hello_world.elf -O binary hello_world.bin 

hello_world.list (the parts we care about)

Disassembly of section .text:

d6000000 <_start>:
d6000000:   eaffffff    b   d6000004 <reset>

d6000004 <reset>:
d6000004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
d6000008:   eb000004    bl  d6000020 <notmain>
d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
d6000010:   e12fff30    blx r0

d6000014 <hang>:
d6000014:   eafffffe    b   d6000014 <hang>

d6000018 <dummy>:
d6000018:   e12fff1e    bx  lr
d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32

d6000020 <notmain>:
d6000020:   e92d4010    push    {r4, lr}
d6000024:   e3a04000    mov r4, #0
d6000028:   e1a00004    mov r0, r4
d600002c:   e2844001    add r4, r4, #1
d6000030:   ebfffff8    bl  d6000018 <dummy>
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 <notmain+0x8>
d600003c:   e3a00000    mov r0, #0
d6000040:   e8bd4010    pop {r4, lr}
d6000044:   e12fff1e    bx  lr

What I am showing here is a mixture of position independent instructions and position dependent instructions.

these two instructions for example are a shortcut to force the assembler to add a .word style memory location that the linker then has to fill in for us.

ldr r0,=notmain
blx r0

0xD600001c is that location.

    d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
    d6000010:   e12fff30    blx r0
...
    d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32

and it is filled in with the address 0xD6000020 which is an absolute address so for that code to work the function notmain must be at address 0xD6000020 it is not relocatable. but this portion of the example also demonstrates some position independent code as well, the

ldr r0, [pc, #8]

is the pc relative addressing I was talking about the way this instruction set works is at the time of execution the pc is two instructions ahead or basically in this case if the instruction is at 0xD600000c in memory then the pc will be 0xD6000014 when executing then add 8 to that as the instruction states and you get 0xD600001C. But if we moved that exact same machine code instruction to address 0x1000 AND we move all of the surrounding binary there including the thing it is reading (the 0xD6000020). basically do this:

    1000:   e59f0008    ldr r0, [pc, #8]    
    1004:   e12fff30    blx r0
...
    1010:   d6000020    

And those instructions, that machine code will still work, it doesnt have to be re-assembled or re-linked. the 0xD6000020 code sitll hast to be at that fixed address bit the ldr pc and blx dont.

Although the disassembler shows these with 0xd6... based addresses the bl and bne are also pc relative which you can find out by looking at the instruction set documentation

d6000030:   ebfffff8    bl  d6000018 <dummy>
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 <notmain+0x8>

0xD6000030 would have a pc of 0xD6000038 when executed and 0xD6000038-0xD6000018 = 0x20 which is 8 instructions. And a negative 8 in twos complement is 0xFFF..FFFF8, you can see the bulk of that machine code ebfffff8 is ffff8, which is what is sign extended and added to the program counter to basically say branch backward 8 instrucitons. Same goes for the ffffa in 1afffffa it means if not equal then branch backward 6 instructions. Remember this instruction set (arm) assumes the pc is two instructions ahead so that back 6 means forward two then back 6 or effectively back 4.

If you remove the

d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
d6000010:   e12fff30    blx r0

Then this entire program ends up being position independent, by accident if you will (I happened to have known it would happen) but not because I told the tools to do that but simply because I made everything close and didnt use any absolute addressing.

lastly when you say "wherever the linker finds room for them" if you notice in my linker script I tell the linker to put everything starting at 0xD6000000, I didnt specify any file names or functions, so if not told otherwise this linker places the items in the order they are specified on the command line. the hello.c code is second so after the linker has placed the novectors.s code, then the wherever the linker had room is right after that, the hello.c code starts at 0xD6000020.

And an easy way to see what is position independent and what isnt without having to research each instruction would be to change the linker script to put the code at some other address.

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

and see what machine code changes if any, and what doesnt.

00001000 <_start>:
    1000:   eaffffff    b   1004 <reset>

00001004 <reset>:
    1004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
    1008:   eb000004    bl  1020 <notmain>
    100c:   e59f0008    ldr r0, [pc, #8]    ; 101c <dummy+0x4>
    1010:   e12fff30    blx r0

00001014 <hang>:
    1014:   eafffffe    b   1014 <hang>

00001018 <dummy>:
    1018:   e12fff1e    bx  lr
    101c:   00001020    andeq   r1, r0, r0, lsr #32

00001020 <notmain>:
    1020:   e92d4010    push    {r4, lr}
    1024:   e3a04000    mov r4, #0
    1028:   e1a00004    mov r0, r4
    102c:   e2844001    add r4, r4, #1
    1030:   ebfffff8    bl  1018 <dummy>
    1034:   e3540ffa    cmp r4, #1000   ; 0x3e8
    1038:   1afffffa    bne 1028 <notmain+0x8>
    103c:   e3a00000    mov r0, #0
    1040:   e8bd4010    pop {r4, lr}
    1044:   e12fff1e    bx  lr

OTHER TIPS

I'm not sure that the accepted answer is necessarily correct here. There is a fundamental difference between Relocatable Code and what is considered Position-Independent Code.

Now I've been coding assembly a long time and on many different architectures and I've always thought of machine code as coming in three specific flavours:-

  • Position-Independent-Code
  • Relocatable-Code
  • Absolute-Code

Let's firstly discuss position-independent code. This is code that when assembled has all of its instructions relative to one other. So branches for instance specify an offset from the current Instruction Pointer (or Program Counter whichever you want to call it). Code that is position independent will consist of only one segment of code and have its data also contained within this segment (or section). There are exceptions to data being embedded within the same segment, but these are benefits usually passed onto you by the operating system or loader.

It's a very useful type of code because it means the operating system does not need to perform any post-loading operations on it in order to be able to start executing. It will just run anywhere that it is loaded in memory. Of course this type of code has its problems too, namely things like not being able to segregate code and data that might be suitable to differing memory types and limitations on size before relatives start moving out of range etc. to name but a few.

Relocatable-Code is quite like position-independent code in many ways, but it has a very subtle difference. As it's name suggests, this type of code is relocatable in that code can be loaded anywhere in memory, but usually has be relocated or fixed up before it is executable. In fact, some architectures that use this type of code embed things like "reloc" sections for this very purpose of fixing up the relocatable parts of the code. The downside of this type of code is that once it is relocated and fixed up, it almost becomes absolute in nature and fixed at its address.

What gives relocatable code its major advantage and the reason why it is the most prevalent code around is that it allows code to be easily broken down into sections. Each section can be loaded anywhere in memory to fit its requirements and then during relocation, any code that references another section can be fixed-up with a relocation table and thus the sections can be tied together nicely. The code itself is usually relative (as with the x86 architecture), but it need not be, as anything that might be out of range can be assembled as an relocatable instruction such that it consists of an offset added to its load address. It also means that limitations imposed by relative-addressing are no-longer an issue.

The final type of code is Absolute-Code. This code that is assembled to work at one specific address and will only work when loaded at that specific address. Branch and jump instructions all contain a fixed exact (absolute) address. It is a type of code usually found on embedded systems whereby it can be guaranteed that a piece of code will be loaded at that specific address as it is the only thing that is loaded there. On a modern computer, such absolute code wouldn't work because code needs to be loaded wherever there is free memory and there's never any guarantee that a certain memory range will be available. Absolute code does have its advantages though, mainly being that it is generally the fastest executing, but this can be platform dependent.

Anything that actually contains an address inside the code has an absolute address. Programs which contain no addresses within the code (everything is done with relative addresses) can be run from any address.

The assembler does not do this, the programmer does. I've done a bit of it in the past, for small stuff it's usually easy, once you go beyond the range of a relative jump it becomes quite a pain. IIRC the only two approaches are to slip relative jumps in between routines or to add a known offset to the current address, push it and then return. In the old days there was a third approach of calculating it and writing it into the code but that's no longer acceptable. It's been long enough that I won't swear there aren't other approaches.

IIRC the only way to "call" something without absolute addresses is to push the address you want to return to, the calculate the address, push it and return.

Note that in practice you usually use a hybrid approach. The assembler and linker store the information needed to make the adjustments, when the program is loaded into memory it's modified to run at whatever address it was loaded at. The actual image in memory is thus absolute but the file on disk works like it was relative but without all the headaches that normally introduces. (Note that the same approach is used with all higher level languages that actually produce native code.)

Basically, "absolute" mode means that the code and RAM variables will be placed exactly where you tell the assembler it will be, while "relocatable" means the assembler builds code chunks and specifies RAM needs that can be placed wherever the linker finds room for them.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top