Подтвердить что ты не робот

Что C и Ассемблер действительно скомпилировать?

Итак, я выяснил, что программы C (++) на самом деле не компилируются в обычный "двоичный" (возможно, я получил некоторые ошибки здесь, в этом случае мне жаль: D), но к целому ряду вещей (таблица символов, связанный с os материал,...), но...

  • Ассемблер "компилируется" в чистый двоичный файл? Это означает, что нет лишних материалов, кроме ресурсов, таких как предопределенные строки и т.д.

  • Если C компилируется на что-то другое, чем обычный двоичный файл, как этот маленький загрузчик может просто скопировать инструкции с жесткого диска в память и выполнить их? Я имею в виду, если ядро ​​ОС, которое, вероятно, написано на языке С, компилируется в нечто отличное от простого двоичного файла - как его обрабатывает загрузчик?

edit: Я знаю, что ассемблер не "компилируется", потому что он только установил вашу машинную инструкцию - я не нашел хорошего слова для того, что собирает сборщик. Если он у вас есть, оставьте его здесь как комментарий, и я его изменю.

4b9b3361

Ответ 1

C обычно компилируется для ассемблера, только потому, что это облегчает жизнь для плохого автора компилятора.

Код сборки всегда собирает (не "компилирует" ) на перемещаемый объектный код. Вы можете рассматривать это как двоичный машинный код и двоичные данные, но с большим количеством украшений и метаданных. Ключевыми частями являются:

  • Код и данные отображаются в названных "разделах".

  • Перемещаемые объектные файлы могут включать определения меток, которые относятся к местоположениям в разделах.

  • Перемещаемые объектные файлы могут содержать "дыры", которые должны быть заполнены значениями меток, определенных в другом месте. Официальное название такой дыры - это запись о перемещении.

Например, если вы компилируете и собираете (но не связываете) эту программу

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

вы, вероятно, закончите с перемещаемым объектным файлом с помощью

  • A text, содержащий машинный код для main

  • Определение метки для main, которое указывает на начало текстового раздела

  • A rodata (только для чтения) раздел, содержащий байты строкового литерала "Hello, world\n"

  • Элемент перемещения, который зависит от printf и указывает на "отверстие" в команде вызова в середине текстового раздела.

Если вы находитесь в системе Unix, перемещаемый объектный файл обычно называется файлом .o, как в hello.o, и вы можете исследовать определения меток и использовать их с помощью простого инструмента под названием nm, и вы можете получить более подробную информацию из несколько более сложного инструмента под названием objdump.

Я преподаю класс, который охватывает эти темы, и у меня есть ученики, которые пишут ассемблер и компоновщик, который занимает пару недель, но когда они сделали это, у большинства из них есть довольно хороший дескриптор перемещаемого объектного кода. Это не такая легкая вещь.

Ответ 2

Возьмем программу C.

