mars MIPS模拟器是Big Endian还是Little Endian
Is mars MIPS simulator Big or Little Endian
作为作业,我必须确定火星模拟器是大端还是小端,乍一看这似乎很简单,但我遇到了一些问题。
首先我尝试用.byte 0, 0, 0, 1在内存中存储4个字节,在内存中这显示为0x01000000,因此,顺序相反,这似乎表明模拟器是小端,但是,当我将 4 个字节作为整数加载到寄存器时,寄存器中出现的又是 0x01000000,据我所知,如果它是小端字节序,将加载的是 0x00000001.
另外,用.word 1存储4个字节时,存储的是0x00000001,这次没有字节反转。
我想知道模拟器是大端还是小端,以及对此行为的解释
这是来自网站: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
如你所见,它是小端
你的问题涉及到很多层次,我尽量一一解答...
机器:
机器有可按字节寻址的内存。第一个字节的地址为 0,第二个字节的地址为 1,等等......每当我在这个答案中写内存内容时,我都会使用这种格式:01 02 0E 0F 10 ...
,使用十六进制值并在字节之间使用空格,地址从起始地址到结束地址连续不断。 IE。如果此内容从地址 0x800000 开始,则内存将为(所有六进制):
address | byte value
------- | ----------
800000 | 01
800001 | 02
800002 | 0E
800003 | 0F
800004 | 10
800005 | ...
至此无所谓,目标MIPS平台是little还是big endian,只要涉及到字节大小的内存,字节顺序就是"normal".
如果您要从地址 0x800000
加载 byte 到 t0
(使用 lb
指令),t0
将是等于值 1
.
如果你要从地址 0x800000
加载 word 到 t0
(使用 lw
指令),字节序最终会发挥作用.
在 little-endian 机器上 t0
将等于值 0x0F0E0201
,字的第一个字节(在内存中)是 256 0(最低次方),第二个是2561的数量,...最后一个是2563的数量.
在 big-endian 机器上 t0
将等于值 0x01020E0F
,字的第一个字节(在内存中)是 256 3,第二个是金额2562,...最后一个是金额2560 .
(256 is 28,而那个幻数来自"one byte is 8 bits",一个位可以包含两个值(0或1),一个字节有8位,所以一个byte可以包含28个不同的值)
在这两种情况下,CPU 将从内存中读取相同的四个字节(地址为 0x800000 到 0x800003),但是字节序定义了它们将以何种顺序出现在字值的最后 32 位中。
t0
在CPU芯片上由32位物理组成,没有地址。当你想在 CPU 指令中寻址它时(即使用存储在 t0
中的值),你将它编码到指令中作为 </code> 寄存器(<code>
有 $t0
别名是为了在您的汇编程序中方便,所以我使用 t0
别名。
字节顺序不适用于寄存器的那32位,它们已经是32位b0-b31,一旦加载值0x0F0E0201
,这32位将设置为0000 1111 0000 1110 ...
(我将它从顶部 b31 位向下写到底部 b0,以理解移位 left/right 指令并使其作为人类格式化的二进制数工作),没有必要考虑寄存器的字节序或其中这些位存储在芯片上的物理顺序,将其视为完整的 32 位值就足够了,并且在算术指令中它会这样工作。
当用 lb
将字节值加载到寄存器时,它进入 b0-b7 位,b8-b31 包含 b7 的副本(将带符号的 8 位值符号扩展为带符号的 32 位值)。
当将寄存器的值存储到内存中时,字节顺序再次适用,即将 word
值 0x11223344
存储到内存中会将各个字节设置为 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
,她可以"smart"并为小端目标平台使用.word 0x04030201
,但这混淆了初衷,所以我建议在这种情况下使用 .byte 1, 2, 3, 4
,因为程序员的意图是定义字节,而不是字。
当您使用 .byte
指令声明字节值时,它们会按照您编写它们的顺序进行编译,不会应用字节顺序。
调试器
最后 memory/register 调试器视图...此工具将再次努力以 intuitive/convenient 方式工作,因此当您检查内存视图时, 并将其配置为字节,内存将显示为:
0x800000: 31 32 33 34 41 42 43 44 | 1234ABCD
当你切换到"word"视图时,它将使用配置的字节顺序按照目标平台顺序连接字节,即在MARS/SPIM作为小端平台它将显示在相同的平台上内存:
0x800000: 34333231 44434241
(如果ASCII视图也包括在内,是不是也是"worded"?如果是,那么它看起来就是4321 DCBA
。我暂时没有MARS/SPIM 安装以检查它们在调试器中的内存视图实际是什么样子,抱歉)
因此,作为程序员,您可以直接从显示器读取 "word" 值,而无需将字节改组为 "correct" 顺序,您已经看到 "word" 值是什么(从那些四个字节的内存内容)。
寄存器视图通常默认显示十六进制字值,即从地址 0x800000 加载字到 t0
后,寄存器 </code> 将包含值 <code>0x34333231
(875770417
十进制)。
如果您对用于该加载的内存中第一个字节的值是什么感兴趣,此时您必须应用您对该目标平台的字节顺序的了解,并查看前两位数字“34” (大端),或寄存器视图中的最后两个“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 是 storing/loading 个字,您就不需要关心字节顺序。
但是如果你的软件是多平台的,它确实保存了二进制文件...为了使文件在两个 big/little endian 平台上以相同的方式工作,文件结构的规范必须还指定文件数据的字节顺序。然后根据该规范,一种类型的目标平台可能会将其读取为 "native" 字值,另一种目标平台将不得不打乱字值中的字节以读取正确的字值(加上规范还应指定多少bytes "word" 是 :))。那么这样的运行时测试可能会很方便,如果你将洗牌器包含在 save/load 例程中,使用字节顺序检测例程来决定它是否必须打乱单词字节。这将使剩余代码的目标平台字节顺序 "transparent" 简单地发送到 save/load 例程它的本机 "word" 值,并且您的 save/load 可以使用相同的源每个平台(至少只要你使用像 C 这样的多平台可移植编程语言,当然 MIPS 的汇编根本不会在不同的 CPUs 上工作,并且需要从头开始重写)。
此外,网络通信通常使用自定义二进制协议(通常包装在网络层最常见的 TCP/IP 数据包中,甚至加密,但您的应用程序将从中提取原始字节内容在某一时刻),然后 sent/received 数据的字节顺序很重要,然后 "other" 平台必须重新排列字节以读取正确的值。
其他平台(非 MIPS)
几乎可以应用上面的所有内容,只需检查其他平台上的 byte
和 word
是什么(我认为 byte
最后一个是 8 位35 年以上,但 word
可能不同,例如在 x86 平台上 word
仅是 16 位)。仍然是小端机器将以 "reversed" 顺序读取 "word" 字节,第一个字节用作最小的 2560 次方的数量,最后一个字节用作的数量最高的 256 次幂(在 x86 平台上为 2561,因为那里只有两个字节组成字,MIPS "word" 在中称为 "double word" 或 "dword" x86 世界)。
作为作业,我必须确定火星模拟器是大端还是小端,乍一看这似乎很简单,但我遇到了一些问题。
首先我尝试用.byte 0, 0, 0, 1在内存中存储4个字节,在内存中这显示为0x01000000,因此,顺序相反,这似乎表明模拟器是小端,但是,当我将 4 个字节作为整数加载到寄存器时,寄存器中出现的又是 0x01000000,据我所知,如果它是小端字节序,将加载的是 0x00000001.
另外,用.word 1存储4个字节时,存储的是0x00000001,这次没有字节反转。
我想知道模拟器是大端还是小端,以及对此行为的解释
这是来自网站: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
如你所见,它是小端
你的问题涉及到很多层次,我尽量一一解答...
机器:
机器有可按字节寻址的内存。第一个字节的地址为 0,第二个字节的地址为 1,等等......每当我在这个答案中写内存内容时,我都会使用这种格式:01 02 0E 0F 10 ...
,使用十六进制值并在字节之间使用空格,地址从起始地址到结束地址连续不断。 IE。如果此内容从地址 0x800000 开始,则内存将为(所有六进制):
address | byte value
------- | ----------
800000 | 01
800001 | 02
800002 | 0E
800003 | 0F
800004 | 10
800005 | ...
至此无所谓,目标MIPS平台是little还是big endian,只要涉及到字节大小的内存,字节顺序就是"normal".
如果您要从地址 0x800000
加载 byte 到 t0
(使用 lb
指令),t0
将是等于值 1
.
如果你要从地址 0x800000
加载 word 到 t0
(使用 lw
指令),字节序最终会发挥作用.
在 little-endian 机器上 t0
将等于值 0x0F0E0201
,字的第一个字节(在内存中)是 256 0(最低次方),第二个是2561的数量,...最后一个是2563的数量.
在 big-endian 机器上 t0
将等于值 0x01020E0F
,字的第一个字节(在内存中)是 256 3,第二个是金额2562,...最后一个是金额2560 .
(256 is 28,而那个幻数来自"one byte is 8 bits",一个位可以包含两个值(0或1),一个字节有8位,所以一个byte可以包含28个不同的值)
在这两种情况下,CPU 将从内存中读取相同的四个字节(地址为 0x800000 到 0x800003),但是字节序定义了它们将以何种顺序出现在字值的最后 32 位中。
t0
在CPU芯片上由32位物理组成,没有地址。当你想在 CPU 指令中寻址它时(即使用存储在 t0
中的值),你将它编码到指令中作为 </code> 寄存器(<code>
有 $t0
别名是为了在您的汇编程序中方便,所以我使用 t0
别名。
字节顺序不适用于寄存器的那32位,它们已经是32位b0-b31,一旦加载值0x0F0E0201
,这32位将设置为0000 1111 0000 1110 ...
(我将它从顶部 b31 位向下写到底部 b0,以理解移位 left/right 指令并使其作为人类格式化的二进制数工作),没有必要考虑寄存器的字节序或其中这些位存储在芯片上的物理顺序,将其视为完整的 32 位值就足够了,并且在算术指令中它会这样工作。
当用 lb
将字节值加载到寄存器时,它进入 b0-b7 位,b8-b31 包含 b7 的副本(将带符号的 8 位值符号扩展为带符号的 32 位值)。
当将寄存器的值存储到内存中时,字节顺序再次适用,即将 word
值 0x11223344
存储到内存中会将各个字节设置为 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
,她可以"smart"并为小端目标平台使用.word 0x04030201
,但这混淆了初衷,所以我建议在这种情况下使用 .byte 1, 2, 3, 4
,因为程序员的意图是定义字节,而不是字。
当您使用 .byte
指令声明字节值时,它们会按照您编写它们的顺序进行编译,不会应用字节顺序。
调试器
最后 memory/register 调试器视图...此工具将再次努力以 intuitive/convenient 方式工作,因此当您检查内存视图时, 并将其配置为字节,内存将显示为:
0x800000: 31 32 33 34 41 42 43 44 | 1234ABCD
当你切换到"word"视图时,它将使用配置的字节顺序按照目标平台顺序连接字节,即在MARS/SPIM作为小端平台它将显示在相同的平台上内存:
0x800000: 34333231 44434241
(如果ASCII视图也包括在内,是不是也是"worded"?如果是,那么它看起来就是4321 DCBA
。我暂时没有MARS/SPIM 安装以检查它们在调试器中的内存视图实际是什么样子,抱歉)
因此,作为程序员,您可以直接从显示器读取 "word" 值,而无需将字节改组为 "correct" 顺序,您已经看到 "word" 值是什么(从那些四个字节的内存内容)。
寄存器视图通常默认显示十六进制字值,即从地址 0x800000 加载字到 t0
后,寄存器 </code> 将包含值 <code>0x34333231
(875770417
十进制)。
如果您对用于该加载的内存中第一个字节的值是什么感兴趣,此时您必须应用您对该目标平台的字节顺序的了解,并查看前两位数字“34” (大端),或寄存器视图中的最后两个“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 是 storing/loading 个字,您就不需要关心字节顺序。
但是如果你的软件是多平台的,它确实保存了二进制文件...为了使文件在两个 big/little endian 平台上以相同的方式工作,文件结构的规范必须还指定文件数据的字节顺序。然后根据该规范,一种类型的目标平台可能会将其读取为 "native" 字值,另一种目标平台将不得不打乱字值中的字节以读取正确的字值(加上规范还应指定多少bytes "word" 是 :))。那么这样的运行时测试可能会很方便,如果你将洗牌器包含在 save/load 例程中,使用字节顺序检测例程来决定它是否必须打乱单词字节。这将使剩余代码的目标平台字节顺序 "transparent" 简单地发送到 save/load 例程它的本机 "word" 值,并且您的 save/load 可以使用相同的源每个平台(至少只要你使用像 C 这样的多平台可移植编程语言,当然 MIPS 的汇编根本不会在不同的 CPUs 上工作,并且需要从头开始重写)。
此外,网络通信通常使用自定义二进制协议(通常包装在网络层最常见的 TCP/IP 数据包中,甚至加密,但您的应用程序将从中提取原始字节内容在某一时刻),然后 sent/received 数据的字节顺序很重要,然后 "other" 平台必须重新排列字节以读取正确的值。
其他平台(非 MIPS)
几乎可以应用上面的所有内容,只需检查其他平台上的 byte
和 word
是什么(我认为 byte
最后一个是 8 位35 年以上,但 word
可能不同,例如在 x86 平台上 word
仅是 16 位)。仍然是小端机器将以 "reversed" 顺序读取 "word" 字节,第一个字节用作最小的 2560 次方的数量,最后一个字节用作的数量最高的 256 次幂(在 x86 平台上为 2561,因为那里只有两个字节组成字,MIPS "word" 在中称为 "double word" 或 "dword" x86 世界)。