【问题标题】:Is mars MIPS simulator Big or Little Endianmars MIPS 模拟器是 Big Endian 还是 Little Endian
【发布时间】:2017-10-02 20:41:21
【问题描述】:

作为作业,我必须确定火星模拟器是大端还是小端,这起初看起来很简单,但我遇到了一些问题。

首先我尝试使用.byte 0,0,0,1在内存中存储4个字节,在内存中这显示为0x01000000,因此,以相反的顺序,这似乎表明模拟器是小端,但是,当我将4个字节作为整数加载到寄存器中,寄存器中出现的又是0x01000000,据我所知,如果它是小端序,则加载的是0x00000001。

另外,当用.word 1存储4个字节时,存储的是0x00000001,这次没有字节反转。

我想知道模拟器是大端还是小端,以及对此行为的解释

【问题讨论】:

  • 如果在同一个地址或地址加一二三的字交易和字节交易怎么办?
  • "在内存中这显示为 0x01000000" - 只有当您将内存内容视为单词时,该视图才会尊重字节顺序,并将四个字节 0、0、0、 1 到 0x1000000 的值(小端序,第一个字节是 *256^0,第二个是 *256^1,...)。如果您将内存视图检查为字节,您仍然会看到00 00 00 01.byte 指令不会以任何方式重新排列字节,它们完全按照您编写的方式存储。
  • @Ped7g 谢谢,这是有道理的,但是为什么当我用 .word 存储 1 时结果会不同?我相信我以相同的顺序存储完全相同的字节,所以不应该它们也显示为 0x01000000?
  • 通过使用指令.word,您告诉汇编器将下一个文本视为32位大小的整数值,因此它将像“1”一样解析文本,将其转换为整数值1 , 并将其以正确的字节顺序存储到二进制文件中以保持该值,当您使用字指令对该内存进行操作时。所以.word 0x11223344 定义了四个字节44 33 22 11。然后,当您执行 lw $t0,(...) 时,t0 的值将是 0x11223344,正如您在源代码中所写的那样。以“字节交换”的方式在源代码中写入所有值会很疯狂,这就是存在 .word 的原因(当 .byte 可以用于任何事情时)。
  • 在大端目标平台上,汇编器应该将.word 0x11223344 编译为四个字节11 22 33 44。因此,当您使用源代码(文本)中的单词值时,汇编程序(针对正确的平台时)会向您隐藏字节序。如果您坚持定义特定字节,并且想自己处理字节顺序,则汇编器允许您通过使用字节大小指令(如.byte.space)来做到这一点。然后您需要了解目标平台以及如何正确按字节定义字值。 (在您的情况下,.word 1 将编译为 01 00 00 00,而不是 0,0,0,1)

标签: assembly mips endianness mars-simulator


【解决方案1】:

您的问题涉及多个层面,因此我尝试一一解决...

机器:

机器具有可按字节寻址的内存。第一个字节的地址为 0,第二个字节的地址为 1,等等......每当我在这个答案中写下内存内容时,我都会使用这种格式:01 02 0E 0F 10 ...,使用十六进制值并在字节之间使用空格,地址连续从起始地址到结束地址。 IE。如果此内容从地址 0x800000 开始,则内存将是(全部为 hexa):

address | byte value
------- | ----------
800000  | 01
800001  | 02
800002  | 0E
800003  | 0F
800004  | 10
800005  | ...

到目前为止,无论目标MIPS平台是小端还是大端都无所谓,只要涉及字节大小的内存,字节顺序是“正常的”。

如果您将 byte 从地址0x800000 加载到t0(使用lb 指令),t0 将等于值1

如果你将 word 从地址0x800000 加载到t0 (使用lw 指令),字节序最终会发挥作用。

little-endian 机器上,t0 将等于值 0x0F0E0201,字的第一个字节(在内存中)的数量为 2560 (最低功率),第二个是256的数量1,...最后一个是256的数量3

big-endian 机器上,t0 将等于值 0x01020E0F,字的第一个字节(在内存中)的数量为 2563,第二个是256的数量2,...最后一个是256的数量0

(256 is 28,而那个幻数来自“一个字节是8位”,一个位可以包含两个值(0或1),一个字节有8个位,所以一个字节可以包含2个8 不同的值)

