C和汇编程序实际编译的是什么?

时间:2010-01-25 21:28:53

标签: c++ c compiler-construction linker assembly

所以我发现C(++)程序实际上并没有编译成简单的“二进制”(我可能在这里遇到了一些错误,在这种情况下我很抱歉:D)但是对于一系列事情(符号表,os相关的东西,......)但是......

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

  • 如果C编译成除了普通二进制文件以外的其他内容,那么小型汇编程序引导加载程序如何才能将指令从HDD复制到内存并执行它们?我的意思是,如果操作系统内核(可能用C语言编写)编译成不同于普通二进制文件的内容 - 引导加载程序如何处理它?<​​/ p>

编辑:我知道汇编程序没有“编译”,因为它只有你机器的指令集 - 我没有找到汇编程序“汇编”的好词。如果你有一个,请留在这里作为评论,我会改变它。

12 个答案:

答案 0 :(得分:42)

C通常编译为汇编程序,只是因为这样可以让糟糕的编译器生成简单。

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

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

  • 可重定位目标文件可能包含标签的定义,这些定义涉及各部分中的位置。

  • 可重定位目标文件可能包含要填充其他位置定义的标签值的“空洞”。此类漏洞的官方名称是重定位条目

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

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

您最终可能会使用

的可重定位目标文件
  • text部分包含main

  • 的机器代码
  • main的标签定义,指向文字部分的开头

  • 包含字符串文字rodata

  • 字节的"Hello, world\n"(只读数据)部分
  • 依赖于printf并且指向文本部分中间的调用指令中的“洞”的重定位条目。

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

我教一个涵盖这些主题的课程,我让学生编写汇编程序和链接器,这需要几个星期,但是当他们完成时,他们中的大部分都可以很好地处理可重定位目标代码。这不是一件容易的事。

答案 1 :(得分:36)

