【问题标题】:Branch Link to putchar causes segmentation fault in ARM指向 putchar 的分支链接导致 ARM 中的分段错误
【发布时间】:2019-04-16 02:18:17
【问题描述】:

我已经隔离了这样一个事实,即无论我多么努力,我都无法将链接分支到 putchar。

连两行都像

mov r0,$48
bl putchar

当我期望它打印 ASCII 0 时总是会出现段错误

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

mov r0,$48
b putchar

会起作用

我觉得我错过了一些非常基本的东西,但我不知道为什么。我只能假设它与putchar的返回有关,但我不知道是什么。

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

编辑:虽然上述陈述对于我来说即使是一个独立的程序也是正确的,但我最终会在一个子程序中实现它,我认为这可能很重要

【问题讨论】:

  • 你没有给我们太多。我的一个盲目猜测 - 你没有将 LR 保存在你需要的地方,所以你的代码在尝试从子例程返回时会在某个地方爆炸。
  • 请向我们展示您的整个程序。该错误可能在您的代码中的其他地方。
  • 什么处理器/架构?

标签: assembly arm


【解决方案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>
   ...

【讨论】:

  • 好的,这确实可以解决段错误,但我有一个小的后续问题,希望您能回答。据我了解,putchar 只返回写入的字符或 EOF,它应该在 r0 中是吗?出于某种原因,当我分支链接 put char 时,它似乎覆盖了一些较低寄存器(如 r2 和 r3)中的值,但不会覆盖 r5 及以上寄存器中的值。 putchar 是在使用这些寄存器,还是我没有看到其他原因?
  • @Josh Hu:我在阅读您的评论后补充了上面的答案。
  • 哇,谢谢!不幸的是,我没有足够的声誉来明显提高此回复的分数,但它非常有帮助和启发
  • @Josh Hu:没问题,我很高兴它有帮助。
  • 如果单个函数没有修改 fp,那么你不需要。还有这个和arm指令集不完全兼容,所以我们还需要知道是什么指令集?
【解决方案2】:

让编译器指导您。编译器并不完美,但如果我们假设已调试,那么它们的输出就可以工作。

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

(顺便说一下,这确实符合堆栈对齐方式)。

你发现你可以在那里分支:

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 不会这样做,它们只会构建错误的代码,如果您不注意的话,甚至最近的版本也会构建错误的代码。就看你那天的运气了。所以使用 bl 指令要非常小心。

我想我们都认为问题在于你没有读到 bl 修改了 lr 这意味着如果你想从使用 bl 的函数返回,你需要保存返回地址而不是销毁它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-28
    • 2018-10-19
    • 2021-05-25
    • 2015-07-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多