如何在ARM Assembly中的循环中使用分支?

时间:2018-11-19 11:15:21

标签: assembly raspberry-pi arm

我的程序使用.skip 1000在内存中创建一个二维数组。然后,使用以下循环,使用stdin的输入填充该数组:

    @@loop to store message in array
    @outer for loop over rows
    MOV r0,#0 @r0 = i (row index)
msgrowloop:
    CMP r0,r2 @compare to nrows
    BEQ msgendrowloop

    @multiply/accumulate instruction
    MLA r7, r3, r0, r6 @calculates the address of the first element in each row

    @inner for loop over columns
    MOV r1,#0 @r1 = j (column index)
msgcolumnloop:
    CMP r1,r3 @compare to ncolumns
    BEQ msgendcolumnloop

    @@@store from stdin

    PUSH {r0-r4}
    BL getchar @branch & link to getchar - reads single character from stdin

    CMP r0,#-1 @check if we're at the end of file
    BEQ msgendrowloop @if so, exit loop

    MOV r8, r0 @move character to r8
    POP {r0-r4}

    @@@store from stdin end

    @store r8 in memory and increase r7 by one byte
    STRB r8,[r7],#1
    ADD r1,r1,#1 @j += 1
    B msgcolumnloop
msgendcolumnloop:
    ADD r0,r0,#1 @i += 1
    B msgrowloop
msgendrowloop:
    @rest of the program...

现在,使用此命令我会遇到分段错误,但是如果我将stdin函数更改为此:

PUSH {r0-r4}
BL getchar @branch & link to getchar - reads single character from stdin

CMP r0, #-1 @check if we are at end of file
MOV r8, r0 @move character to r8

POP {r0-r4}
BEQ msgendrowloop @exit loop when done

代替此:

PUSH {r0-r4}
BL getchar @branch & link to getchar - reads single character from stdin

CMP r0,#-1 @check if we're at the end of file
BEQ msgendrowloop @if so, exit loop

MOV r8, r0 @move character to r8
POP {r0-r4}

它完美地工作。这里的逻辑令人困惑,因为我的原始代码在逻辑上是合理的。

1 个答案:

答案 0 :(得分:0)

将以上评论形式化为答案:

您所看到的行为的基本原因是stdin函数的第二种形式没有保留堆栈指针(它对堆栈的使用不是“平衡的”)。 PUSH {r0-r4}通过POP {r0-r4}进行平衡,该BEQ将堆栈指针恢复为其在输入该块时所具有的值,但是如果采用POP分支,则POP {r4-r6,pc}被跳过,堆栈操作不再平衡。这意味着当另一段代码从堆栈中弹出数据时,如果期望找到更早放入该堆栈的内容,则会弹出错误的值。很有可能是弹出消息,其中涉及程序计数器作为函数返回,并且弹出了一个无意义的地址,因此出现了段错误。

使用调试器来尝试发现这种错误的根本原因是发展技能的好主意。假设我对段错误的原因是正确的,在这种情况下,基本过程将是

  1. 要查找生成段错误的代码行,该行可能类似于r13
  2. 通过从pc中获取堆栈指针并查看内存中的堆栈,来确定正在弹出的PUSH {r4-r6,lr}中的值是无效的分支地址
  3. 要找到平衡此POP的{​​{1}},并确定首先要推送适当的地址
  4. 要注意,堆栈指针在PUSH的末尾与平衡POP的开始之间已经改变了值(如果所有中间堆栈操作均已正确平衡,则不应这样做)
  5. 单步执行代码并尝试查找堆栈不平衡的根源。

此外,请注意procedure call standard in the ARM ABI。简而言之:

  • r0-r3用于函数自变量和返回值;
  • r0-r3r12是“调用密集型”的,您不能依赖它们在函数调用之间保留它们的值,因此,除非有必要,否则最好不要将它们用于中间存储到;
  • r4-r11lrr14)是“调用保留”的,因此您编写的任何函数都必须保留这些(但不必保留r0-r3或{{1} });
  • 堆栈应在翻译单元边界之间对齐8字节,因此始终推入并弹出偶数个寄存器是个好主意,以免成为怪异的不确定行为的受害者。您对r12的调用当前使用未对齐的堆栈进行操作。