Calling ARM assembly from C, GCC (bare metal)

前端 未结 4 476
广开言路
广开言路 2021-02-06 06:07

I am trying to do some bare-metal programming in ARM with GCC and testing on QEMU. Whenever I call into an ARM label from C, my program hangs. I have a simple example of code

4条回答
  •  长情又很酷
    2021-02-06 06:57

    see http://github.com/dwelch67/yagbat qemu directory.

    Here are a couple of examples of calling arm or thumb from arm

    start_vector:
        mov sp,#0x20000
        ;@ call an arm function from arm
        bl notmain
    
        ;@ call a thumb function frm arm
        ldr r0,=0xAABBAABB
        bl hexstring_trampoline
    
        ;@ call a thumb function frm arm
        ldr r0,=0x12341234
        ldr r1,hexstring_addr
        mov lr,pc
        bx r1
    
        ;@ call a thumb function frm arm
        ldr r0,=0x12312344
        bl hexstring_trampoline
    
    hang:
        b hang
    
    hexstring_trampoline:
        ldr r1,hexstring_addr
        bx r1
    
    hexstring_addr: .word hexstring
    

    If you look at the instruction set reference you will see that you need to use BX or BLX to switch between arm and thumb states. BLX is not as widely supported as BX.

    From a definition standpoint the program counter, pc is two instructions ahead during execution of an instruction. for thumb that is 4 bytes, for arm 8 bytes. Either case two instructions. To simulate a bl which cant be used to change state, you need to load the link register with the return address, and use a bx to branch to the function changing state depending on the lsbit of the address. so the

    mov lr,pc
    bx r1
    here:
    

    the mov lr,pc above loads the address of here: which is our return address, bx r1 in a state independent manner calls the function. the lsbit of the lr address indicates the mode to return to and you need to always use bx to return

    pre_thumb:
    ldr pc,lr
    
    thumb_capable:
    bx lr
    

    The compiler allocates a bl instruction for calling functions, the linker fills in the rest later, if it is too far of a reach then it needs a trampoline function which the linker is adding itself. Likewise if you need to change modes the bl calls a trampoline function that does that. I have modeled that in one of the above to mimic that, you can see it is a bit wasteful, hopefully my explanation of the compiler only allocating space for a bl makes that more clear, wasteful would be to always plan for a mode change and have to insert nops for the majority of the function calls in code.

    The code also includes a call to arm from thumb in assembler:

    .thumb
    
    .thumb_func
    .globl XPUT32
    XPUT32:
        push {lr}
        ;@ call an arm function from thumb asm
        ldr r2,=PUT32
        mov lr,pc
        bx r2
        pop {r2}
        bx r2
    

    mostly the same except you cannot pop to lr in thumb mode, you can pop to pc, but I dont think that switches modes, so you cant use it, you again need a spare register. You of course need to know the calling conventions to know what registers you can use or you can wrap another set of pushes and pops to preserve all but lr

        push {r2,lr}
        ;@ call an arm function from thumb asm
        ldr r2,=PUT32
        mov lr,pc
        bx r2
        pop {r2}
        mov lr,r2
        pop {r2}
        bx lr
    

    Thumb to thumb or arm to arm you just use a bl if you can reach. ldr pc,address if you cant.

提交回复
热议问题