C和汇编实际编译到什么地方?

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

  • 汇编程序“编译”为纯二进制文件吗? 这意味着除了预定义的string等资源外,没有额外的东

  • 如果C编译成其他的东西而不是纯二进制文件,那么这个小的汇编引导程序如何才能将指令从硬盘拷贝到内存中并执行? 我的意思是,如果操作系统内核(可能用C编写)编译为不同于普通二进制文件的东西 – 引导加载程序如何处理它?

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

C通常编译为汇编程序,只是因为这使得糟糕的编译器编写者容易生活。

汇编代码总是汇编(而不是“编译”)到可重定位目标代码 。 你可以把它看作二进制机器码和二进制数据,但是有很多装饰和元数据。 关键部分是:

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

  • 可重定位的对象文件可能包括标签的定义,这些定义是指段内的位置。

  • 可重新定位的对象文件可能包括要填充其他地方定义的标签值的“孔”。 这个洞的官方名称是一个搬迁入境

例如,如果你编译和汇编(但不链接)这个程序

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

你很可能用一个可重定位的目标文件结束

  • 包含main的机器码的text部分

  • main的标签定义指向文本部分的开头

  • 包含string文字"Hello, world\n"的字节的一个rodata (只读数据)

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

如果你在一个Unix系统上,一个可重定位的目标文件通常被称为一个.o文件,就像在hello.o ,你可以通过一个叫做nm的简单工具来探索标签的定义和使用,你可以从一个简单的工具有些更复杂的工具称为objdump

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

我们来一个C程序。

