Как создать объектный файл

Обновлено: 05.07.2024

GСС - это свободно доступный оптимизирующий компилятор для языков C, C++.

Программа gcc, запускаемая из командной строки, представяляет собой надстройку над группой компиляторов. В зависимости от расширений имен файлов, передаваемых в качестве параметров, и дополнительных опций, gcc запускает необходимые препроцессоры, компиляторы, линкеры.

Файлы с расширением .cc или .C рассматриваются, как файлы на языке C++, файлы с расширением .c как программы на языке C, а файлы c расширением .o считаются объектными.

Чтобы откомпилировать исходный код C++, находящийся в файле F.cc, и создать объектный файл F.o, необходимо выполнить команду:

Опция –c означает «только компиляция».

Чтобы скомпоновать один или несколько объектных файлов, полученных из исходного кода - F1.o, F2.o, . - в единый исполняемый файл F, необходимо ввести команду:

Опция -o задает имя исполняемого файла.

Можно совместить два этапа обработки - компиляцию и компоновку - в один общий этап с помощью команды:

<compile-and-link –options> - возможные дополнительные опции компиляции и компоновки. Опция –lg++ указывает на необходимость подключить стандартную библиотеку языка С++, <other-libraries> - возможные дополнительные библиотеки.
После компоновки будет создан исполняемый файл F, который можно запустить с помощью команды

<arguments> - список аргументов командной строки Вашей программы.
В процессе компоновки очень часто приходится использовать библиотеки. Библиотекой называют набор объектных файлов, сгруппированных в единый файл и проиндексированных. Когда команда компоновки обнаруживает некоторую библиотеку в списке объектных файлов для компоновки, она проверяет, содержат ли уже скомпонованные объектные файлы вызовы для функций, определенных в одном из файлов библиотек. Если такие функции найдены, соответствующие вызовы связываются с кодом объектного файла из библиотеки. Библиотеки могут быть подключены с помощью опции вида -lname . В этом случае в стандартных каталогах, таких как /lib , /usr/lib, /usr/local/lib будет проведен поиск библиотеки в файле с именем libname.a. Библиотеки должны быть перечислены после исходных или объектных файлов, содержащих вызовы к соответствующим функциям.

Опции компиляции

Среди множества опций компиляции и компоновки наиболее часто употребляются следующие:

Объектный файл
Только что начал изучать Assembler и сразу же столкнулся с проблемой: не удаётся получить объектный.

не создается объектный файл в TASM
ЗАДАНИЕ: Имеется система трех уравнений и следующие данные: -строка десятичных байт X из двадцати.

Как создать COM-файл?
Здравствуйте. ; Programm Hello3 .MODEL SMALL .CODE org 100h begin: jmp start Hello DB.

  • под DOS
    D:\masm32\bin\ml /c /Cp D:\MYPROG\atest.asm
  • под Windows
    D:\masm32\bin\ml /c /Cp /Gz /ID:\masm32\include /coff /nologo D:\MYPROG\atest.asm

Что-то произошло, как бы малозаметное милькание. Под виндовс.

Добавлено через 2 минуты
Где его искать, объектный файл?

Добавлено через 1 час 7 минут
Не разобрался.

D:\masm32 это папка с подпапками ассемблера

D:\MYPROG\atest.asm это моя программа

Как создать обьектны файл в D:\MYPROG ?

Объектный файл будет создан в текущем каталоге (папке).

Перемещайся в каталог (папку) с твоей программой
cd D:\MYPROG
И будучи там запускай ассемблер, указав операционке где его брать :
D:\MASM32\BIN\ml ключи atest.asm
, это если у тебя MASM32 инсталлирован в папку D:\MASM32

Добавлено через 1 минуту
Ну да, инсталлирован в папку D:\MASM32

Добавлено через 16 минут
То есть так? cd D:\MYPROG D:\MASM32\BIN\ml

Так выходит ошибка, а так D:\MYPROG D:\MASM32\BIN\ml попадаю в папку D:\MYPROG

И так D:\MYPROG D:\masm32\topgun.exe попадаю в папку D:\MYPROG

>То есть так? cd D:\MYPROG D:\MASM32\BIN\ml
Да не в одну строчку. Это две отдельные команды. М-да. Позвольте начать с командной строки. как в ней двигаца назад?? Часто бывает ошибка , да и вообще.. Уже кучу разных клавиш наугад перепробовал.

В чистой командной строке мало средств редактирования. Ошибся, нажимаешь Backspace несколько раз, забивая набранное неправильно, потом вписываешь правильно. Ну и нажатие F3 вспоминает последнюю исполненную команду.

Очень удобно работать с командной строкой из под FAR. Ctrl-стрелка тогда перемещает курсор по кускам команды а строке. Ctrl-E вспоминает последнюю набранную команду. Alt-F8 выдает список уже набранных команд из которого можно выбрать. Можно вставлять в командную строку из буфера обмена по Shift-Ins.

