ARM-C互操作

gst*_*gst 4 c assembly thumb lpc cortex-m

我正在尝试一个用于 ARM-C 互操作的简单程序。这是代码:

 #include<stdio.h>
#include<stdlib.h>

int Double(int a);
extern int Start(void);
int main(){

int result=0;
printf("in C main\n");
result=Start();
printf("result=%d\n",result);
return 0;
}

int Double(int a)
{
  printf("inside double func_argument_value=%d\n",a);

  return (a*2);
}
Run Code Online (Sandbox Code Playgroud)

汇编文件如下:

.syntax unified
    .cpu cortex-m3
    .thumb

    .align
    .global Start
    .global Double
    .thumb_func



Start:
      mov r10,lr
      mov r0,#42
      bl Double
      mov lr,r10
      mov r2,r0
      mov pc,lr
Run Code Online (Sandbox Code Playgroud)

在 LPC1769(嵌入式艺术家板)上调试期间,我在指令“ result=Start() ”上收到硬故障错误。我正在尝试在这里进行arm-C网络互连。上述语句(result=Start())执行过程中lr值为0x0000029F,即错误指令所在,pc值为0x0000029E。这就是我在 r1 中得到错误指令的方式

__asm("mrs r0,MSP\n"
"isb\n"
"ldr r1,[r0,#24]\n");
Run Code Online (Sandbox Code Playgroud)

有人可以解释一下我哪里出错了吗?任何解决方案表示赞赏。先感谢您。

我是 cortex-m3 的初学者,正在使用由 Code_Red 提供支持的 NXP LPCXpresso IDE。这是我的代码的反汇编。

      IntDefaultHandler:
00000269:   push {r7} 
0000026b:   add r7, sp, #0
0000026d:   b.n 0x26c <IntDefaultHandler+4>
0000026f:   nop 
00000271:   mov r3, lr
00000273:   mov.w r0, #42 ; 0x2a
00000277:   bl 0x2c0 <Double>
0000027b:   mov lr, r3
0000027d:   mov r2, r0
0000027f:   mov pc, lr



