Home > Back-end >  Simple ARM assembly startup file doesn't seem to reach any code
Simple ARM assembly startup file doesn't seem to reach any code

Time:10-23

I'm trying to do a very simple program using ARM Thumb assembly, which will turn off the LED on my Arduino Due (The ATSAM3X8E pin linked to it seems to be pulled up internally by default). I'm doing this as a test before doing more complex things in assembly.

I've placed the inital SP value as well as all 15 mandatory system exception vectors in a section, and did a quick GPIO hardware registers initialization routine (Defines the pin as an output I can write to, disables the pull-up, so as long as I don't actually write into the output data register the output pin should remain off). (I didn't do any RAM loading routine because I don't have values other than read-only constants in the program at the moment, so no data section too).

I used the GNU Assembler, ld with a simple linker script that ensures the vector table is at the very beginning of the binary file, did a quick objcopy from the elf and got a nice raw binary that I flashed in the internal flash of the MCU using the BOSSA command-line. (See code below)

But nothing happened, the LED stayed high.

.thumb
.syntax unified

.section .vectors

.word 0x20001000 @StackTop, somewhere near the end of the SRAM
.word Reset_Handler
.word No_Handler @NMI_Handler
.word No_Handler @HardFault_Handler
.word No_Handler @MemFault_Handler
.word No_Handler @BusFault_Handler
.word No_Handler @UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word No_Handler @SVC_Handler
.word No_Handler @DebugMon_Handler
.word 0
.word No_Handler @PendSV_Handler
.word No_Handler @SysTick_Handler
.word No_Handler @IRQ0
@... (same with all IRQ's until 28)
.word No_Handler @IRQ29

.section .text

.thumb_func
Reset_Handler:
    BL init
    BL main
    B  No_Handler

.thumb_func
No_Handler: 
    B No_Handler    

.thumb_func 
init:
    LDR r0, =WDT_MR @Disable Watchdog Timer
    LDR r1, [r0]
    ORR r1, r1, #0x8000
    STR r1, [r0]
    
    LDR r0, =PIOB_WPMR @Disable GPIO Write Protection
    LDR r1, [r0]
    LDR r2, =PIOB_KEY
    LDR r3, [r2]
    ORR r1, r1, r3
    STR r1, [r0]
    
    LDR r0, =PIOB_PER @Disable peripeheral function (pure GPIO)
    LDR r1, [r0]
    LDR r2, =PIOB27
    LDR r3, [r2]
    ORR r1, r1, r3
    STR r1, [r0]
    
    LDR r0, =PIOB_OER @Define pin as output
    LDR r1, [r0]
    ORR r1, r1, r3
    STR r1, [r0]
    
    LDR r0, =PIOB_OWER @Allow writes to ODSR
    LDR r1, [r0]
    ORR r1, r1, r3
    STR r1, [r0]
    
    LDR r0, =PIOB_PUDR @Disable internal pull-up for the pin
    LDR r1, [r0]
    ORR r1, r1, r3
    STR r1, [r0]
    BX  lr

.thumb_func
main:
    @nothing much here
    B   main

WDT_MR:     .word 0x400E1A54
PIOB_WPMR:  .word 0x400E10E4
PIOB_KEY:   .word 0x50494F00
PIOB_PER:   .word 0x400E1000
PIOB_OER:   .word 0x400E1010
PIOB_OWER:  .word 0x400E10A0
PIOB_PUDR:  .word 0x400E1060
PIOB_ODSR:  .word 0x400E1038
PIOB27:     .word 0x8000000 @ 0x1u<<27

I did the same thing in C with the exact same registers (but letting Microchip Studio use ARM GCC for me) and it worked this time. In case the error is in my custom linker script, here it is :

MEMORY
{
  flash    (rx)  : ORIGIN = 0x00080000, LENGTH = 0x00080000
  ram      (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00018000
}

SECTIONS
{
    .text :
    {
        *(.vectors)
        *(.text)
    } > flash
}

What I'm afraid of is that the program does not actually seem to reach any of the initialization code.

CodePudding user response:

If I take a subset of your program

.thumb
.syntax unified

.section .vectors

.word 0x20001000
.word Reset_Handler

.section .text

.thumb_func
Reset_Handler:
    BL init
    BL main
    B  No_Handler

.thumb_func
No_Handler: 
    B No_Handler    

.thumb_func 
init:
    LDR r0, =WDT_MR @Disable Watchdog Timer
    LDR r1, [r0]
    ORR r1, r1, #0x8000
    STR r1, [r0]

    BX  lr

.thumb_func
main:
    @nothing much here
    B   main

WDT_MR:     .word 0x400E1A54

assemble and link it with your linker script, then disassemble.

Disassembly of section .text:

00080000 <Reset_Handler-0x8>:
   80000:   20001000    andcs   r1, r0, r0
   80004:   00080009    andeq   r0, r8, r9

00080008 <Reset_Handler>:
   80008:   f000 f804   bl  80014 <init>
   8000c:   f000 f808   bl  80020 <main>
   80010:   e7ff        b.n 80012 <No_Handler>

00080012 <No_Handler>:
   80012:   e7fe        b.n 80012 <No_Handler>

00080014 <init>:
   80014:   4804        ldr r0, [pc, #16]   ; (80028 <WDT_MR 0x6>)
   80016:   6801        ldr r1, [r0, #0]
   80018:   f441 4100   orr.w   r1, r1, #32768  ; 0x8000
   8001c:   6001        str r1, [r0, #0]
   8001e:   4770        bx  lr

00080020 <main>:
   80020:   e7fe        b.n 80020 <main>

00080022 <WDT_MR>:
   80022:   400e1a54    andmi   r1, lr, r4, asr sl
   80026:   00220000    eoreq   r0, r2, r0
   8002a:   Address 0x000000000008002a is out of bounds.

The first thing you have to examine is the vector table.

00080000 <Reset_Handler-0x8>:
   80000:   20001000    andcs   r1, r0, r0
   80004:   00080009    andeq   r0, r8, r9

00080008 <Reset_Handler>:

The first entry is the sp init value, and that looks fine, the second is the reset vector, and the lsbit must be set, and it is, that is correct, so you will not instantly fail, there is some hope.

As pointed out in comments though

00080014 <init>:
   80014:   4804        ldr r0, [pc, #16]   ; (80028 <WDT_MR 0x6>)
...
00080022 <WDT_MR>:
   80022:   400e1a54    andmi   r1, lr, r4, asr sl

Is NOT putting the value 0x400e1a54 in r0, but instead the ADDRESS OF WDT_MR in r0. Which the disassembler sort of mangled. But basically it puts 0x00080022 in r0. Which is not what you want.

So if I do this:

.thumb
.syntax unified

.section .vectors

.word 0x20001000
.word Reset_Handler

.section .text

.equ WDT_MR, 0x400E1A54


.thumb_func
Reset_Handler:
    BL init
    BL main
    B  No_Handler

.thumb_func
No_Handler: 
    B No_Handler    

.thumb_func 
init:
    LDR r0, =WDT_MR @Disable Watchdog Timer
    LDR r1, [r0]
    ORR r1, r1, #0x8000
    STR r1, [r0]

    BX  lr

.thumb_func
main:
    @nothing much here
    B   main
.align
.word 0x12345678    

And the .align at the bottom and that extra word (not really needed I assume) is to make the disassembly easier to read.

Gives this

00080014 <init>:
   80014:   4804        ldr r0, [pc, #16]   ; (80028 <main 0x8>)
   80016:   6801        ldr r1, [r0, #0]
   80018:   f441 4100   orr.w   r1, r1, #32768  ; 0x8000
   8001c:   6001        str r1, [r0, #0]
   8001e:   4770        bx  lr

00080020 <main>:
   80020:   e7fe        b.n 80020 <main>
   80022:   bf00        nop
   80024:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000
   80028:   400e1a54    andmi   r1, lr, r4, asr sl

And now 0x400e1a54 IS being loaded into r0, so that you can then control that register in the logic.

Visually inspect the disassemble to see if it is doing what you think it is doing.

WDT_MR in your code is a label, which is basically an address. So when you do ldr r0,=WDT_MR you are asking for the ADDRESS OF. Now here is another thing you could do:

.thumb
.syntax unified

.section .vectors

.word 0x20001000
.word Reset_Handler

.section .text

.thumb_func
Reset_Handler:
    BL init
    BL main
    B  No_Handler

.thumb_func
No_Handler: 
    B No_Handler    

.thumb_func 
init:
    LDR r0, WDT_MR @Disable Watchdog Timer
    LDR r1, [r0]
    ORR r1, r1, #0x8000
    STR r1, [r0]

    BX  lr

.thumb_func
main:
    @nothing much here
    B   main
    .align
WDT_MR:     .word 0x400E1A54

so you were one character away from success

00080014 <init>:
   80014:   4803        ldr r0, [pc, #12]   ; (80024 <WDT_MR>)
   80016:   6801        ldr r1, [r0, #0]
   80018:   f441 4100   orr.w   r1, r1, #32768  ; 0x8000
   8001c:   6001        str r1, [r0, #0]
   8001e:   4770        bx  lr

00080020 <main>:
   80020:   e7fe        b.n 80020 <main>
   80022:   bf00        nop

00080024 <WDT_MR>:
   80024:   400e1a54    andmi   r1, lr, r4, asr sl

Now it is loading the value at that label not the address of the label. And I suspect this is actually what you want to do rather than the .equ thing. YMMV. if you do the .EQU thing then the assembler is creating the pool, if you do it then you can control where these things live.

Now you potentially had a second problem that was shown and talked about above, and I solved it:

 00080022 <WDT_MR>:
   80022:   400e1a54    andmi   r1, lr, r4, asr sl
   80026:   00220000    eoreq   r0, r2, r0
   8002a:   Address 0x000000000008002a is out of bounds.

Had you gotten the ldr right you would have been doing a 32 bit load from 0x80022, which is not a 32 bit aligned address. Before a pool like that you want to align on at least a 4 byte boundary and for arm gas, the .align will suffice:

.thumb_func
main:
    @nothing much here
    B   main
    .align
WDT_MR:     .word 0x400E1A54

00080020 <main>:
   80020:   e7fe        b.n 80020 <main>
   80022:   bf00        nop

00080024 <WDT_MR>:
   80024:   400e1a54    andmi   r1, lr, r4, asr sl

Notice the nop the tool added as padding, if we were to:

.thumb_func
main:
    @nothing much here
    B   main
    adds r0,#1
    .align
WDT_MR:     .word 0x400E1A54

00080020 <main>:
   80020:   e7fe        b.n 80020 <main>
   80022:   3001        adds    r0, #1

00080024 <WDT_MR>:
   80024:   400e1a54    andmi   r1, lr, r4, asr sl

It is still properly aligned.

  • Related