Другой удобный способ - создать BAT-файл с командами командной строки и уже его запускать. BAT-файл можно редактировать как текстовый файл, например в блокноте.

Добавлено через 1 минуту
Наиболее удобный способ - работать с BAT-файлом сидя в FAR Manager-е

Добавлено через 5 минут
Те кто начинали в MS-DOS под Norton Commander-ом или Волков-коммандером или DOS-Navigator-ом типично сидят в FAR-е, поскольку для них интерфейс Виндоуз - это только время терять. Почти что угодно в FAR-е делается в несколько раз быстрее. У интерфейса Виндоуз только одно преимущество - он интуитивно понятен людям, которые компьютер видят первый раз.

Добавлено через 2 минуты
А чистой командной строкой, получаемой по cmd.exe, пользуются только мазохисты или на чужом компе, на котором FAR-а нет.

Решение

Dimka-novitsek,
я расскажу как использую один единственный бат-файл для работы с разными ассемблерами (tasm, masm, fasm, nasm, goasm) и при этом создаю разные типы файлов COM, EXE for DOS, EXE for Windows, SYS, DLL при желании можете продлить этот список самостоятельно.
Внутри нашего asm.bat вставим процедуру, которая по трем первым словам, разделенным пробелами или табуляцией составит метку, на которую передаст управление внутри asm.bat и там уже из нашего asm-файла сделают необходимый нам файл-результат


Релокация — это этап процесса линковки, в рамках которого каждому внешнему символу присваиваются соответствующие адреса. Поскольку пакеты компилируются отдельно, они не имеют понятия, где функции или переменные из других пакетов находятся фактически. Начнем с тривиального примера, когда нам потребуется релокация.

Компиляция

Следующая программа задействует два разных пакета: main и fmt .


При построении этой программы первым отработает компилятор, который скомпилирует каждый пакет отдельно:


В этих промежуточных файлах мы можем увидеть временные адреса инструкций (с помощью команды go tool compile -S -l main.go ):

После того, как компиляция нашей программы будет завершена, мы можем посмотреть сгенерированный файл с помощью команды go tool compile -S -l main.go , которая отображает ассемблерный код.

У нас есть несколько вариантов, как посмотреть на сгенерированную компилятором инструкцию:

Представить результат компиляции в виде ассемблерного кода. Команда: go tool compile -S -l main.go :

Флаг -l используется для предотвращения инлайнинга, чтобы немного упростить нам задачу.

Сгенерированный ассемблерный код показывает, что инструкция для вызова Println расположена со смещением (offset) 88 байт от начала функции main. Это смещение нужно линкеру, чтобы правильно релоцировать вызов функции.

Дизассемблируйте сгенерированный main.o с помощью команды go tool objdump main.o :

Идентификатор R_CALL означает релокацию вызова.

Однако, поскольку функция принадлежит другому пакету, компилятор не знает, где на самом деле находится функция. Это можно подтвердить, проверив сгенерированный файл main.o и перечислив символы с помощью команды go tool nm main.o . Вот результат:


Вы могли заметить, что нужно использовать команду go tool nm вместо нативной команды nm . Это потому что объектный файл (.o), созданный Go, имеет собственный формат.

Символ U расшифровывается как undefined, что означает, что компилятор не знает, где находится этот символ. Этому символу необходима релокация, т. е. нужно найти адрес для успешного вызова Println , и именно здесь на сцену выходит линкер. Прежде чем переходить к линкеру, давайте проанализируем сгенерированный объектный файл main.o , чтобы понять, с какими данными приходится работать линкеру.

Объектный файл

Документация по объектному файлу хорошо объясняет его содержимое и формат:


Объектный файл (объектный модуль, object file) состоит из зависимостей, отладочной информации (DWARF), списка проиндексированных символов, раздела данных и, наконец, списка символов, в котором можно найти релокации. Вот его формат:


Каждый символ начинается с байта fe в шестнадцатеричном формате. Итак, давайте откроем наш объектный файл main.o с помощью шестнадцатеричного редактора, например xxd на Mac. Вот часть содержимого с выделенными символами:


Символ main.main - это первый символ в списке:


Первые байты 0102 00dc 0100 dc01 0a представляют первые атрибуты, охарактеризованные в определении: тип (type), флаг (flag), размер (size), данные (data), и количество релокаций.

Байты хранятся в формате zigzag (формат переменной длины varint). zigzag кодирует беззнаковые целые числа, используя младший бит для знака, делая их меньше по размеру.

Таким образом, релокация Println представляет собой последовательность байтов b201 0810 0008 :

b201 - это закодированное значение смещения (offset) - 89 . Это смещение является int32 , а благодаря формату varint оно может уместиться в двух байтах.

08 - количество байтов для перезаписи. Декодированное значение 4.

10 - это тип релокации, закодированное значение 8 представляет R_CALL , релокацию вызова функции.

