分支链接到putchar导致ARM中的分段错误

时间:2019-04-16 02:18:18

标签: assembly arm

我已经隔离了一个事实,无论我如何努力,都无法将其链接到putchar。

即使是两行,就像

mov r0,$48
bl putchar

当我期望它输出ASCII 0时,它将始终出现段错误

我可以分支到putchar,它可以工作,但是我不能分支链接。含义

mov r0,$48
b putchar

可以工作

我觉得我缺少了一些难以置信的基本知识,但我不知道为什么。我只能认为这与putchar的收益有关,但我不知道是什么。

很抱歉,这似乎是一个愚蠢的问题,但是老实说,我找不到关于此的资源。

编辑:尽管以上陈述对于我自己的独立程序都是正确的,但最终我将在一个子例程中实现它,我认为这可能很重要

2 个答案:

答案 0 :(得分:1)

这很难说,因为您没有提供足够的代码,但是您可能会缺少符合以下要求的代码:    ARM calling conventions
完整的代码应将fp,lr保存在堆栈上,而不是调用putchar,然后还原fp,lr并返回或还原fp,pc,这基本上是相同的。

创建一个名为example.s的文件,其内容如下:

        .arch armv7-a
        .align  2
        .globl main
        .arch armv7-a
        .syntax unified
        .arm
main:
         push    {fp, lr}
         mov     r0, #48
         bl      putchar
         pop     {fp, pc}

编译并链接它-我编译了一个静态版本,因为我使用qemu-arm进行了测试:

/opt/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -static -O0 -o example  example.s

执行它-在我的情况下使用qemu-arm

/opt/qemu-3.1.0-static/bin/qemu-arm example
0

请注意:

pop     {fp, pc}

等效于:

pop     {fp, lr}
ret

我希望有帮助。

更新

putchar()确实返回已传递的字符或r0中的EOF。由于r0并未在main中进行修改,因此它包含的值将返回给被调用方(即bash),并且可以使用echo $?命令来看到它:

opt/qemu-3.1.0/bin/qemu-arm example
0
echo $?
48

根据ARM calling conventions的第15页,在子例程调用中保留了r4-r8,但可能没有保存r0-r3。

使用objdump拆卸示例程序:

/opt/arm/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-objdump -D example > example.lst

在example.lst中,您可以看到putchar()为:
1)根据ARM调用约定保留r4,r5,r6,r7,r8,lr,
2)利用您提到的已被修改的寄存器:

00016f50 <putchar>:
   16f50:   e92d41f0    push    {r4, r5, r6, r7, r8, lr}
   16f54:   e30354a8    movw    r5, #13480  ; 0x34a8
   16f58:   e3405008    movt    r5, #8
   16f5c:   e1a06000    mov r6, r0
   16f60:   e5954000    ldr r4, [r5]
   16f64:   e5943000    ldr r3, [r4]
   16f68:   e3130902    tst r3, #32768  ; 0x8000
   16f6c:   1a000015    bne 16fc8 <putchar+0x78>
   16f70:   e5943048    ldr r3, [r4, #72]   ; 0x48
   16f74:   ee1d7f70    mrc 15, 0, r7, cr13, cr0, {3}
   16f78:   e2477d13    sub r7, r7, #1216   ; 0x4c0
   16f7c:   e5932008    ldr r2, [r3, #8]
   16f80:   e1520007    cmp r2, r7
   16f84:   0a000030    beq 1704c <putchar+0xfc>
   16f88:   e3a02001    mov r2, #1
   16f8c:   e1931f9f    ldrex   r1, [r3]
   16f90:   e3510000    cmp r1, #0
   16f94:   1a000003    bne 16fa8 <putchar+0x58>
   16f98:   e1830f92    strex   r0, r2, [r3]
   16f9c:   e3500000    cmp r0, #0
   16fa0:   1afffff9    bne 16f8c <putchar+0x3c>
   16fa4:   f57ff05b    dmb ish
   16fa8:   1a00002d    bne 17064 <putchar+0x114>
   16fac:   e5943048    ldr r3, [r4, #72]   ; 0x48
   16fb0:   e5950000    ldr r0, [r5]
   16fb4:   e5837008    str r7, [r3, #8]
   16fb8:   e5932004    ldr r2, [r3, #4]
   16fbc:   e2822001    add r2, r2, #1
   16fc0:   e5832004    str r2, [r3, #4]
   16fc4:   ea000000    b   16fcc <putchar+0x7c>
   16fc8:   e1a00004    mov r0, r4
   16fcc:   e5903014    ldr r3, [r0, #20]
   16fd0:   e6efc076    uxtb    ip, r6
   16fd4:   e5902018    ldr r2, [r0, #24]
   16fd8:   e1530002    cmp r3, r2
   16fdc:   32832001    addcc   r2, r3, #1
   16fe0:   35802014    strcc   r2, [r0, #20]
   16fe4:   35c36000    strbcc  r6, [r3]
   16fe8:   2a000019    bcs 17054 <putchar+0x104>
   16fec:   e5943000    ldr r3, [r4]
   16ff0:   e3130902    tst r3, #32768  ; 0x8000
   16ff4:   1a000005    bne 17010 <putchar+0xc0>
   16ff8:   e5940048    ldr r0, [r4, #72]   ; 0x48
   16ffc:   e5903004    ldr r3, [r0, #4]
   17000:   e2433001    sub r3, r3, #1
   17004:   e5803004    str r3, [r0, #4]
   17008:   e3530000    cmp r3, #0
   1700c:   0a000001    beq 17018 <putchar+0xc8>
   17010:   e1a0000c    mov r0, ip
   17014:   e8bd81f0    pop {r4, r5, r6, r7, r8, pc}
   17018:   e5803008    str r3, [r0, #8]
   1701c:   f57ff05b    dmb ish
   17020:   e1902f9f    ldrex   r2, [r0]
   17024:   e1801f93    strex   r1, r3, [r0]
   17028:   e3510000    cmp r1, #0
   1702c:   1afffffb    bne 17020 <putchar+0xd0>
   17030:   e3520001    cmp r2, #1
   17034:   dafffff5    ble 17010 <putchar+0xc0>
   17038:   e3a01081    mov r1, #129    ; 0x81
   1703c:   e3a02001    mov r2, #1
   17040:   e3a070f0    mov r7, #240    ; 0xf0
   17044:   ef000000    svc 0x00000000
   17048:   eafffff0    b   17010 <putchar+0xc0>
   1704c:   e1a00004    mov r0, r4
   17050:   eaffffd8    b   16fb8 <putchar+0x68>
   ...

答案 1 :(得分:0)

让编译器指导您...编译器并不完美,但是如果我们假定已调试,则它们的输出有效。

void next ( char );
void fun ( void )
{
    next(0x33);
}

00000000 <fun>:
   0:   e92d4010    push    {r4, lr}
   4:   e3a00033    mov r0, #51 ; 0x33
   8:   ebfffffe    bl  0 <next>
   c:   e8bd4010    pop {r4, lr}
  10:   e12fff1e    bx  lr

未链接,但表明需要保留lr。 R4可以使堆栈在64位边界上对齐,具体取决于编译器的版本,它可能早于此规则,并且只会压入lr并将其弹出。

最大的兼容性,最古老的拇指指令,适用于armv4t到armv7a和armv8m。

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   2033        movs    r0, #51 ; 0x33
   4:   f7ff fffe   bl  0 <next>
   8:   bc10        pop {r4}
   a:   bc01        pop {r0}
   c:   4700        bx  r0

(确实符合堆栈对齐btw)。

您发现可以在此处分支

00000000 <fun>:
   0:   2033        movs    r0, #51 ; 0x33
   2:   f7ff bffe   b.w 0 <next>

,但是在这种情况下是尾部优化。如果您想分支链接并返回此链接对您不起作用。

int next ( char );
int fun ( char a )
{
    return(next(a)+1);
}

00000000 <fun>:
   0:   b508        push    {r3, lr}
   2:   f7ff fffe   bl  0 <next>
   6:   3001        adds    r0, #1
   8:   bd08        pop {r3, pc}

有时pop支持与pop {pc}互通,不是原来的拇指,后来是。就像r4高于r3一样,这里只是用于64位堆栈对齐,许多低位寄存器在这里都可以工作,这是无关紧要的事情。

我们需要了解体系结构的另一个原因是您是否可以/应该使用bl,因为bl在两种模式之间不起作用,但是blx在您的体系结构支持下可以使用。

00001000 <fun>:
    1000:   b508        push    {r3, lr}
    1002:   f000 e804   blx 100c <next>
    1006:   3001        adds    r0, #1
    1008:   bd08        pop {r3, pc}
    100a:   bf00        nop

0000100c <next>:
    100c:   e2800002    add r0, r0, #2
    1010:   e12fff1e    bx  lr



00001000 <fun>:
    1000:   e92d4010    push    {r4, lr}
    1004:   eb000003    bl  1018 <__next_from_arm>
    1008:   e8bd4010    pop {r4, lr}
    100c:   e2800001    add r0, r0, #1
    1010:   e12fff1e    bx  lr

00001014 <next>:
    1014:   3002        adds    r0, #2
    1016:   4770        bx  lr

00001018 <__next_from_arm>:
    1018:   e59fc000    ldr ip, [pc]    ; 1020 <__next_from_arm+0x8>
    101c:   e12fff1c    bx  ip
    1020:   00001015    andeq   r1, r0, r5, lsl r0
    1024:   00000000    andeq   r0, r0, r0

在这两种情况下,链接器都解决了问题,但是旧版本的gnu不能解决这些问题,它们只会生成错误的代码,如果您不注意的话,即使是较新的gnu也会生成错误的代码。取决于你那天有多幸运。因此,使用bl指令时要非常小心。

我假设我们都相信问题是您没有读过bl修改lr的意思,这意味着如果您想从使用bl的函数中返回,则需要保存该返回地址而不要销毁它。