汇编语言:尝试理解一个小函数

12

为了我的工作,我需要反向解析这段代码(ARM9)正在做什么。我是一个 Java 开发者,真的不理解这段与单个函数有关的代码。

当然,我之所以要求助是因为原始代码不再可用。是否有人可以帮助我使用任何高级语言中的小算法了解此代码正在执行的操作?那将是很好的。我已经尝试了很多小时,但没有结果。

sub_FFFF7B38
    PUSH    {LR}
    ADDS    R2, R0, #0
    LDRB    R3, [R2]
    CMP     R3, #0
    BEQ     loc_FFFF7B52
    SUBS    R1, #1
    BCC     loc_FFFF7B52

loc_FFFF7B46:
    ADDS    R0, #1
    LDRB    R3, [R0]
    CMP     R3, #0
    BEQ     loc_FFFF7B52
    SUBS    R1, #1
    BCS     loc_FFFF7B46

loc_FFFF7B52:
    SUBS    R0, R0, R2
    POP     {R1}
5个回答

6
除了最后两行,它可能类似于以下内容。
如果我不是100%正确,请不要打我。

如果
R0p0p,并且
R1n,并且
R2是临时值(已编辑;首先我认为:ip0 [i] 的地址)
R3是临时值

.

sub_FFFF7B38
          PUSH {LR}           ; save return address
          ADDS R2, R0, #0     ; move R0 to R2
          LDRB R3, [R2]       ; load *p0
          CMP R3, #0          ; if *p0==0 
          BEQ loc_FFFF7B52    ; then jump to loc_FFFF7B52 
          SUBS R1, #1         ; decrement n
          BCC loc_FFFF7B52    ; if there was a borrow (i.e. n was 0): jump to loc_FFFF7B52


loc_FFFF7B46:
          ADDS R0, #1         ; increment p
          LDRB R3, [R0]       ; load *p
          CMP R3, #0          ; if *p==0
          BEQ loc_FFFF7B52    ; jump to loc_FFFF7B52
          SUBS R1, #1         ; decrement n
          BCS loc_FFFF7B46    ; if there was no borrow (i.e. n was not 0): jump to loc_FFFF7B46


loc_FFFF7B52:
          SUBS R0, R0, R2     ; calculate p - p0
          POP {R1}            ; ??? I don't understand the purpose of this
                              ; isn't there missing something?

或者用C语言实现:
int f(char *p0, unsigned int n)
{
  char *p;

  if (*p0==0 || n--==0)
    return 0;

  for(p=p0; *++p && n>0; n--)
  {
  }
  return p - p0;
}

@Curd - 我认为你的答案比我想象的更接近,我觉得我无法超越你的回答。+1 - user65628
R2也是一个临时寄存器。 ADDS R2,R0,#0在其被读取之前覆盖了R2R2只保存了R0的原始值,它不是索引,所以应该用p0而不是R2。 此外,CC是“清除进位”条件,而不是设置进位(见后面的CS)。 - CB Bailey
@Charley Bailey: (1) 关于 R2,你是对的。我会相应地编辑我的答案。 (2) 是的,CC 是“清除进位标志”,CS 是“设置进位标志”,但 ARM 在减法/比较中使用进位标志的方式与预期(以及其他架构)相反。因此,当我在评论中谈到“进位”时,它并不反映实际的 ARM 标志位,而是指是否有借位。 感谢您的更正。 - Curd
@Curd:是的,你说得对。我从来没有需要手动使用减法中的进位位,所以我不知道这一点。但我应该知道,因为我之前用过SBC,它需要正确地工作。 - CB Bailey
@Charles Bailey:是的,我也这么认为。但是为什么要先保存LR呢?(函数内没有调用子程序)。最后加上MOV PC,R14就可以了。 - Curd
显示剩余2条评论

4

以下是逐行注释的指示:

sub_FFFF7B38
    PUSH    {LR}          ; save LR (link register) on the stack
    ADDS    R2, R0, #0    ; R2 = R0 + 0 and set flags (could just have been MOV?)
    LDRB    R3, [R2]      ; Load R3 with a single byte from the address at R2
    CMP     R3, #0        ; Compare R3 against 0...
    BEQ     loc_FFFF7B52  ; ...branch to end if equal
    SUBS    R1, #1        ; R1 = R1 - 1 and set flags
    BCC     loc_FFFF7B52  ; branch to end if carry was clear which for subtraction is
                          ; if the result is not positive

loc_FFFF7B46:
    ADDS    R0, #1        ; R0 = R0 + 1 and set flags
    LDRB    R3, [R0]      ; Load R3 with byte from address at R0
    CMP     R3, #0        ; Compare R3 against 0...
    BEQ     loc_FFFF7B52  ; ...branch to end if equal
    SUBS    R1, #1        ; R1 = R1 - 1 and set flags
    BCS     loc_FFFF7B46  ; loop if carry set  which for subtraction is
                          ; if the result is positive

loc_FFFF7B52:
    SUBS    R0, R0, R2    ; R0 = R0 - R2
    POP     {R1}          ; Load what the previously saved value of LR into R1
                          ; Presumably the missing next line is MOV PC, R1 to
                          ; return from the function.

因此,在基本的C语言代码中:

void unknown(const char* r0, int r1)
{
    const char* r2 = r0;
    char r3 = *r2;
    if (r3 == '\0')
        goto end;
    if (--r1 <= 0)
        goto end;

loop:
    r3 = *++r0;
    if (r3 == '\0')
        goto end;
    if (--r1 > 0)
        goto loop;

end:
    return r0 - r2;
}

添加一些控制结构以摆脱goto:

void unknown(const char* r0, int r1)
{
    const char* r2 = r0;
    char r3 = *r2;

    if (r3 != '\0')
    {
        if (--r1 >= 0)
        do
        {
             if (*++r0 == '\0')
                 break;
        } while (--r1 >= 0);
    }

    return r0 - r2;
}

编辑:现在我对进位标志和SUBS的困惑有所消除,这使得下面的内容更加合理。

简化:

void unknown(const char* r0, int r1)
{
    const char* r2 = r0;

    while (*r0 != '\0' && --r1 >= 0)
        r0++;

    return r0 - r2;
}

换言之,这是在指向r0的字符串指针的前r1个字符中查找第一个NUL的索引,如果没有,则返回r1


1
在你的第二个例子中,不是 while (r1-- ==0) 而是 while (r1-- != 0)。因此,你最终的例子实际上就是它真正做的事情,换句话说,它是一个长度有限的 strlen - Andrew McGregor
@Andrew McGregor:我把“if !=,goto end”改成了“while ==”。我相当确定这是正确的转换。我花了一些时间来琢磨它,因为它看起来很不对劲。你确定我完全搞砸了吗? - CB Bailey
ARM的进位标志被反转了;它是一个非进位位,因此在sub之后跟着bcc意味着如果减法没有得到零就分支。 - Andrew McGregor
@Andrew McGregor:这可以解释。因为我确实不记得在ARM2上遇到过这个问题 - 尽管那是一段时间之前的事了。 - CB Bailey
@Andrew McGregor:好的,我找到了一些文档;对于加法,进位标志位(carry set)的设置与我预期的一样,但是对于减法,当减法结果为正数时设置进位标志位,考虑到SBC的工作原理,这是有道理的。 - CB Bailey
啊,是的...加法正常,减法倒置...我漏掉了那个细节。 - Andrew McGregor

2

Filip提供了一些指引,你还需要阅读一下ARM调用约定。(也就是说,在函数进入时哪个寄存器包含函数参数以及它的返回值。)

从快速阅读中我认为这段代码是strnlen或者与之密切相关。


代码与strnlen完全一致,我很惊讶只有你说了这个,而且你的答案没有赞。 - Z.T.

1

这样怎么样:ARM指令集

一些提示/简化汇编

  • Push - 将某物放在“堆栈”/内存中
  • Add - 通常是“add”,表示加号+
  • Pop - 从“堆栈”/内存中检索某物
  • CMP - 是比较的缩写,用于将某物与其他东西进行比较。

X:或:Whatever:表示以下内容是“子程序”。你在Java中使用过“goto”吗?实际上类似于那个。

如果您有以下内容(忽略是否正确的arm-asm,它只是伪代码):

PUSH 1
x:     
    POP %eax

首先,它会将1放入堆栈中,然后将其弹回eax中(eax是扩展ax的缩写,是一个可以放置32位数据的寄存器)

现在,x:是做什么的呢?好吧,假设在那之前有100行汇编代码,那么您可以使用“跳转”指令导航到x:

这是一些汇编语言的简单介绍。

尝试理解上面的代码并检查指令集。


我理解push、add、cmp、jmp等指令,但我仍需理解代码的目的。 - mada

1

我的汇编语言有点生疏,所以请不要扔烂番茄。假设这从sub_FFFF7B38开始:

PUSH {LR} 命令保留链接寄存器,这是一个特殊的寄存器,它在子例程调用期间保存返回地址。

ADDS 设置标志(就像 CMN 一样)。另外,ADDS R2, R0, #0R0 加上 0 并存储在 R2 中。(来自评论区 Charles 的更正)

LDRB R3, [R2]R2 的内容加载到主内存中,而不是引用 R3 的寄存器。 LDRB 只加载单个字节。加载时,字中的三个未使用的字节被清零。基本上,将 R2 从寄存器中取出并进行安全保管(也许)。

CMP R3, #0 对两个操作数执行减法并设置寄存器标志,但不存储结果。这些标志导致......

BEQ loc_FFFF7B521的意思是“如果前面的比较相等,就跳转到loc_FFFF7B521”,或者if(R3 == 0) {goto loc_FFFF7B521;}

所以如果R3不为零,则SUBS R1,#1命令会从R1中减去一并设置一个标志。

BCC loc_FFFF7B52将导致执行跳转到loc_FFFF7B52,如果进位标志被设置。

(省略)

最后,POP {LR}会恢复在此代码执行之前保存在链接寄存器上的先前返回地址。

编辑 - 当我在车里时,Curd几乎拼出了我在尝试写出答案时所想的内容,但时间不够了。


ADDS 设置标志位(就像 CMN 一样)。另外,ADDS R2,R0,#0R0 加 0 并存储在 R2 中,它不会将 R0R2 相加。 - CB Bailey
@Charles - 我在我自己的文档(全部是x86)中找不到ADDS命令,也没有在Fillip提供的网站上看到它。谢谢你的纠正。 :) - user65628
“S”只是一个后缀,“ADDS”不是单独的指令。您可以在任何算术指令后面加上“S”来设置标志位。 - CB Bailey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接