08 - это ссылка на индексированные символы.

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

Релокация

Это этап, на котором линкер назначает виртуальные адреса всем разделам и инструкциям. Адреса каждого раздела можно увидеть с помощью команды objdump -h my-binary . Вот вывод для предыдущего примера:


Функция main находится в разделе __text . Его также можно найти с помощью команды objdump -d my-binary , которая отображает инструкцию с адресами:


Функции main назначен адрес 109cfa0 . Функция fmt.Println получила адрес 1096a00 . Как только виртуальные адреса назначены, совершить релокацию вызова fmt.Println становится легко. Линкер просто вычислит адрес fmt.Println из адреса main , смещения и размера инструкции, и мы получим глобальное смещение для вызова инструкции. В предыдущем примере мы получили бы следующую операцию: 1096a00 (fmt.Println) - 109cfa0 (main) - 84 (смещение внутри main) - 4 (размер) = -26109 .

Теперь инструкция знает, что функция fmt.Println расположена по смещению -26109 от текущего адреса памяти, и вызов будет успешным.

В статье вы научитесь создавать программу из нескольких файлов

Содержание

Объявления и определения

Рассмотрим задачу: надо вывести числа от N до 1, при этом для нечётных чисел надо писать odd $ , а для чётных even $ . Вывод должен выглядеть так:

Ради интереса решим задачу с помощью рекурсии:

Увы программа не компилируется. В C++ каждая функция должна быть объявлена или определена до первого использования, но в нашем случае printEvenRecursive использует printOddRecursive, а printOddRecursive использует printEvenRecursive! Мы не можем поместить определение одной функции выше другой так, чтобы каждая функция была объявлена перед использованием.

Но кроме определений функций в C++ есть объявления функций

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

Объявление функции похоже на определение функции, только вместо тела стоит точка с запятой:

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

Хорошим стилем считается заранее и в одном месте писать объявления всех функций, кроме main. Вы можете писать объявления в начале cpp-файла или в заголовке. О заголовках читайте ниже.

Пишем свой заголовок

  • Заголовок может иметь и другое расширение файла, но не стоит нарушать джентльменских соглашений: используйте h или hpp
  • В заголовке пишут только объявления функций, а все определения можно и нужно помещать в cpp-файл

Создайте каталог, и разместите в нём файл print.h , в котором будут объявления функций. Скопируйте туда код, приведённый ниже.

Мы могли бы убрать из заголовка printEvenRecursive и printOddRecursive, сделав его чище, но сейчас для примера оставим.

Запомните правила хорошего тона:

Теперь создайте файл print.cpp , в котором будут реализованы функции из заголовка print.h . Скопируйте туда код, расположенный ниже.

В конце создайте файл main.cpp и скопируйте в него код:

Компоновка программы из нескольких файлов

Собирая программу из одного файла с помощью g++, вы на деле выполняли одним махом два действия:

  • компиляцию, в ходе которой исходный текст файла превращается в логическую модель (AST) и затем превращается в объектный код, в котором машинные коды смешаны со ссылками на внешние функции
  • компоновку, в ходе все внешние ссылки на функции заменяются на машинный код либо превращаются в ссылки на динамические библиотеки (dll/so, также известны как shared libraries)

Сейчас эти же действия мы выполним раздельно. Отройте терминал и введите последовательно две команды:

Если код в обоих cpp-файлах синтаксически правилен, то компилятор создаст два файла: main.o и print.o . Эти файлы называют объектными файлами (object files). Именно они содержат машинный код, смешанный со ссылками на внешние функции.

Вы можете дизассемблировать эти файлы, чтобы посмотреть, во что компилятор превратил ваш код. Для этого выполните команду objdump -d main.o .

Теперь мы вызовем g++ для компоновки объектных файлов. На выходе мы получим исполняемый файл print_executable.exe

На деле компилятор не будет компоновать: он передаст эту задачу утилите ld. Вызывать утилиту ld вручную мы не станем, потому что потребуются дополнительные флаги, которые включают компоновку со стандартной библиотекой C++.

Компоновка программы в CMake

CMake прячет факт раздельной компиляции файлов. Чтобы в CMake скомпоновать программу, состоящую из нескольких частей, просто перечислите эти файлы в add_executable.

Удалите файл print_recursive.exe :

Создайте файл CMakeFiles.txt с одной строкой:

Теперь выполните конфигурирование и сборку программы:

Мы почти закончили! Остался только один вопрос: почему в add_executable мы указали заголовок print.h , если он всё равно не компилируется сам по себе? Дело в том, что при любых изменениях в коде заголовка print.h вся программа должна быть пересобрана, но файл print.h сам по себе не компилируется. Добавление print.h в список исходников в CMake позволяет CMake следить за датой и временем модификации заголовка, чтобы решить, надо ли повторно собирать проект из-за изменений в заголовках.

Читайте также: