这个由gcc生成的strlen()mips循环如何不是一一对应的?

时间:2019-03-11 00:16:17

标签: gcc mips strlen off-by-one

这是一个非常基本的strlen()实现的源代码。

#include <stddef.h>
#include <stdint.h>

extern uintptr_t lx_syscall3(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t nr);

static void lx_sys_exit(uintptr_t code)
{
  lx_syscall3(code, 0, 0, 4001);
  while (1);
}

static size_t lx_strlen(char const* s)
{
  size_t len = 0;

  while (*(s++)) {
    len++;
  }

  return len;
}

int main() {
  lx_sys_exit(lx_strlen("HELO"));
  while (1);
}

与一个与此问题无关的syscall.s文件一起编译,为lx_strlen生成的GCC代码被内联到main中(在-Os处):

004004fc <main>:
  4004fc: 3c1c000b  lui gp,0xb
  400500: 279c8154  addiu gp,gp,-32428
  400504: 0399e021  addu gp,gp,t9
  400508: 8f828034  lw v0,-32716(gp)
  40050c: 27bdffe0  addiu sp,sp,-32
  400510: 24424a64  addiu v0,v0,19044
  400514: afbc0010  sw gp,16(sp)
  400518: afbf001c  sw ra,28(sp)
  40051c: 00402825  move a1,v0
  400520: 00452023  subu a0,v0,a1

  # strlen loop block follows
  400524: 24420001  addiu v0,v0,1
  400528: 8043ffff  lb v1,-1(v0)
  40052c: 5460fffd  bnezl v1,400524 <main+0x28>
  400530: 00452023  subu a0,v0,a1

  400534: 8f998118  lw t9,-32488(gp)
  400538: 24070fa1  li a3,4001
  40053c: 00003025  move a2,zero
  400540: 04110093  bal 400790 <lx_syscall3>
  400544: 00002825  move a1,zero
  400548: 1000ffff  b 400548 <main+0x4c>
  40054c: 00000000  nop

使用qemu-mipsel运行时,代码正确输出退出状态4。看来一切正常,问题在于我只是不了解它如何工作。注意-1(v0)处的偏移量400528。因此,循环总是从v0中存储的地址中检查前一个字节。因此,到零时,减去原始地址应该得到5,而不是4。知道它是如何工作的吗?

1 个答案:

答案 0 :(得分:1)

该代码使用bnezl指令,该指令对延迟槽指令有特殊处理:仅当采用分支时才执行。因此,您的代码将始终使用上一次迭代中的$a0,因为subu a0,v0,a1处的400530不会为退出循环的最后一个执行。请注意,对于长度为零的字符串,在400520 $a0处为零。