我们来看一个C程序。

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

  1. 预处理器lexing(#include,#ifdef,trigraph分析,编码翻译,评论管理,宏......)
  2. 词法分析(产生令牌和词汇错误)。
  3. 句法分析(产生解析树和语法错误)。
  4. 语义分析(生成符号表,作用域信息和作用域/输入错误)。
  5. 输出到装配(或其他中间格式)
  6. 装配优化(如上所述)。可能还在ASM字符串中。
  7. 将程序集组装成一些二进制对象格式。
  8. 需要将程序集链接到任何静态库,并在需要时重新定位。
  9. 以elf或coff格式输出最终可执行文件。
  10. 实际上,其中一些步骤可能同时完成,但这是逻辑顺序。

    请注意,实际可执行二进制文件周围有一个elf或coff格式的“容器”。

    你会发现一本关于编译器的书(我推荐Dragon书,该领域的标准入门书)将所有所需的信息以及更多。

    正如Marco评论的那样,链接和加载是一个很大的区域,龙书或多或少停止在可执行二进制文件的输出。实际上从那里开始运行在操作系统上是一个相当复杂的过程,Levine在Linkers and Loaders中介绍过。

    我维基这个答案让人们调整任何错误/添加信息。

答案 2 :(得分:18)

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

源C ++汇编或中间语言

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

装配到目标代码

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

链接目标代码

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

执行二进制文件

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

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

优点

这些阶段的一个优点是C ++程序可以分解成碎片,单独编译并在以后链接。它们甚至可以与其他开发人员(a.k.a. libraries)的作品链接。这允许开发人员仅在开发中编译片段并链接已经验证的片段。通常,从C ++到对象的转换是该过程的耗时部分。此外,当源代码中存在错误时,一个人不希望等待所有阶段完成。

保持开放的态度并始终期待第三种选择(选项)

答案 3 :(得分:3)

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

  1. 汇编程序不编译为纯二进制文件,它们是原始机器代码,用段定义,例如数据,文本和bss等等,这就是所谓的目标代码。链接器介入并调整段以使其可执行,即准备运行。顺便提一下,使用gcc编译时的默认输出是'a.out',这是汇编程序输出的简写。
  2. 引导加载程序有一个特殊的指令定义,在DOS的时代,通常会找到一个指令,如.Org 100h,它将汇编程序代码定义为.EXE之前的旧.COM变种。接管人气。此外,您不需要使用汇编程序来生成.COM文件,使用MSDOS附带的旧debug.exe,为小型简单程序执行操作,.COM文件不需要链接器并且直接就绪 - 运行二进制格式。这是一个使用DEBUG的简单会话。
  3. 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的特征。这个旧脚本主要用于等待响应的批处理文件而不是回显它。可以找到原始脚本here

    同样,在引导加载程序的情况下,它们被转换为二进制格式,有一个程序曾经用于DOS,称为EXE2BIN。这是将原始对象代码转换为可以复制到可引导磁盘以进行引导的格式的工作。请记住,没有链接器针对汇编的代码运行,因为链接器用于运行时环境并设置代码以使其可运行和可执行。

    BIOS启动时,要求代码位于segment:offset,0x7c00,如果我的内存服务正确,代码(在EXE2BIN之后)将开始执行,然后引导加载程序将自身重新定位在内存中,通过发出int 0x13继续加载从磁盘读取,打开A20门,启用DMA,当BIOS处于16位模式时切换到保护模式,然后从磁盘读取的数据被加载到内存中,然后引导加载程序发出远远跳入数据代码(可能用C语言编写)。这实质上就是系统的启动方式。

    好的,上一段听起来抽象而且简单,我可能错过了一些东西,但简而言之就是这样。

    希望这有帮助, 最好的祝福, 汤姆。

答案 4 :(得分:1)

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

在Windows机器上,操作系统的boostrapper位于由BIOS加载的磁盘扇区中,其中两者都是“普通”的。一旦操作系统加载了它的加载程序,它就可以读取包含标题和段的文件。

这有帮助吗?

答案 5 :(得分:1)

要回答问题的汇编部分,汇编不会编译为二进制,因为我理解它。汇编===二进制。它直接翻译。每个汇编操作都有一个直接匹配它的二进制字符串。每个操作都有一个二进制代码,每个寄存器变量都有一个二进制地址。

也就是说,除非汇编!=汇编,我误解了你的问题。

答案 6 :(得分:1)

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

这些代码是软件最低级别的代码。比这更深入到实际芯片的架构中,这是我没有参与的事情。

答案 7 :(得分:1)

你可以在这里混合两件事。通常有两个主题:

后者可能在汇编过程中编译成前者。某些中间格式未组装,但由虚拟机执行。在C ++的情况下,可能被编译成CIL,它被组装成一个.NET程序集,因此我有些困惑。

但通常C和C ++通常被编译成二进制文件,换句话说,编译成可执行文件格式。

答案 8 :(得分:1)

你有很多答案可供阅读,但我想我可以保持这种简洁。

“二进制代码”是指通过微处理器电路提供的位。微处理器按顺序从存储器中加载每条指令,按照它们所说的做。不同的处理器系列具有不同的指令格式:x86,ARM,PowerPC等。通过为处理器提供内存中指令的地址,您可以将处理器指向您想要的指令,然后通过程序的其余部分快速地进行处理。

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

引导加载程序有不同的要求,因此其文件格式可能不同。但这个想法是一样的:二进制代码总是一个更大的文件格式的有效载荷,它至少包括一个健全性检查,以确保它写在正确的指令集中。

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

答案 9 :(得分:0)

上面有很多答案供您查看,但我想我会添加这些资源,让您了解会发生什么。基本上,在Windows和Linux上,有人试图创建最小的可执行文件;在Linux,ELF,windows,PE。

两者都运行已删除的内容以及为什么使用汇编程序构建ELF文件而不使用为您执行此操作的-felf like选项。

希望有所帮助。

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

答案 10 :(得分:0)

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

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

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

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

答案 11 :(得分:-4)

С(++)(非托管)真正编译为普通二进制文件。一些与操作系统相关的东西 - 是BIOS和OS函数调用,它们对于每个操作系统都是不同的,但仍然是二进制的 1.汇编程序编译为纯二进制文件,但是,尽管它很奇怪,但它的优化程度不如C(++)
2.操作系统内核以及bootloader也是用C编写的,所以这里没问题。

Java,Managed C ++和其他.NET东西,编译成一些伪代码(.NET中的MSIL),这使得它跨操作系统和跨平台,但需要本地解释器或转换器才能运行。