Search code examples
gccarmcortex-m

Indirect function call uses odd address


When the GCC 4.7.3 (20121207) for ARM Cortex-M3 takes the address of a function it doesn't get the exact address of the function. I can see an off-by-one in that pointer.

// assume at address 0x00001204;
int foo() {
  return 42;
}

void bar() {
  int(*p)() = &foo;  // p = 0x1205;
  p();               // executed successfully
  foo();             // assembly: "bl 0x00001204;"
}

Although the pointer points to an odd address, the execution is successful. I would expect an exception at this point. Why does it takes that strange address and why doesn't it hurt.

Edit

  • The SO article describes a difference between thumb and ARM mode. Why is that offset not visible when the function is called directly although the CPU is in the same mode?
  • Should the odd address be kept or would resetting the bit 0 cause hard? (what I could not see until now)

Solution

  • I cobbled up something from one of my examples to quickly demonstrate what is going on.

    vectors.s:

    /* vectors.s */
    .cpu cortex-m3
    .thumb
    
    .word   0x20002000  /* stack top address */
    .word   _start      /* 1 Reset */
    .word   hang        /* 2 NMI */
    .word   hello       /* 3 HardFault */
    .word   hang        /* 4 MemManage */
    .word   hang        /* 5 BusFault */
    .word   hang        /* 6 UsageFault */
    .word   hang        /* 7 RESERVED */
    .word   hang        /* 8 RESERVED */
    .word   hang        /* 9 RESERVED*/
    .word   hang        /* 10 RESERVED */
    .word   hang        /* 11 SVCall */
    .word   hang        /* 12 Debug Monitor */
    .word   hang        /* 13 RESERVED */
    .word   hang        /* 14 PendSV */
    .word   hang        /* 15 SysTick */
    .word   hang        /* 16 External Interrupt(0) */
    .word   hang        /* 17 External Interrupt(1) */
    .word   hang        /* 18 External Interrupt(2) */
    .word   hang        /* 19 ...   */
    
    .thumb_func
    .global _start
    _start:
        /*ldr r0,stacktop */
        /*mov sp,r0*/
        bl notmain
        ldr r0,=notmain
        mov lr,pc
        bx r0
        b hang
    
    .thumb_func
    hang:   b .
    
    hello: b .
    
    .thumb_func
    .globl PUT32
    PUT32:
        str r1,[r0]
        bx lr
    
    .end
    

    blinker01.c:

    extern void PUT32 ( unsigned int, unsigned int );
    
    int notmain ( void )
    {
        PUT32(0x12345678,0xAABBCCDD);
        return(0);
    }
    

    Makefile:

    #ARMGNU = arm-none-eabi
    ARMGNU = arm-none-linux-gnueabi
    
    AOPS = --warn --fatal-warnings 
    COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding 
    
    
    all : blinker01.gcc.thumb.bin 
    
    vectors.o : vectors.s
        $(ARMGNU)-as vectors.s -o vectors.o
    
    blinker01.gcc.thumb.o : blinker01.c
        $(ARMGNU)-gcc $(COPS) -mthumb -c blinker01.c -o blinker01.gcc.thumb.o
    
    blinker01.gcc.thumb2.o : blinker01.c
        $(ARMGNU)-gcc $(COPS) -mthumb -mcpu=cortex-m3 -march=armv7-m -c blinker01.c -o blinker01.gcc.thumb2.o
    
    blinker01.gcc.thumb.bin : memmap vectors.o blinker01.gcc.thumb.o
        $(ARMGNU)-ld -o blinker01.gcc.thumb.elf -T memmap vectors.o blinker01.gcc.thumb.o
        $(ARMGNU)-objdump -D blinker01.gcc.thumb.elf > blinker01.gcc.thumb.list
        $(ARMGNU)-objcopy blinker01.gcc.thumb.elf blinker01.gcc.thumb.bin -O binary
    

    Disassembly:

    Disassembly of section .text:
    
    08000000 <_start-0x50>:
     8000000:   20002000    andcs   r2, r0, r0
     8000004:   08000051    stmdaeq r0, {r0, r4, r6}
     8000008:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     800000c:   0800005e    stmdaeq r0, {r1, r2, r3, r4, r6}
     8000010:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000014:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000018:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     800001c:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000020:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000024:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000028:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     800002c:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000030:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000034:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000038:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     800003c:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000040:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000044:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     8000048:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
     800004c:   0800005d    stmdaeq r0, {r0, r2, r3, r4, r6}
    
    08000050 <_start>:
     8000050:   f000 f80a   bl  8000068 <notmain>
     8000054:   4803        ldr r0, [pc, #12]   ; (8000064 <PUT32+0x4>)
     8000056:   46fe        mov lr, pc
     8000058:   4700        bx  r0
     800005a:   e7ff        b.n 800005c <hang>
    
    0800005c <hang>:
     800005c:   e7fe        b.n 800005c <hang>
    
    0800005e <hello>:
     800005e:   e7fe        b.n 800005e <hello>
    
    08000060 <PUT32>:
     8000060:   6001        str r1, [r0, #0]
     8000062:   4770        bx  lr
     8000064:   08000069    stmdaeq r0, {r0, r3, r5, r6}
    
    08000068 <notmain>:
     8000068:   b508        push    {r3, lr}
     800006a:   4803        ldr r0, [pc, #12]   ; (8000078 <notmain+0x10>)
     800006c:   4903        ldr r1, [pc, #12]   ; (800007c <notmain+0x14>)
     800006e:   f7ff fff7   bl  8000060 <PUT32>
     8000072:   2000        movs    r0, #0
     8000074:   bd08        pop {r3, pc}
     8000076:   46c0        nop         ; (mov r8, r8)
     8000078:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000
     800007c:   aabbccdd    bge 6ef33f8 <_start-0x110cc58>
    

    First off note hang vs hello, this is a gnuism you need to, in assembly, declare a label to be a thumb function in order for it to actually work for this kind of thing. hang is properly declared and the vector table properly uses the odd address, hello is not properly declared and the even address is put in there. C compiled code automatically does this properly.

    Here is a prime example of what you are asking though, bl to the C function notmain does not, cannot, use an odd address. But to use bx you ask for the address to the function main and that address is provided to the code as 0x8000069 for for a function at address 0x8000068, if you did a bx to 0x800068 on an ARMvsometingT it would switch to arm mode and crash eventually if it hit thumb mode (hopefully crash and not stumble along) on a cortex-m a bx to an even address should fault immediately.

    08000050 <_start>:
     8000050:   f000 f80a   bl  8000068 <notmain>
     8000054:   4803        ldr r0, [pc, #12]   ; (8000064 <PUT32+0x4>)
     8000056:   46fe        mov lr, pc
     8000058:   4700        bx  r0
     800005a:   e7ff        b.n 800005c <hang>
     8000064:   08000069    stmdaeq r0, {r0, r3, r5, r6}
    

    Why can't bl be odd? Look at the encoding above bl from 0x8000050 to 0x8000068, the pc is two ahead so 4 byte so take 0x8000068 - 0x8000054 = 0x14 divide that by 2 and you get 0x00A. That is the offset to the pc and that is what is encoded in the instructions (the 0A in the second half of the instruction). The divide by two is based on knowledge that thumb instructions are always 2 bytes (well at the time) and so they can reach twice as far if they put the offset in 2 byte instructions rather than in bytes. So the lsbit is lost of the delta between the two, so controlled by the hardware.

    What your code did was in one place you asked for the address of a thumb function which gives the odd address, the other case was looking at the disassembly of a branch link which is always even.