在这两种情况下,CPU 都会从内存中读取相同的四个字节(地址为 0x800000 到 0x800003),但字节序定义了它们作为最后 32 位字值出现的顺序。

t0 在 CPU 芯片上由 32 位物理组成,它没有地址。当您想在 CPU 指令中寻址它(即使用存储在 t0 中的值)时,您将其编码为指令为 $8 寄存器($8 具有 $t0 别名,以便在您的汇编程序中使用,所以我使用而是t0 别名)。

字节序不适用于这 32 位寄存器,它们已经是 32 位 b0-b31,一旦加载了值 0x0F0E0201,这 32 位将设置为 0000 1111 0000 1110 ...(我是从顶部 b31 位向下到底部 b0,以理解左移/右移指令并使其作为人类格式化的二进制数工作),没有必要考虑寄存器的字节顺序或位存储在哪个物理顺序芯片,将其视为完整的 32 位值就足够了,并且在算术指令中它可以那样工作。

当将带有lb 的字节值加载到寄存器中时,它会落入 b0-b7 位,其中 b8-b31 包含 b7 的副本(将带符号的 8 位值符号扩展为带符号的 32 位值)。

当将寄存器的值存储到内存中时,字节序仍然适用,即将word0x11223344 存储到内存中会将单个字节设置为44 33 22 11

汇编器(源代码和编译)

为其目标平台配置良好的汇编器将对程序员隐藏字节顺序,以方便使用字值方便

所以当你定义内存值时:

myPreciousValue .word 0x11223344

汇编器将解析文本(您的源代码是文本(!),即一个字符是一个字节值 - 在 ASCII 编码中,如果您在 UTF8 文本编辑器中编写源代码并使用非 ASCII 字符,它们可能会被编码跨多个字节,ASCII 可打印字符在 ASCII 和 UTF8 中具有相同的编码,并且只占用一个字节)“0x11223344”(10 个字节30 78 31 31 32 32 33 33 34 34),从中计算出 32 位字值0x11223344,然后它将对其应用目标平台字节序以生成四个字节的机器代码:

44 33 22 11           # little-endian target

或:

11 22 33 44           # big-endian target

当您随后在代码中使用 lw 指令将 myPreciousValue 从内存加载到寄存器时,寄存器将包含预期的字值 0x11223344(只要您没有混淆您的汇编程序配置并且使用了错误的字节序,在 MARS/SPIM 中不会发生,因为它只支持所有东西(VM、汇编器、调试器)中的小端序配置。

因此程序员不必每次将 32 位值写入源代码中的某处时都考虑字节顺序,汇编器会将其解析并处理为字节值的目标变体。

如果程序员想在内存中定义四个字节01 02 03 04,她可以“聪明”,将.word 0x04030201用于little-endian目标平台,但这会混淆原意,所以我建议使用.byte 1, 2, 3, 4在这种情况下,程序员的意图是定义字节,而不是单词。

当您使用 .byte 指令声明字节值时,它们会按照您编写它们的顺序进行编译,不会应用字节序。

调试器

最后是调试器的内存/寄存器视图...此工具将再次尝试以直观/方便的方式工作,因此当您检查内存视图并将其配置为字节时,内存将显示为:

0x800000: 31 32 33 34 41 42 43 44 | 1234ABCD

当您将其切换到“word”视图时,它将使用配置的字节序以目标平台顺序连接字节,即在 MARS/SPIM 作为 little-endian 平台中,它将显示在同一内存上:

0x800000: 34333231 44434241

(如果还包括 ASCII 视图,是否也包含“文字”?如果是,那么它将看起来像 4321 DCBA。我目前没有安装 MARS/SPIM 来检查它们调试器中的内存视图实际上看起来像,对不起)

因此,作为程序员,您可以直接从显示器读取“word”值,而无需将字节改组为“正确”顺序,您已经看到“word”值将是什么(从这四个字节的内存内容中)。

寄存器视图通常默认显示十六进制字值,即从地址 0x800000 加载字到t0 后,寄存器$8 将包含值0x34333231875770417 十进制)。

