问与答 C和汇编语言实际上可以编译为什么?

moore · 2020-03-06 20:56:54 · 热度: 22

因此,我发现C(++)程序实际上不会编译为普通的“二进制”(在这里我可能弄错了一些东西,在这种情况下,我很抱歉:D),而是将其编译为一系列东西(符号表) ,与操作系统相关的内容,...),但是...

  • 汇编程序是否可以“编译”为纯二进制文件? 这意味着除了预定义的字符串等资源外,没有多余的东西。

  • 如果C编译为普通二进制以外的其他程序,那么那个小型汇编程序引导加载程序如何将指令从HDD复制到内存并执行呢? 我的意思是,如果OS内核(可能是用C编写)编译为与普通二进制文件不同的东西-引导程序如何处理它?

编辑:我知道汇编程序不会“编译”,因为它只具有您机器的指令集-对于汇编程序“汇编”到的内容,我找不到很好的词。 如果您有一个,请将其留在此处作为评论,我将对其进行更改。

猜你喜欢:
共收到 12 条回复
nestor #1 · 2020-03-06 20:56:55

C通常编译为汇编程序,只是因为这使可怜的编译器编写者变得容易。

汇编代码始终将汇编(而不是“编译”)为可重定位的目标代码。 您可以将其视为二进制机器代码和二进制数据,但是具有大量修饰和元数据。 关键部分是:

  • 代码和数据显示在名为“ sections”的部分中。

  • 可重定位的目标文件可能包括标签的定义,这些标签指的是各部分中的位置。

  • 可重定位目标文件可能包含“空洞”,这些空洞将被其他位置定义的标签值填充。 这样的孔的正式名称是重新定位条目。

例如,如果您编译并汇编(但不链接)此程序

int main () { printf("Hello, world\n"); }

您可能会遇到一个带有以下内容的可重定位目标文件:

  • text部分包含hello.o的机器代码

  • hello.o的标签定义,它指向文本部分的开头

  • hello.o(只读数据)部分包含字符串文字nm的字节

  • 依赖于hello.o的重定位条目,其指向文本部分中间的调用指令中的“孔”。

如果您在Unix系统上,则可重定位的目标文件通常称为.o文件,如hello.o中所示,您可以使用名为nm的简单工具来探索标签的定义和使用,并且可以从更多信息中获得更详细的信息。 复杂的工具称为objdump

我教的课程涵盖了这些主题,我让学生写了一个汇编器和链接器,这需要花几个星期的时间,但是当他们完成后,大多数人对可重定位的目标代码都有很好的理解。 这不是一件容易的事。

ansel #2 · 2020-03-06 20:56:56

让我们来一个C程序。