在c程序中运行“gcc”或“cl”时,将经历以下几个阶段:

  1. 预处理器(#include,#ifdef,trigraph分析,编码转换,注释pipe理,macros…)
  2. 词汇分析(产生令牌和词法错误)。
  3. 语法分析(产生一个分析树和语法错误)。
  4. 语义分析(产生符号表,范围信息和范围/打字错误)。
  5. 输出到程序集(或其他中间格式)
  6. assembly优化(如上)。 可能在ASMstring中仍然存在。
  7. 汇编成一些二进制对象格式。
  8. 将程序集链接到需要的任何静态库中,并根据需要重新定位。
  9. 以elf或coff格式输出最终的可执行文件。

实际上,这些步骤中的一部分可能同时完成,但这是合乎逻辑的顺序。

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

你会发现一本关于编译器的书(我推荐龙书,这个领域的标准介绍性书籍)将有你需要的所有信息和更多。

正如Marco所评论的,链接和加载是一个很大的领域,Dragon书或多或less地停在可执行二进制文件的输出处。 实际上从那里到在操作系统上运行是一个相当复杂的过程,在连接器和装载机的莱文涵盖。

我已经wiki'd这个答案,让人们调整任何错误/添加信息。

将C ++翻译成二进制可执行文件有不同的阶段。 语言规范没有明确说明翻译阶段。 不过,我会介绍一下通用的翻译阶段。

源代码C ++汇编或中间语言

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

汇编到对象代码

下一个常见步骤是将汇编语言转换为对象代码。 目标代码包含具有相对地址的汇编代码和对外部子程序(方法或函数)的开放引用。 一般而言,翻译者尽可能多地将信息放入目标文件中,其他所有内容都是无法parsing的

链接对象代码

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

执行二进制文件

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

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

优点

这些阶段的优点之一是可以将C ++程序分解成单独的代码并在以后进行链接。 他们甚至可以与其他开发人员(又名图书馆)的作品链接。 这允许开发人员只编译开发中的部分,并链接到已经validation的部分。 一般来说,从C ++到对象的转换是这个过程中耗时的部分。 另外,当源代码出现错误时,人们不希望等待所有阶段的完成。

保持开放的态度,始终期待第三个select(选项)

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

  1. 汇编程序不编译为纯二进制文件,它们是原始的机器代码,用段来定义,比如数据,文本和bss等等,这叫做目标代码。 链接器介入和调整段使其可执行,即准备运行。 顺便说一句,使用gcc编译时的默认输出是'a.out',这是汇编器输出的简写。
  2. 引导加载程序有一个特殊的指令定义,回到DOS的时代,通常会find一个像.Org 100h这样的指令,它将汇编代码定义为以前的.COM变种.EXE接pipestream行。 此外,您不需要汇编程序来生成.COM文件,使用MSDOS随附的旧版debug.exe,为简单的小程序做了一些小技巧,.COM文件不需要链接程序,要运行的二进制格式。 这是一个使用DEBUG的简单会话。
 1:*一个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:* CX
 12:10 *
 13:* n respond.com
 14:*宽
 15:* Q

这会产生一个名为“respond.com”的即时运行的.COM程序,它等待一个按键,而不是回显到屏幕上。 注意,一开始,使用“100h”表示指令指针从100h开始,这是.COM的特征。 这个旧脚本主要用于等待响应的batch file,而不是回显它。 原始脚本可以在这里find。

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

启动时,BIOS预期代码位于segment:offset,0x7c00,如果我的内存服务正确,代码(在EXE2BIN'd之后)将开始执行,然后Bootloader将自己重新定位到内存中并继续加载发出int 0x13从磁盘读取,打开A20门,启用DMA,当BIOS处于16位模式时切换到保护模式,然后从磁盘读取的数据被加载到内存中,然后引导加载程序发出一个远程跳转写入数据代码(很可能用C写成)。 这基本上就是系统启动的方式。

好吧,前一段听起来很抽象,很简单,我可能错过了一些东西,但这是简单的。

希望这有助于,最好的问候,汤姆。

它们以特定格式(COFF for Windows等)编译成文件,由头文件和段组成,其中一些文件具有“纯二进制”操作码。 汇编器和编译器(如C)创build相同的输出。 一些格式,比如旧的.COM文件,没有头文件,但是仍然有一定的假设(比如在内存中它将被加载的位置或者可能有多大)。

在Windows计算机上,操作系统的boostrapper位于由BIOS加载的磁盘扇区中,其中这两个都是“普通”的。 一旦操作系统加载它的加载器,它可以读取具有标题和段的文件。

这有帮助吗?

为了回答问题的汇编部分,汇编不会按照我的理解编译为二进制文件。 大会===二进制。 它直接翻译。 每个程序集操作都有一个直接匹配它的二进制string。 每个操作都有一个二进制代码,每个寄存器variables都有一个二进制地址。

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

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

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

后者可以在汇编过程中对前者进行编纂。 有些中间格式不会被汇编,而是由虚拟机执行。 在C ++的情况下,它可能被编译成CIL,它被组装成一个.NET程序集,因此我有一些混淆。

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

你有很多答案要通读,但我想我可以保持这个简洁。

“二进制码”是指通过微处理器电路的位。 微处理器按顺序加载内存中的每条指令,做他们所说的。 不同的处理器系列具有不同的指令格式:x86,ARM,PowerPC等。通过给处理器指定内存中的指令地址,可以将处理器指向所需的指令,然后快速地沿着程序的其余部分跳转。

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

自举程序有不同的要求,所以其文件格式可能会有所不同。 但是这个想法是一样的:二进制代码总是一个更大的文件格式的有效载荷,其中至less包括一个完整性检查,以确保它被写入正确的指令集。

C编译器和汇编程序通常被configuration为产生静态库文件。 对于embedded式应用程序,您更有可能find一个编译器,它会生成类似原始内存映像的指令,其指令从零开始。 否则,你可以编写一个链接器,将C编译器的输出转换成你想要的任何东西。

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

这些代码是软件所能达到的最低级别。 进一步深入到实际芯片的架构,这是我没有涉及的东西。

上面有很多答案供您参考,但是我认为我会添加这些资源,让您知道发生了什么。 基本上,在Windows和Linux上,有人试图创build最小的可执行文件; 在Linux,ELF,windows,PE。

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

两者都是通过删除的内容以及为什么使用汇编器来构buildELF文件,而不使用自己喜欢的选项。

希望有所帮助。

编辑 – 你也可以看看程序集中的bootloader,比如truecrypt http://www.truecrypt.org或者grub的“stage1”(实际上被写入MDR的那个)。;

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

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

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

NTLDR具有加载PE文件(包括DLL和驱动程序)的完整function。

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

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