我正在学习汇编 MIMPS(与我的大学略有不同的版本)。
当我将代码转换为二进制时,计算机如何知道二进制文件的哪部分是代码,哪部分是数据?举个例子来说明我想问的问题,在 c 语言中,我研究过单词以“\0”结尾。这是一个惯例,告诉我们一个单词何时结束,另一个单词何时开始。为了将代码和数据加载到内存的不同部分,我假设计算机必须以某种方式知道二进制文件中数据在哪里结束,代码在哪里开始。
确实,汇编语言中存在诸如“.data”或“.text”之类的指令,但是,据我所知,当我汇编时,这些指令会被删除(它们不在二进制文件中),理论上它们仅供编译器使用。所以它没有回答我的问题,这些指令属于汇编语言,而不是二进制文件。
Tommaso Bianchi 的回答非常详细,但可能有点太技术化了,超出了你的预期。所以,让我用更简单的方式解释一下。
汇编程序和二进制结构
汇编代码时,汇编程序会将其转换为二进制文件。但这个二进制文件不仅仅是一堆随机的字节,它具有结构化的格式。部分
.text
(指令)和.data
部分(数据,更准确地说是静态变量)位于二进制文件的不同部分。实际上,一个名为链接器的程序会进行一些额外的处理,但我将跳过它,因为它在这里并不重要。最后,您会得到一个可执行文件,就像 Windows 上的一样.exe
。计算机如何知道?当您的操作系统运行可执行文件时,名为加载器的
系统组件会读取二进制格式。由于二进制文件包含节信息,加载器可以正确地将指令()与数据(、静态变量)分开,并将它们放在正确的内存位置。
.text
.data
这有道理吗?如果你想深入了解,我推荐John R. Levine 的《Linkers & Loaders 》一书。
希望这有帮助!
PS 回答下面提问者的评论。
这是软件科学中非常基础的课题。通常有两种常见方法:
将长度存储在文件头中
一种方法是将每个部分的大小存储在文件开头(文件头)。例如,如果
.text
部分为1000 字节,.data
部分为200 字节,则二进制文件可以在开头存储此信息。然后,在读取文件时,系统就会确切知道每个部分的起始和结束位置。在末尾使用特殊标记另一种方法是在每个节的末尾
放置一个特殊的字节序列,该序列永远不会出现在数据本身中。例如,在 C 编程语言中,像 这样的字符串在末尾存储了一个额外的空字节 ( ) 。由于 C 字符串中从不包含任何内容,因此这清楚地标记了结尾。 但对于像汇编中的节这样的内容,很难保证特定的字节永远不会出现,因此这种方法不实用。
"Hello World!"
0x00
0x00
.text
对于 ELF (Linux) 或 PE (Windows) 等可执行格式,应使用第一种方法(使用节大小信息)。如果你想深入研究,可以查阅有关 ELF 等可执行格式的书籍,但老实说,这种详细程度对于大多数程序员来说不是必要的。
抱歉我的英语不好,虽然我的英语水平足够好,能够理解,但是写作时却很吃力。
然而,当汇编代码被翻译成二进制时,计算机会根据内存的组织和可执行文件中包含的信息来区分代码和数据。
内存分段程序被划分为不同的部分:
文本段 (.text):包含可执行指令。数据段 (.data):包含静态数据,例如已初始化的变量。BSS 段 (.bss):包含未初始化的数据。堆栈和堆:用于动态内存管理的区域。可执行文件格式当汇编和链接程序集时,生成的文件(例如,Linux 系统上的 ELF 格式)包含一个表,该表指定哪些部分包含代码和哪些数据。操作系统在将程序加载到内存中时会遵循这种划分。
内存保护 现代 CPU 使用内存保护来分离代码和数据。例如,文本段通常是只读且可执行的,而数据段是可写但不可执行的。这有助于防止错误和缓冲区溢出等攻击。
指针和寻址 CPU 的执行单元根据指令计数器 (PC - 程序计数器) 来判断要执行哪些指令。可执行代码被加载到特定地址,而数据则加载到其他地址,从而避免两者混淆。
综上所述,虽然在汇编之后.data和.text伪指令被删除,但是可执行文件仍然保留了划分,操作系统和CPU使用这种组织方式来区分代码和数据。
希望我已经让你了解它是如何工作的!