在c程序上运行.comclang或'cl'时,它将经历以下阶段:

  1. 预处理程序(#include,#ifdef,trigraph分析,编码翻译,注释管理,宏...),包括词汇化为预处理程序令牌,最终生成纯文本,以供适当的编译器输入。
  2. 词法分析(产生标记和词法错误)。
  3. 语法分析(生成语法分析树和语法错误)。
  4. 语义分析(生成符号表,作用域信息和作用域/类型错误)以及数据流,将程序逻辑转换为优化器可以使用的“中间表示”。 (通常是SSA)。 clang / LLVM使用LLVM-IR,gcc使用GIMPLE,然后使用RTL。
  5. 程序逻辑的优化,包括常量传播,内联,将不变量提升到循环外,自动矢量化以及许多其他事情。 (对于一个广泛使用的现代编译器,大多数代码都是优化过程。)通过中间表示进行转换只是某些编译器工作方式的一部分,这使得“禁用所有优化”成为不可能/毫无意义。
  6. 输出到汇编源(或其他中间格式,如.NET IL字节码)
  7. 将程序集组装为某种二进制对象格式。
  8. 将程序集链接到所需的任何静态库中,并在需要时进行重新定位。
  9. 以elf,PE / coff,MachO64或其他任何格式输出最终可执行文件

实际上,这些步骤中的某些步骤可以同时完成,但这是合乎逻辑的顺序。 大多数编译器都有在任何给定步骤(例如预处理或汇编)之后停止的选项,包括在诸如GCC之类的开源编译器的优化过程之间转储内部表示。 (.com

请注意,实际的可执行二进制文件周围有elf或coff格式的“容器”,除非它是DOS .com可执行文件

您会发现一本关于编译器的书(我建议使用Dragon本书,该书是该领域的标准入门书),它将提供您需要的所有信息以及更多信息。

正如Marco所评论的那样,链接和加载是一个很大的区域,而Dragon本书或多或少地停止在可执行二进制文件的输出上。 从那里实际运行到操作系统是一个相当复杂的过程,链接程序和装载程序中的Levine对此进行了介绍。

我已经将此答案维基化,以使人们可以调整任何错误/添加信息。

raul #3 · 2020-03-06 20:56:58

将C ++转换为二进制可执行文件有不同的阶段。 语言规范未明确规定翻译阶段。 但是,我将介绍常见的翻译阶段。

汇编或中间语言的源C ++

一些编译器实际上将C ++代码转换为汇编语言或中间语言。 这不是必需的阶段,但有助于调试和优化。

汇编到目标代码

下一步是将汇编语言转换为目标代码。 目标代码包含具有相对地址的汇编代码和对外部子例程(方法或函数)的开放引用。 通常,翻译器将尽可能多的信息输入到目标文件中,其他所有未解决的信息。

链接目标代码

链接阶段合并一个或多个目标代码,解析引用并消除重复的子例程。 最终输出是一个可执行文件。 该文件包含有关操作系统和相对地址的信息。

执行二进制文件

操作系统通常从硬盘驱动器加载可执行文件,然后将其放入内存。 OS可以将相对地址转换为物理位置。 操作系统还可以准备可执行文件(可能在可执行文件中声明)所需的资源(例如DLL和GUI小部件)。

直接编译为二进制某些编译器(例如,嵌入式系统中使用的编译器)具有从C ++直接编译为可执行二进制代码的能力。 该代码将具有物理地址,而不是相对地址,并且不需要操作系统加载。

优点

这些阶段的优点之一是C ++程序可以分解成多个部分,分别编译并在以后链接。 它们甚至可以与其他开发人员(也称为库)的作品链接。 这允许开发人员仅在开发中编译部分,并链接已验证的部分。 通常,从C ++到对象的转换是过程的耗时部分。 同样,当源代码中有错误时,人们也不想等待所有阶段完成。

保持开放的态度,并始终期待第三个替代方案(选项)。

sebastian #4 · 2020-03-06 20:56:59

要回答您的问题,请注意,这是主观的,因为有不同的处理器,不同的平台,不同的汇编程序和C编译器,在这种情况下,我将讨论英特尔x86平台。

  1. 汇编程序不会编译为纯二进制文件,它们是原始的机器代码,它使用段(例如数据,文本和bss等)来定义,但其中一些称为目标代码。 链接器进入并调整段以使其可执行,即可以运行。 顺便说一句,使用gcc进行编译时的默认输出是'a.out',这是汇编输出的简写形式。
  2. 在DOS时代,引导加载程序有一个特殊的指令定义,通常会找到诸如.Org 100h之类的指令,该指令将汇编程序代码定义为.EXE变种之前的.COM变体。 另外,您不需要使用MSDOS附带的旧版debug.exe来生成.COM文件的汇编程序,就可以解决小型简单程序的问题,.COM文件不需要链接程序,并且可以直接使用- 运行二进制格式。 这是一个使用DEBUG的简单会话。
1:*a 0100
2:* mov AH,07
3:* int 21
4:* cmp AL,00
5:* jnz 010c
6:* mov AH,07
7:* int 21
8:* mov AH,4C
9:* int 21
10:*
11:*r CX
12:*10
13:*n respond.com
14:*w
15:*q

这将生成一个名为“ respond.com”的随时可运行的.COM程序,该程序等待按键并不将其回显到屏幕上。 请注意,开始时使用的“ a 100h”表示指令指针从100h开始,这是.COM的功能。 这个旧脚本主要用于批处理文件中,等待响应而不回显。 原始脚本可以在这里找到。

同样,在使用引导加载程序的情况下,它们会转换为二进制格式,而DOS附带有一个名为EXE2BIN的程序。 那是将原始目标代码转换成可以复制到可引导磁盘上进行引导的格式的工作。 请记住,没有链接程序针对汇编代码运行,因为该链接程序用于运行时环境,并且设置了代码以使其可运行和可执行。

BIOS在引导时,期望代码位于以下位置:offset:offset,0x7c00,如果我的内存正确无误,则该代码(在EXE2BIN之后)将开始执行,然后引导加载程序将其自身重新定位到内存中的较低位置并继续加载 发出int 0x13以从磁盘读取,打开A20门,启用DMA,在BIOS处于16位模式时切换到保护模式,然后将从磁盘读取的数据加载到内存中,然后引导加载程序发出一个远跳转 放入数据代码(可能用C编写)。 从本质上讲,这就是系统引导的方式。

好的,上一段听起来很抽象而且很简单,我可能错过了一些内容,但是总的来说就是这样。

希望这可以帮助,最好的祝福,汤姆

walther #5 · 2020-03-06 20:57:00

它们编译为特定格式的文件(Windows为COFF等),该文件由标头和段组成,其中有些具有“普通二进制”操作码。 汇编器和编译器(例如C)创建相同类型的输出。 某些格式,例如旧的* .COM文件,没有标头,但仍具有某些假设(例如,它将在内存中加载的位置或可能的大小)。

在Windows计算机上,操作系统的引导程序位于BIOS加载的磁盘扇区中,这两个扇区均为“普通”磁盘。 操作系统加载了加载程序后,便可以读取具有标头和段的文件。

有帮助吗?

omar #6 · 2020-03-06 20:57:02

为了回答问题的汇编部分,据我所知,汇编未编译为二进制。 汇编===二进制。 它直接翻译。 每个汇编操作都有一个与之直接匹配的二进制字符串。 每个操作都有一个二进制代码,每个寄存器变量都有一个二进制地址。

也就是说,除非Assembler!= Assembly,否则我会误解您的问题。

aubree #7 · 2020-03-06 20:57:03

您可以在此处混合两件事。 通常有两个主题:

  • 可执行文件格式(请参见此处的列表),例如COFF,XCOFF,ELF
  • 中间语言,例如CIL或GIMPLE或字节码

后者可以在组装过程中编译为前者。 某些中间格式不是汇编的,而是由虚拟机执行的。 如果使用C ++,则可以将其编译为CIL,再将其组装为.NET程序集,因此我有些困惑。

但是通常,C和C ++通常会编译为二进制文件,或者换言之,编译为可执行文件格式。

trever #8 · 2020-03-06 20:57:04

您有很多答案需要阅读,但我想我可以保持简洁。

“二进制代码”是指通过微处理器电路馈送的位。 微处理器按顺序执行从存储器中加载的每个指令。 不同的处理器系列具有不同的指令格式:x86,ARM,PowerPC等。您可以通过在内存中提供指令的地址来将处理器指向所需的指令,然后在程序的其余部分轻松地进行转换。

当您要将程序加载到处理器中时,首先必须使二进制代码可在内存中访问,因此它首先具有一个地址。 C编译器在文件系统中输出一个文件,该文件必须加载到新的虚拟地址空间中。 因此,除了二进制代码之外,该文件还必须包含以下信息:该文件具有二进制代码以及其地址空间应为什么样。

引导加载程序具有不同的要求,因此其文件格式可能不同。 但是想法是一样的:二进制代码始终是较大文件格式的有效负载,其中至少包括完整性检查以确保以正确的指令集编写了二进制代码。

通常将C编译器和汇编器配置为生成静态库文件。 对于嵌入式应用程序,您更有可能找到一个编译器,该编译器会生成诸如原始内存映像之类的指令,指令从地址零开始。 否则,您可以编写一个链接器,该链接器将C编译器的输出转换为您想要的任何其他内容。

nichole #9 · 2020-03-06 20:57:06

据我了解,芯片组(CPU等)将具有一组用于存储数据的寄存器,并了解一组用于操纵这些寄存器的指令。 这些指令将是“将这个值存储到该寄存器”,“移动这个值”或“比较这两个值”之类的事情。 这些指令通常用人类可写的短字母代码(汇编语言或汇编程序)表示,这些代码映射到芯片组可以理解的数字-这些数字以二进制(机器代码)的形式呈现给芯片。

这些代码是软件所能达到的最低级别。 深入到实际芯片的体系结构中,这是我没有参与的事情。

samli #10 · 2020-03-06 20:57:07

上面有很多答案供您参考,但我想我会添加这些资源,以使您对发生的事情有所了解。 基本上,在Windows和Linux上,有人试图创建最小的可执行文件。 在Linux,ELF,Windows,PE中。

  • 小小PE:[http://www.phreedom.org/solar/code/tinype/]
  • 小型ELF文件:[http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html]

两者都经历了删除的内容以及原因,并且您使用汇编程序来构造ELF文件,而没有使用为您执行此操作的-felf之类的选项。

希望能有所帮助。

编辑-您还可以查看引导加载程序的程序集,例如truecrypt [http://www.truecrypt.org]中的引导程序或grub的“ stage1”(实际写入MDR的位)中的程序。

tab #11 · 2020-03-06 20:57:09

可执行文件(Windows上为PE格式)不能用于引导计算机,因为PE加载器不在内存中。

引导的工作方式是磁盘上的主引导记录包含数百字节代码的blob。 计算机的BIOS(在主板上的ROM中)将此Blob加载到内存中,并将CPU指令指针设置为该引导代码的开头。

然后,引导代码从根目录在Windows上加载称为“ NTLDR(无扩展名)”的“第二阶段”加载程序。 这是原始的机器代码,就像MBR加载程序一样,被冷加载到内存中并执行。

NTLDR具有加载PE文件(包括DLL和驱动程序)的全部功能。

bagot #12 · 2020-03-06 20:57:10

С(++)(非托管)实际上可以编译为纯二进制。 一些与OS相关的东西-是BIOS和OS函数调用,它们对于每个OS都是不同的,但仍然是二进制的。
1.汇编程序编译为纯二进制文件,但奇怪的是,它的优化程度不及C(++)
2. OS内核以及引导程序也是用C编写的,因此这里没有问题。

Java,Managed C ++和其他.NET东西会编译为某种伪代码(.NET中为MSIL),从而使其可以跨OS和跨平台运行,但需要本地解释器或翻译器才能运行。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册