学习 RISC-V 汇编并需要帮助转换 C 循环

Learning RISC-V assembly and need help converting a C loop

提问人:test1 提问时间:11/13/2023 最后编辑:Peter Cordestest1 更新时间:11/13/2023 访问量:86

问:

我正在学习如何将 RISC-V 汇编代码转换为 C,但我不明白这种转换。我有几个问题:

  1. 为什么被初始化为 而不是 ?t160
  2. 我们使用 bne 来比较 t1 和 t2,但我们被教导使用相反的,这意味着 C 代码将是 .这种相反的技术似乎被用于 。为什么?while (t1 == t0)if (t1 ==0)
  3. 最后,为什么用 sub 代替 sub?addi t0, t0, -1

任何见解都非常感谢。我的课堂环境节奏很快,对提问不是很友好,所以我在课堂上提问很紧张。

main:
    # Tests simple looping behavior
    li t0, 60
    li t1, 0
loop:
    addi t1, t1, 5
    addi t0, t0, -1
    bne t1, t0, loop
    bne t1, zero, success
failure:
    li a0, 0
    li a7, 93 
    ecall
    
success:
    li a0, 42 
    li a7, 93
    ecall

这是我得到的答案:

int main(){

    int t0 = 60;
    int t1 = 6;
    
    while(t1 != t0){
        t1 = t1 + 5;
        t0 = t0 - 1;
    }

    if(t1 == 0){
        int a0 = 0;
        return 0;
    }else{
        int a0 = 42;    
        return 0;    
    }
}

我们被教导在将 C 转换为 RISC-V 时使用 bne/beq 的反义词,因此令人困惑的是,为什么这个 RISC-V 汇编的“正确”C 转换会包括 .while (t1 != t0)

将 t1 初始化为 6 对我来说也没有任何意义。它看起来清楚地加载到 0 与“li t1, 0”。

C 装配 逆向工程 RISCV

评论

1赞 Peter Cordes 11/13/2023
C 与 RISC-V 汇编不太匹配。你是对的,asm 显然没有.如果此材料被打印出来并扫描回去或其他东西,可能是错别字或 OCR 错误。这些是 和 ,而不是语句。不过,其余的看起来都是正确的。自己试一试,单步执行 asm 和单步执行 C 程序,分别观察寄存器或变量的变化。应该清楚的是,执行会一直保持在循环中,直到它们相等,即当它们不相等时。t1 = 0t1 = 6ecallexit(0)exit(42)return
0赞 Erik Eidt 11/13/2023
汇编代码正在执行,而 C 代码正在执行。区别在于,do/while 将在检查退出条件之前执行一次迭代,而 while/do 代码将在第一次迭代之前检查退出条件。所以,这是另一个例子,说明这两者(C 与汇编)如何并不完全相同。do ... while ()while () { /* do */ ...}

答:

0赞 Peter Cordes 11/13/2023 #1

C 与 RISC-V 汇编不太匹配。
你是对的,asm 显然没有.如果此材料被打印出来并扫描回去或其他东西,可能是错别字或 OCR 错误。或者,也许有人只是改变了对循环起点的想法,但忘记更新其中一个版本。 并且两者都会导致循环终止而不环绕(或者实际上是 C 符号溢出未定义行为,因为这不是。
t1 = 0t1 = 606unsigned

此外,这些是 和 ,而不是语句。asm 不使用其返回地址。ecall_exit(0)_exit(42)return


不过,其余的看起来都是正确的。自己试一试,单步执行 asm 和单步执行 C 程序,分别观察寄存器或变量的变化。应该清楚的是,执行会一直保持在循环中,直到它们相等,即当它们不相等时。

对于像这样的惯用 asm 循环,最直接的 C 表示是将条件放在底部,就像 asm 一样。(将其显示为 or 循环取决于使条件为 true 的初始值设定项,因此循环体至少运行一次迭代。请参阅为什么循环总是编译为“do...而“风格(尾巴跳跃)?do{}while(t1 != t0);forwhile

(3) 尝试更改 asm 以立即使用 .大多数汇编程序会拒绝它,因为 RISC-V 没有 ,除非可能作为伪指令。它没有 FLAGS / 条件代码寄存器,因此减法完全等同于添加负数,并且 RISC-V 总是符号扩展直接操作数。它从操作码中获得的唯一好处是能够立即更改值,而不是 的范围。这显然不值得再使用一个操作码。subi1subisubi+4096 .. -4095addi-4096 .. +4095

MIPS在这一点上与RISC-V非常相似(没有FLAGS并且没有硬件);请参阅addi和subi之间的“关系”是什么?以及ISA与软件汇编程序伪指令设计选择。subi

评论

0赞 test1 11/13/2023
我非常感谢这一点;你已经清理了很多。你关于 ecalls 的返回与退出的观点是有道理的,但在讲座幻灯片上,他说“ecall 只是 C 中的返回<代码>调用”。我不知道他为什么会这么建议。我认为给定的问题与解决方案说明了很多。
0赞 Peter Cordes 11/13/2023
@test1:C 确实保证返回的值与该值等效,并且 C 没有“析构函数”或任何可以使返回时运行更多代码的东西,而不是调用另一个函数或系统调用。因此,不同的代码具有相同的最终效果。mainexit()
0赞 Peter Cordes 11/13/2023
除了 libc 函数会调用注册的任何函数,并在退出之前刷新 stdio 缓冲区。但是原始退出系统调用就像 C 一样,它不会这样做。请参阅在装配中使用 printf 会导致管道连接时输出为空,但在终端 / x86 装配上有效 - printf 不会在没有“\n”的情况下打印。也 re. vs. 在 Linux 上 vs.raw asm exit,请参见 exit() 的 Syscall 实现exitatexit_exitexit()_exitecall