main:
00000281:   push {r7, lr}
00000283:   sub sp, #8
00000285:   add r7, sp, #0
00000287:   mov.w r3, #0
0000028b:   str r3, [r7, #4]
0000028d:   movw r3, #11212 ; 0x2bcc
00000291:   movt r3, #0
00000295:   mov r0, r3
00000297:   bl 0xd64 <printf>
0000029b:   bl 0x270 <Start>
0000029f:   mov r3, r0
000002a1:   str r3, [r7, #4]
000002a3:   movw r3, #11224 ; 0x2bd8
000002a7:   movt r3, #0
000002ab:   mov r0, r3
000002ad:   ldr r1, [r7, #4]
000002af:   bl 0xd64 <printf>
000002b3:   mov.w r3, #0
000002b7:   mov r0, r3
000002b9:   add.w r7, r7, #8
000002bd:   mov sp, r7
000002bf:   pop {r7, pc}

Double:
000002c0:   push {r7, lr}
000002c2:   sub sp, #8
000002c4:   add r7, sp, #0
000002c6:   str r0, [r7, #4]
000002c8:   movw r3, #11236 ; 0x2be4
000002cc:   movt r3, #0
000002d0:   mov r0, r3
000002d2:   ldr r1, [r7, #4]
000002d4:   bl 0xd64 <printf>
000002d8:   ldr r3, [r7, #4]
000002da:   mov.w r3, r3, lsl #1
000002de:   mov r0, r3
000002e0:   add.w r7, r7, #8
000002e4:   mov sp, r7
000002e6:   pop {r7, pc}
Run Code Online (Sandbox Code Playgroud)

根据您的建议 Dwelch,我已将 r10 更改为 r3。

old*_*mer 5

我想你的意思是互通而不是网络互联?LPC1769 是一个 cortex-m3,仅是拇指/拇指 2,因此它不支持 ARM 指令,因此该平台没有可用的互操作。尽管如此,还是用编译器来看看会发生什么:

首先让编译器为你做这件事,然后自己在 asm 中尝试......

开始.s

.thumb
.globl _start
_start:
    ldr r0,=hello
    mov lr,pc
    bx r0
hang : b hang
Run Code Online (Sandbox Code Playgroud)

你好ç

extern unsigned int two ( unsigned int );
unsigned int hello ( unsigned int h )
{
    return(two(h)+7);
}
Run Code Online (Sandbox Code Playgroud)

二.c

unsigned int two ( unsigned int t )
{
    return(t+5);
}
Run Code Online (Sandbox Code Playgroud)

生成文件

hello.list : start.s hello.c two.c
    arm-none-eabi-as -mthumb start.s -o start.o
    arm-none-eabi-gcc -c -O2 hello.c -o hello.o
    arm-none-eabi-gcc -c -O2 -mthumb two.c -o two.o
    arm-none-eabi-ld -Ttext=0x1000 start.o hello.o two.o -o hello.elf
    arm-none-eabi-objdump -D hello.elf > hello.list

clean :
    rm -f *.o
    rm -f *.elf
    rm -f *.list
Run Code Online (Sandbox Code Playgroud)

产生 hello.list

Disassembly of section .text:

00001000 <_start>:
    1000:   4801        ldr r0, [pc, #4]    ; (1008 <hang+0x2>)
    1002:   46fe        mov lr, pc
    1004:   4700        bx  r0

00001006 <hang>:
    1006:   e7fe        b.n 1006 <hang>
    1008:   0000100c    andeq   r1, r0, ip

0000100c <hello>:
    100c:   e92d4008    push    {r3, lr}
    1010:   eb000004    bl  1028 <__two_from_arm>
    1014:   e8bd4008    pop {r3, lr}
    1018:   e2800007    add r0, r0, #7
    101c:   e12fff1e    bx  lr

00001020 <two>:
    1020:   3005        adds    r0, #5
    1022:   4770        bx  lr
    1024:   0000        movs    r0, r0
    ...

00001028 <__two_from_arm>:
    1028:   e59fc000    ldr ip, [pc]    ; 1030 <__two_from_arm+0x8>
    102c:   e12fff1c    bx  ip
    1030:   00001021    andeq   r1, r0, r1, lsr #32
    1034:   00000000    andeq   r0, r0, r0
Run Code Online (Sandbox Code Playgroud)

hello.o 自行反汇编:

00000000 <hello>:
   0:   e92d4008    push    {r3, lr}
   4:   ebfffffe    bl  0 <two>
   8:   e8bd4008    pop {r3, lr}
   c:   e2800007    add r0, r0, #7
  10:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

编译器使用 bl 假设/希望它将从arm调用arm。但事实并非如此,所以他们在里面放了一个蹦床。

0000100c <hello>:
    100c:   e92d4008    push    {r3, lr}
    1010:   eb000004    bl  1028 <__two_from_arm>
    1014:   e8bd4008    pop {r3, lr}
    1018:   e2800007    add r0, r0, #7
    101c:   e12fff1e    bx  lr


00001028 <__two_from_arm>:
    1028:   e59fc000    ldr ip, [pc]    ; 1030 <__two_from_arm+0x8>
    102c:   e12fff1c    bx  ip
    1030:   00001021    andeq   r1, r0, r1, lsr #32
    1034:   00000000    andeq   r0, r0, r0
Run Code Online (Sandbox Code Playgroud)

bl 到 __two_from_arm 是arm 模式到arm 模式的分支链接。设置了 lsbit 的目标函数(二)的地址(告诉 bx 切换到拇指模式)被加载到一次性寄存器 ip (r12?)中,然后 bx ip 发生切换模式。分支链接在lr中设置了返回地址,这无疑是一个arm模式地址(ls位为零)。

00001020 <two>:
    1020:   3005        adds    r0, #5
    1022:   4770        bx  lr
    1024:   0000        movs    r0, r0
Run Code Online (Sandbox Code Playgroud)

Two() 函数执行其操作并返回,请注意,互操作时必须使用 bx lr 而不是 mov pc,lr。基本上,如果您运行的不是没有 T 的 ARMv4,或者没有 T 的 ARMv5,mov pc,lr 是一个不错的习惯。但是任何 ARMv4T 或更新版本(ARMv5T 或更新版本)都使用 bx lr 从函数返回,除非您有特殊原因不这样做。(出于同样的原因,也避免使用 pop {pc} ,除非您确实需要保存该指令并且不互通)。现在在cortex-m3上,只有thumb+thumb2,你不能互操作,所以你可以使用mov pc,lr和pop {pc},但是代码不可移植,这不是一个好习惯,因为这个习惯会当你切换回arm编程时会咬你一口。

因此,由于 hello 在使用 bl(设置链接寄存器)时处于 Arm 模式,two_from_arm 中的 bx 不会触及链接寄存器,因此当 Two() 以 bx lr 返回时,它会在 bl __two_from_arm 之后返回到 Arm 模式hello() 函数中的一行。

另请注意拇指函数后的额外 0x0000,这是为了在字边界上对齐程序,以便对齐以下 Arm 代码......

看看编译器如何将拇指更改为手臂,如下所示

unsigned int three ( unsigned int );
unsigned int two ( unsigned int t )
{
    return(three(t)+5);
}
Run Code Online (Sandbox Code Playgroud)

并将该函数放入 hello.c 中

extern unsigned int two ( unsigned int );
unsigned int hello ( unsigned int h )
{
    return(two(h)+7);
}

unsigned int three ( unsigned int t )
{
    return(t+3);
}
Run Code Online (Sandbox Code Playgroud)

现在我们有了另一个蹦床

00001028 <two>:
    1028:   b508        push    {r3, lr}
    102a:   f000 f80b   bl  1044 <__three_from_thumb>
    102e:   3005        adds    r0, #5
    1030:   bc08        pop {r3}
    1032:   bc02        pop {r1}
    1034:   4708        bx  r1
    1036:   46c0        nop         ; (mov r8, r8)
...
00001044 <__three_from_thumb>:
    1044:   4778        bx  pc
    1046:   46c0        nop         ; (mov r8, r8)
    1048:   eafffff4    b   1020 <three>
    104c:   00000000    andeq   r0, r0, r0
Run Code Online (Sandbox Code Playgroud)

现在这是一个非常酷的蹦床。bl 到 Three_from_thumb 处于拇指模式,并且链接寄存器设置为返回到 Two() 函数,而 lsbit 无疑设置为指示返回到拇指模式。

蹦床从 bx pc 开始,pc 被设置为前面两条指令,并且 pc 内部始终将 lsbit 清零,因此 bx pc 总是会带您进入 Arm 模式(如果尚未处于 Arm 模式),并且在任一模式下都前面有两条指令。bx pc 前面的两条指令是一条 arm 指令,它分支(不是分支链接!)到三个函数,完成蹦床。

请注意我首先是如何编写对 hello() 的调用的

_start:
        ldr r0,=hello
        mov lr,pc
        bx r0
    hang : b hang
Run Code Online (Sandbox Code Playgroud)

这实际上行不通,会吗?它可以让你从手臂到拇指,但不能从拇指到手臂。我将把它作为练习留给读者。

如果你把 start.s 改成这样

.thumb

.globl _start
_start:
    bl hello
hang : b hang
Run Code Online (Sandbox Code Playgroud)

链接器会照顾我们:

00001000 <_start>:
    1000:   f000 f820   bl  1044 <__hello_from_thumb>

00001004 <hang>:
    1004:   e7fe        b.n 1004 <hang>
    ...

00001044 <__hello_from_thumb>:
    1044:   4778        bx  pc
    1046:   46c0        nop         ; (mov r8, r8)
    1048:   eaffffee    b   1008 <hello>
Run Code Online (Sandbox Code Playgroud)

我总是会反汇编这样的程序,以确保编译器和链接器解决这些问题。另请注意,例如 __hello_from_thumb 可以在任何拇指函数中使用,如果我从几个地方调用 hello,一些arm,一些thumb,并且 hello 是为arm编译的,那么arm调用将直接调用hello(如果它们可以到达它)并且所有拇指调用将共享相同的 hello_from_thumb (如果它们可以到达它)。

这些示例中的编译器假设代码保持相同模式(简单分支链接),并且链接器添加了互操作代码...

如果您确实指的是互联网络而不是互通,那么请描述那是什么,我将删除此答案。

编辑:

您在调用 Double 期间使用寄存器来保存 lr ,这是行不通的,没有寄存器可以为您需要使用内存而工作,最简单的是堆栈。看看编译器是如何做的:

00001008 <hello>:
    1008:   e92d4008    push    {r3, lr}
    100c:   eb000009    bl  1038 <__two_from_arm>
    1010:   e8bd4008    pop {r3, lr}
    1014:   e2800007    add r0, r0, #7
    1018:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

r3 被推入可能是为了在 64 位边界上对齐堆栈(使其更快)。需要注意的是,链接寄存器保留在堆栈中,但弹出操作不会弹出到 pc,因为这不是 ARMv4 构建,因此需要 bx 才能从函数返回。因为这是arm模式,所以我们可以弹出lr并简单地bx lr。

对于拇指,您只能直接按下 r0-r7 和 lr,并直接弹出 r0-r7 和 pc,您不想弹出到 pc,因为只有当您处于相同模式(拇指或手臂)时,这才有效。这对于 cortex-m 来说很好,或者如果你知道所有调用者是什么也很好,但总的来说不好。所以

00001024 <two>:
    1024:   b508        push    {r3, lr}
    1026:   f000 f811   bl  104c <__three_from_thumb>
    102a:   3005        adds    r0, #5
    102c:   bc08        pop {r3}
    102e:   bc02        pop {r1}
    1030:   4708        bx  r1
Run Code Online (Sandbox Code Playgroud)

同样的处理 r3 用作虚拟寄存器,以保持堆栈对齐以提高性能(我使用 gcc 4.8.0 的默认构建,这可能是具有 64 位 axi 总线的平台,指定架构可能会删除该额外的寄存器)。因为我们不能弹出 pc,所以我假设因为 r1 和 r3 会乱序并且选择了 r3(它们可以选择 r2 并保存指令),所以有两次弹出,一个用于删除堆栈上的虚拟值,一个用于删除堆栈上的虚拟值。其他将返回值放入寄存器中,以便它们可以通过它返回。

您的 Start 函数不符合 ABI,因此当您将其与 printf 调用等大型库混合时,毫无疑问您会崩溃。如果你不这样做,那真是运气不好。main 的汇编列表显示,r4 和 r10 都没有被使用,并且假设除引导程序之外没有调用 main(),那么这就是为什么您使用 r4 或 r10 的原因。

如果这确实是 LPC1769,那么整个讨论都是无关紧要的,因为它不支持 ARM,也不支持互通(互通 = ARM 模式代码和拇指模式代码的混合)。您的问题与互通无关,您没有互通(请注意函数末尾的 pop {pc} )。您的问题可能与您的汇编代码有关。

编辑2:

更改 makefile 以指定 cortex-m

00001008 <hello>:
    1008:   b508        push    {r3, lr}
    100a:   f000 f805   bl  1018 <two>
    100e:   3007        adds    r0, #7
    1010:   bd08        pop {r3, pc}
    1012:   46c0        nop         ; (mov r8, r8)

00001014 <three>:
    1014:   3003        adds    r0, #3
    1016:   4770        bx  lr

00001018 <two>:
    1018:   b508        push    {r3, lr}
    101a:   f7ff fffb   bl  1014 <three>
    101e:   3005        adds    r0, #5
    1020:   bd08        pop {r3, pc}
    1022:   46c0        nop         ; (mov r8, r8)
Run Code Online (Sandbox Code Playgroud)

首先也是最重要的是,它都是拇指,因为 cortex-m 上没有手臂模式,其次函数返回不需要 bx(因为没有手臂/拇指模式更改)。所以 pop {pc} 就可以了。

奇怪的是,虚拟寄存器仍然在推送中使用,我尝试了arm7tdmi/armv4t构建,它仍然这样做,所以还有一些其他标志可以用来消除这种行为。

如果您的愿望是学习如何创建一个可以从 C 调用的汇编函数,那么您应该这样做。创建一个有点类似于您要在 asm 中创建的函数框架的 C 函数:

extern unsigned int Double ( unsigned int );
unsigned int Start ( void )
{
    return(Double(42));
}
Run Code Online (Sandbox Code Playgroud)

组装然后拆卸

00000000 <Start>:
   0:   b508        push    {r3, lr}
   2:   202a        movs    r0, #42 ; 0x2a
   4:   f7ff fffe   bl  0 <Double>
   8:   bd08        pop {r3, pc}
   a:   46c0        nop         ; (mov r8, r8)
Run Code Online (Sandbox Code Playgroud)

并在组装功能时从它开始。

.globl Start
.thumb_func
Start:
   push {lr}
   mov  r0, #42 
   bl   Double
   pop  {pc}
Run Code Online (Sandbox Code Playgroud)

或者阅读 gcc 的 arm abi,​​了解哪些寄存器可以使用,哪些不能在不将它们保存在堆栈上的情况下使用,哪些寄存器用于传递和返回参数。