如果您对用于该加载的内存中第一个字节的值感兴趣,此时您必须应用您对该目标平台的字节序的了解,并查看前两位数字“34”(大endian),或者寄存器视图中的最后两个“31”(小端)(或者更确切地说在字节视图模式下使用内存视图以避免任何错误)。

代码中的运行时检测。

所以有了上面的所有信息,运行时检测代码应该很容易理解(不幸的是我目前没有 MARS/SPIM,所以我没有验证它是否有效,请告诉我):

.data

checkEndianness: .word 0    # temporary memory for function
    # can be avoided by using stack memory instead (in function)

.text

main:
    jal  IsLittleEndian
    # ... do something with v0 value ...
    ... exit (TODO by reader)

# --- functions ---

# returns (in v0) 0 for big-endian machine, and 1 for little-endian
IsLittleEndian:
    # set value of register to 1
    li $v0,1
    # store the word value 1 into memory (4 bytes written)
    sw $v0,(checkEndianness)
      # memory contains "01 00 00 00" on little-endian machine
      #              or "00 00 00 01" on big-endian machine
    # load only the first byte back
    lb $v0,(checkEndianness)
    jr $ra

它有什么用?只要您为单一目标平台编写软件,并且您是通过目标 CPU 存储/加载单词,您就不需要关心字节序。

但是如果你有一个多平台的软件,并且它确实保存了二进制文件......为了使文件在两个大/小端平台上以相同的方式工作,文件结构的规范还必须指定字节序文件数据。然后根据该规范,一种类型的目标平台可能会将其读取为“本机”字值,另一种则必须对字值中的字节进行洗牌以读取正确的字值(此外,规范还应指定多少字节“词”是:) )。那么这样的运行时测试可能会很方便,如果您将混洗器包含在保存/加载例程中,使用字节序检测例程来决定它是否必须对字字节进行混洗。这将使目标平台字节序对其余代码“透明”,这将简单地将其本机“字”值发送到保存/加载例程,并且您的保存/加载可能在每个平台上使用相同的源(至少只要您使用一些多平台可移植编程语​​言,如 C,当然 MIPS 的程序集根本无法在不同的 CPU 上工作,需要从头开始重写。

此外,网络通信通常使用自定义二进制协议完成(通常包装在网络层最常见的 TCP/IP 数据包中,甚至加密,但您的应用程序会在某一时刻从中提取原始字节内容) ,然后发送/接收数据的字节顺序很重要,“其他”平台必须重新排列字节以读取正确的值。

其他平台(非 MIPS)

几乎可以从上面应用所有内容,只需检查另一个平台上的 byteword 是什么(我认为 byte 在过去 35 年多的时间里一直是 8 位,但 word可能会有所不同,例如在 x86 平台上word 仅是 16 位)。仍然 little-endian 机器将以“反转”顺序读取“word”字节,第一个字节用作最小 2560 次方的数量,最后一个字节用作最高 256 次方的数量(2561 在 x86 平台上,由于那里只有两个字节组成 word,所以 MIPS 的“word”在 x86 世界中被称为“double word”或“dword”)。

【讨论】:

    【解决方案2】:

    来自网站:http://courses.missouristate.edu/KenVollmar/mars/Help/MarsHelpDebugging.html

    Memory addresses and values, and register values, can be viewed in either
    decimal or hexadecimal format. All data are stored in little-endian
    byte order (each word consists of byte 3 followed by byte 2 then 1 then 0).
    Note that each word can hold 4 characters of a string and those 4 
    characters will appear in the reverse order from that of the string literal
    

    你可以看到它是小端的

    【讨论】:

    • 在 little-endian 世界中,您从 LSB 对字节进行编号,因此它们以 0、1、2、3 的顺序存储。我猜火星文档作者不同意如何对字节进行编号,但大概他们将其描述为 little-endian 是正确的。 (gcc 称之为 MIPS(el) 模式,因为 MIPS 也支持 big-endian。)
    • 但是,如果是小端,那么为什么字节加载到寄存器就好像它是大端?
    • @JG55:他们不是。 .byte 0,0,0,1 加载到带有lw 的寄存器中应该会给您0x01000000,这与您的观察结果相符。
    猜你喜欢
    • 2017-06-14
    • 2011-10-06
    • 2020-12-01
    • 2019-06-30
    • 2018-05-15
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多