Когда вы запускаете 'gcc' или 'cl' в c-программе, он будет проходить следующие этапы:

  • Лексирование препроцессора (#include, #ifdef, анализ триграфа, переводы кодировок, управление комментариями, макросы...)
  • Лексический анализ (изготовление токенов и лексических ошибок).
  • Синтаксический анализ (создание дерева синтаксического анализа и синтаксических ошибок).
  • Семантический анализ (создание таблицы символов, информации о масштабах и ошибок определения/ввода).
  • Выход в сборку (или другой промежуточный формат)
  • Оптимизация сборки (как указано выше). Вероятно, в ASM-строках все еще.
  • Сборка сборки в некоторый формат двоичного объекта.
  • Связывание сборки с любыми статическими библиотеками необходимо, а также при необходимости переместить ее.
  • Вывод окончательного исполняемого файла в формате эльфа или coff.

На практике некоторые из этих шагов могут выполняться одновременно, но это логический порядок.

Обратите внимание, что там есть "контейнер" эльфа или формата coff вокруг фактического исполняемого двоичного файла.

Вы найдете книгу о компиляторах (я рекомендую Dragon, стандартная вступительная книга в этой области) будет иметь всю информацию вам нужно и многое другое.

Как отметил Марко, связывание и загрузка - большая область, и книга Дракона более или менее останавливается на выходе исполняемого двоичного файла. Фактически переход оттуда к работе в операционной системе - это довольно сложный процесс, который охватывает Левин в Linkers and Loaders.

У меня есть wiki'd этот ответ, чтобы люди настраивали любые ошибки/добавляли информацию.

Ответ 3

Существуют разные этапы перевода С++ в двоичный исполняемый файл. В спецификации языка явно не указаны фазы перевода. Однако я опишу общие этапы перевода.

Источник С++ для сборки или языка Itermediate

Некоторые компиляторы фактически переводят код С++ на язык ассемблера или на промежуточный язык. Это не необходимый этап, но полезный при отладке и оптимизации.

Сборка в код объекта

Следующий общий шаг - перевести язык ассемблера в код объекта. Объектный код содержит код сборки с относительными адресами и открытые ссылки на внешние подпрограммы (методы или функции). В общем, переводчик помещает как можно больше информации в объектный файл, а все остальное неразрешено.

Связывание кода объекта (ов)

Эта фаза связывания объединяет один или несколько объектных кодов, разрешает ссылки и устраняет дубликаты подпрограмм. Конечным результатом является исполняемый файл. Этот файл содержит информацию для операционной системы и относительных адресов.

Выполнение двоичных файлов

Операционная система загружает исполняемый файл, обычно с жесткого диска, и помещает его в память. ОС может преобразовывать относительные адреса в физические местоположения. ОС также может подготовить ресурсы (например, библиотеки DLL и GUI-виджеты), которые требуются исполняемому файлу (который может быть указан в исполняемом файле).

Компиляция непосредственно в двоичную Некоторые компиляторы, такие как те, что используются во встроенных системах, имеют возможность компилировать из С++ непосредственно в исполняемый двоичный код. Этот код будет иметь физические адреса вместо относительного адреса и не требует загрузки ОС.

Преимущества

Одним из преимуществ этих этапов является то, что программы на С++ могут быть разбиты на куски, скомпилированные индивидуально и связаны позже. Их можно даже связать с частями от других разработчиков (библиотеки a.k.a.). Это позволяет разработчикам создавать только компоненты компилятора в разработке и ссылки на уже проверенные части. В общем, перевод с С++ на объект - это трудоемкая часть процесса. Кроме того, человек не хочет ждать завершения всех этапов, когда в исходном коде есть ошибка.

Держите открытый ум и всегда ожидайте Третью альтернативу (вариант).

Ответ 4

Чтобы ответить на ваши вопросы, обратите внимание, что это субъективно, так как есть разные процессоры, разные платформы, разные сборщики и компиляторы C, в этом случае я расскажу о платформе Intel x86.

  • Ассемблеры не компилируются в чистые двоичные файлы, они представляют собой исходный машинный код, определенный с помощью сегментов, таких как данные, текст и bss, но несколько, это называется объектным кодом. Linker выполняет шаги и настраивает сегменты, чтобы сделать их исполняемыми, то есть готовыми к запуску. Кстати, вывод по умолчанию при компиляции с использованием gcc - это "a.out", это сокращение для выхода Assembler.
  • У загрузчиков есть специальная директива, определенная во времена DOS, было бы обычным найти директиву, такую ​​как .Org 100h, которая определяет код ассемблера старого сорта .COM перед .EXE популярность. Кроме того, вам не нужно было создавать ассемблер для создания файла .COM, используя старый debug.exe, который поставлялся с MSDOS, сделал трюк для небольших простых программ. Файлы .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

Это создает готовую к запуску .COM-программу под названием "reply.com", которая ждет нажатия клавиши, а не выводит ее на экран. Обратите внимание, что начало использования "100h", которое показывает, что указатель инструкции начинается с 100h, что является признаком .COM. Этот старый script в основном использовался в пакетных файлах, ожидающих ответа, а не эхом. Оригинальный script можно найти здесь.

Опять же, в случае загрузчиков они преобразуются в двоичный формат, была программа, которая использовалась с DOS, называемая EXE2BIN. Это была задача преобразования исходного кода объекта в формат, который можно скопировать на загрузочный диск для загрузки. Помните, что никакой компоновщик не запускается против собранного кода, поскольку компоновщик предназначен для среды выполнения и настраивает код, чтобы сделать его выполнимым и исполняемым.

BIOS при загрузке ожидает, что код будет находиться в сегменте: offset, 0x7c00, если моя память будет мне полезна, код (после EXE2BIN'd) начнет выполнение, тогда загрузчик переместится вниз в память и продолжайте загрузку, выгрузив int 0x13 для чтения с диска, включите A20-ворот, включите DMA, переключитесь в защищенный режим, так как BIOS находится в 16-битном режиме, затем данные, считанные с диска, загружаются в память, а затем проблемы с загрузчиком далекий переход в код данных (вероятно, написанный на C). Это по сути, как система загружается.

Хорошо, предыдущий абзац звучит абстрактно и просто, я, возможно, что-то пропустил, но так оно и есть в двух словах.

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

Ответ 5

Они компилируются в файл в определенном формате (COFF для Windows и т.д.), состоящий из заголовков и сегментов, некоторые из которых имеют "простые двоичные" op-коды. Ассемблеры и компиляторы (например, C) создают одинаковый вывод. Некоторые форматы, такие как старые *.COM файлы, не имели заголовков, но все же имели определенные предположения (например, где в памяти он загружался или насколько он был бы большой).

На компьютерах Windows ускоритель ОС находится в дисковой области, загруженной BIOS, где оба они являются "обычными". Как только ОС загрузит свой загрузчик, он может читать файлы с заголовками и сегментами.

Помогает ли это?

Ответ 6

Чтобы ответить на часть сборки вопроса, сборка не компилируется в двоичную, насколько я ее понимаю. Сборка === двоичная. Он напрямую переводит. Каждая операция сборки имеет двоичную строку, которая непосредственно соответствует ей. Каждая операция имеет двоичный код, и каждая переменная регистра имеет двоичный адрес.

То есть, если Assembler!= Assembly и я не понимаю ваш вопрос.

Ответ 7

Есть две вещи, которые вы можете смешивать здесь. Обычно есть две темы:

Последний может скомпилировать первый в процессе сборки. Некоторые промежуточные форматы не собираются, а выполняются виртуальной машиной. В случае С++ он может быть скомпилирован в CIL, который собирается в сборку .NET, следовательно, меня может смутить.

Но в целом C и С++ обычно скомпилируются в двоичные файлы, или, другими словами, в формат исполняемого файла.

Ответ 8

У вас есть много ответов, чтобы прочитать, но я думаю, что могу сохранить это кратким.

"Двоичный код" относится к битам, которые питаются через микропроцессорные схемы. Микропроцессор последовательно загружает каждую команду из памяти, делая все, что они говорят. У разных семейств процессоров есть разные форматы для инструкций: x86, ARM, PowerPC и т.д. Вы указываете процессор по желаемой инструкции, указывая ему адрес инструкции в памяти, а затем он весело прокручивает всю оставшуюся часть программы.

Если вы хотите загрузить программу в процессор, вам сначала нужно сделать двоичный код доступным в памяти, чтобы он имел адрес в первую очередь. Компилятор C выводит файл в файловой системе, который должен быть загружен в новое виртуальное адресное пространство. Поэтому в дополнение к двоичному коду этот файл должен содержать информацию о том, что у него есть двоичный код, и как должно выглядеть его адресное пространство.

У загрузчика есть разные требования, поэтому его формат файла может отличаться. Но идея одна и та же: двоичный код всегда является полезной нагрузкой в ​​большем формате файла, который включает в себя как минимум проверку работоспособности, чтобы убедиться, что она написана в правильном наборе инструкций.

Компиляторы и сборщики C обычно конфигурируются для создания файлов статической библиотеки. Для встроенных приложений вы с большей вероятностью найдете компилятор, который создает что-то вроде необработанного образа памяти с инструкциями, начинающимися с нулевого адреса. В противном случае вы можете написать компоновщик, который преобразует вывод компилятора C во все, что вы хотите.

Ответ 9

Как я понимаю, чипсет (CPU и т.д.) будет иметь набор регистров для хранения данных и понять набор инструкций для управления этими регистрами. Инструкции будут такими, как "сохранить это значение в этом регистре", "переместить это значение" или "сравнить эти два значения". Эти инструкции часто выражаются в коротких человеко-поступательных алфавитных кодах (язык ассемблера или ассемблер), которые сопоставляются с цифрами, которые понимает чипсет, - эти числа представлены чипу в двоичном (машинный код).

Эти коды являются самым низким уровнем, на который распространяется программное обеспечение. Идя глубже, чем это проникает в архитектуру реального чипа, к чему я не участвовал.

Ответ 10

На вас было много ответов, но я подумал, что добавлю эти ресурсы, которые дадут вам вкус того, что произойдет. В принципе, в Windows и Linux кто-то пытался создать максимально возможный исполняемый файл; в Linux, ELF, windows, PE.

Оба запускают то, что удалено, и почему и вы используете ассемблеры для создания файлов ELF без использования параметров -felf, которые делают это для вас.

Надеюсь, что это поможет.

Изменить - вы также можете взглянуть на сборку для загрузчика, например, в truecrypt http://www.truecrypt.org или "stage1" grub (бит, который фактически записывается в MDR).

Ответ 11

Исполняемые файлы (формат PE для окон) не могут использоваться для загрузки компьютера, поскольку загрузчик PE не находится в памяти.

Способ загрузки начинается с того, что основная загрузочная запись на диске содержит blob из нескольких сотен байтов кода. BIOS компьютера (в ПЗУ на материнской плате) загружает этот блок в память и устанавливает указатель инструкции процессора на начало этого загрузочного кода.

Затем загрузочный код загружает загрузчик "второй стадии" в Windows под названием NTL;DR (без расширения) из корневого каталога. Это исходный машинный код, который, как и загрузчик MBR, загружается в память холодным и выполняется.

NTL;DR имеет полную возможность загружать файлы PE, включая DLL и драйверы.

Ответ 12

С (++) (неуправляемый) действительно компилируется в обычный двоичный файл. Некоторые связанные с ОС вещи - это вызовы функций BIOS и ОС, они разные для каждой ОС, но все еще двоичные.
1. Ассемблер компилируется в чистый двоичный файл, но, как ни странно, он менее оптимизирован, чем C (++)
2. Ядро ОС, а также загрузчик, также написанные на C, поэтому проблем здесь нет.

Java, управляемый С++ и другие компоненты .NET, компилируется в некоторый псевдокод (MSIL в .NET), что делает его кросс-платформенным и кросс-платформенным, но требует выполнения локального интерпретатора или переводчика.