【问题标题】:How to develop an intuition to code in LC-3 Assembly?如何培养对 LC-3 汇编代码的直觉?
【发布时间】:2021-06-18 10:46:54
【问题描述】:

我了解了 LC-3 的工作原理,但我终其一生都无法弄清楚如何在 LC-3 汇编中编写代码。我的目标是能够编写简单的程序,例如生成斐波那契数或对数组进行排序。

有人可以指点我学习这个的资源吗?我精通 Python 和 Java,所以这些问题背后的基本逻辑对我来说很清楚。

【问题讨论】:

  • 从组装的角度思考就像把积木放在一起来建造更大的东西。像乐高或其他东西:简单的积木。对于任何给定的伪代码步骤,将其分解为机器可以执行的操作,在一系列步骤中使用有限的机器状态(寄存器)来计算一些有用的东西。对于数组,考虑指针(如 C)是有用的。
  • 我从未使用过 LC-3 组件,但也许 this instruction list 可以帮助您。有时当我在编写一个可能需要使用循环之类的可能很长的过程时,我喜欢用更高级的语言编写它,然后慢慢地使函数更像“汇编”(扩展像a+b*c/whatever这样的大型操作,用 do while 循环替换 for/while 循环,甚至 goto 如果我在循环方面遇到很多麻烦)。也许不是最好的方法,但正如 Peter Cordes 所说,简单的构建块。
  • @mediocrevegetable1:在高级语言中获得正确的逻辑是一种非常好的方法,被广泛推荐。是的,为了有帮助,你必须根据 asm 实际可以做的事情来编写它,比如不是 Python 列表理解。 (C 对此有好处;它只能做无需循环即可轻松编译为 asm 的事情)。是的,无论您是尝试让编译器生成您想要的 asm,还是计划手写 asm,以类似 asm 的方式编写高级代码都很好。
  • 这些都不是 LC-3 特有的;它是一个寄存器机器,不需要你跳过疯狂的圈子(比如像一些玩具 ISA 那样用于数组索引的自修改代码,例如 LMC,或者像 MARIE 这样的累加器机器,使用多个数字是一件很痛苦的事情,不断存储/重新加载)。 LC-3 非常简单,缺少右移等关键功能,但如果您的算法不需要它,它与 MIPS 或 x86 等主流 ISA 并没有太大区别。
  • @PeterCordes 是的,我通常为此使用 C,也许 Java 将是 Java 和 Python 中 OP 的最佳选择。我自己也检查了指令列表(found a more comprehensive list and a guide),它确实非常简单和准系统(比如只有 ADD,AND 而不是 ALU)。我从未深入研究过微控制器组装,但这似乎是学习一些基础知识的好方法。

标签: assembly lc3 lc3-trap


【解决方案1】:

学习汇编语言有几个方面,它是处理器机器代码的人类可读版本。

基本上其他语言处于逻辑级别,而机器代码则非常处于物理级别

  • 首先,这尤其体现在存储概念上的差异:

    • 变量是逻辑变量,CPU 寄存器和内存是物理变量
    • 逻辑变量是动态的,CPU 寄存器和内存是固定的、永久的
    • 具有类型的变量与具有位的物理存储

    因此,当我们编写汇编语言时,我们会翻译我们的伪代码:具有大量生命周期有限的类型化变量的逻辑代码,部分是通过将逻辑变量映射到固定的物理资源。变量通常比 CPU 寄存器多,尤其是当某些寄存器具有专用用途时,例如堆栈或返回地址。

  • 另一方面,今天的其他语言通常采用结构化编程,而在汇编语言/机器代码中我们有 if-goto-label。

    所有结构化语句在 if-goto-label 中都有翻译。每个翻译都是将结构化形式的模式转换为 if-goto-label 形式的模式。正确地遵循这些模式,您将重现伪代码的控制流——在这里很容易走捷径并犯令人困惑的错误,所以我在这里鼓励一种有条不紊的方法。

  • 其他语言具有丰富的表达式:具有许多优先级的运算符,并且可以使用()'s 尽可能复杂。机器代码的指令(通常)最多需要 3 个操作数。

  • 函数调用、堆栈帧、参数传递、返回值是一个相当深入的主题,函数序言和结语。

    • 参数需要由调用者放置到已知位置,并由被调用者从这些位置找到
    • 固定的物理寄存器需要在调用者和被调用者之间共享,因此有一个协议可以进行共享。寄存器要么保留调用,要么破坏调用 - 每个组都适用于不同的场景,并且有自己的规则/要求才能正常工作。
    • 在汇编语言中可以有一个带有堆栈指针的显式调用堆栈,这是我们在 C 代码中看不到的。
    • 可以有一个明确的返回地址,它应该被认为是一个参数,被调用者用来返回给正确的调用者(因为它可以是动态不同的)。
    • 返回值由被调用者放置到已知位置,并在返回时由调用者找到。
    • 保存调用保留寄存器,局部变量存储,例如对于数组,参数(包括返回地址)和局部变量可以在函数调用中生效——所有这些东西都需要内存存储,通常以在堆栈上分配一些空间的形式(尽管有时在 LC-3 上这些已经完成作为全局变量,这意味着不支持递归)。如果堆栈空间用于其中任何一个,则该空间称为堆栈帧。
      • 堆栈空间在函数序言中分配并在函数尾声中释放——这些是函数体之前和之后的函数代码部分(它们在每次函数调用时只执行一次,即使整个函数体是一个循环)。
    • 通过查找您正在使用的“调​​用约定”查看更多信息,该约定将描述寄存器共享组、专用寄存器(例如堆栈指针)以及参数和返回值位置。
    • 分析逻辑变量是否“在调用过程中有效”有助于选择适当的 CPU 寄存器,并告诉我们是否需要在序言中保存该寄存器并在结尾中恢复它。

有关详细信息,请参阅以下一些资源:

【讨论】:

  • LC-3 是一个 2 操作数 ISA,如 add reg, reg。 (它有 8 个 16 位寄存器,是一个负载存储机器,有几种寻址模式,包括 PC 相对和寄存器间接)
  • @PeterCordes,当然。 ISA 不同。我认为 OP 对一般感兴趣,而不仅仅是对 LC-3。
  • 对,因此如果您以后仍需要旧值,将 3 地址代码转换为 LC-3 asm 可能需要一些 mov 指令来复制寄存器。
  • @PeterCordes,是的,好点。 TAC 只是其中的一步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-21
  • 2011-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多