Как лучше написать простой ассемблер для x86? - PullRequest
16 голосов
/ 30 октября 2009

Я заинтересован в написании ассемблера x86 для хобби-проекта.

Поначалу мне это показалось довольно простым, но чем больше я читал об этом, тем больше вопросов оставалось без ответа. Я не совсем неопытен: я довольно много использовал сборку MIP и написал игрушечный компилятор для подмножества языка C в школе.

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

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

Учитывая мои требования, кто-нибудь может предложить некоторые общие рекомендации по методам, которые я должен использовать в моем ассемблере для pet-проекта?

Ответы [ 9 ]

7 голосов
/ 10 ноября 2009

Дэвид Саломон (David Salomon) предлагает отличную бесплатную электронную книгу (pdf) по сборке и загрузке. Вы можете найти его по адресу:

http://www.e -booksdirectory.com / details.php? Книга = 1311

6 голосов
/ 30 октября 2009

Вам может пригодиться книга дракона .

Фактическое название: Компиляторы: принципы, методы и инструменты ( amazon.com ).

Ознакомьтесь с Руководствами разработчика программного обеспечения Intel Architectures для получения полной документации по наборам команд IA-32 и IA-64.

Технические документы AMD по архитектуре также доступны на ее веб-сайте.

Компоновщики и загрузчики ( amazon.com ) - хорошее введение в форматы объектов и проблемы связывания. ( неотредактированная оригинальная рукопись также доступна онлайн.)

4 голосов
/ 07 ноября 2009

Хотя многие люди предлагают специальные анализаторы, я думаю, что в наши дни следует использовать генератор синтаксических анализаторов, потому что это действительно упрощает задачу построения всего сложного синтаксиса, который нужен интересному современному ассемблеру. Смотрите мой пример BNF / ответ на StackOverflow: Z80 ASM BNF .

«Один проход» против «Два прохода» относится к тому, читали ли вы сам исходный код дважды. Вы всегда можете сделать один проход ассемблера. Вот два способа:

1) Создайте двоичные результаты на лету (представьте, что они представляют собой пары в аннотации, имеющие тенденцию к монотонно увеличивающимся адресам) и генерируют обратные исправления в виде исправлений, когда вы находите информацию, которая позволяет разрешать прямые ссылки (воспринимайте их как просто пары, где адреса используются для перезаписи ранее отправленных местоположений). Для JMP передайте тип / размер кода операции JMP, когда вы его встретите. Значение по умолчанию может быть коротким или длинным в зависимости от вкуса или даже опции ассемблера. Достаточно небольшого фрагмента синтаксиса, введенного кодером со словами «использовать другой вид» или «я настаиваю на этом виде» (например, « JMP long target») для обработки тех случаев, когда выбор ассемблера по умолчанию равен неправильно. (Это ассемблер, все в порядке, чтобы иметь прикольные правила).

2) На первом проходе сгенерируйте данные для буферов в памяти. JMP по умолчанию (и другие зависящие от диапазона инструкции) для коротких смещений. Запишите расположение всех JMP (инструкции, зависящие от диапазона и т. Д.). В конце этого прохода вернитесь к JMP и пересмотрите те, которые «слишком короткие», чтобы быть длиннее; перемешайте код и настройте другие JMP. Умная схема для этого и достижения почти оптимальных наборов коротких JMP-документов приведена в документе 1978 года: Сборка кода для машин с инструкциями, зависящими от диапазона / Szymanski

2 голосов
/ 30 октября 2009

Чтобы ответить на один из ваших вопросов, однопроходный переход недействителен, если вы не отправите код после пропуска.

Представьте себе это:

  JMP some_label
  .. code here
some_label:

что вы выводите в качестве значения расстояния для инструкции JMP? Какую инструкцию JMP вы отправляете, ту, которая требует близкого значения, или метка далеко?

Таким образом, два прохода должны быть минимумом.

1 голос
/ 06 ноября 2009

Взять таблицы NASM и попытаться реализовать более простые инструкции, используя таблицы для декодирования

1 голос
/ 31 октября 2009

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

Он будет компилироваться в байт-код, который будет распространяться, а затем в машинный код при установке или при изменении среды процессора. Поэтому, когда исполняемый файл загружен, будет часть загрузчика, которая проверит процессор и несколько байтов управляющих данных в объекте, и если два совпадения, то исполняемая часть объекта может быть загружена сразу, но если нет Затем байт-код для этого объекта должен быть перекомпилирован, а исполняемая часть обновлена. (Таким образом, это не компиляция Just In Time - это при установке программы или компиляции с измененным ЦП.) Часть загрузчика будет очень короткой и приятной, она будет в коде 386, поэтому ее не нужно будет компилировать. Он будет загружать компилятор байт-кода только в случае необходимости, и если это так, он будет загружать объект компилятора, который был маленьким и компактным и оптимизирован для обнаруженной архитектуры. В идеале загрузчик и компилятор должны оставаться резидентными после загрузки, и будет только один экземпляр обоих.

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

Что вы делаете, когда вы сталкиваетесь с символом, проверяете вашу хеш-таблицу символов, и, если там нет записи, создайте ее и сохраните маркер «прямой ссылки» в скомпилированном коде с указателем на запись таблицы. , Когда вы встретите определения для меток и символов, обновите (или добавьте новые данные) свою таблицу символов.

Отдельные скомпилированные объекты никогда не должны быть такими большими, чтобы они занимали очень много памяти, поэтому, безусловно, весь скомпилированный код должен храниться в памяти до тех пор, пока все это не будет готово для записи. То, как вы сохраняете свой отпечаток памяти небольшим, заключается в простом обращении только с одним объектом за раз, и при этом никогда не храните в памяти более одного небольшого буфера, заполненного исходным кодом. Может быть, 64 КБ или 128 КБ или что-то. (Что-то настолько большое, что накладные расходы, связанные с выполнением вызова для загрузки буфера с диска, невелики по сравнению со временем, которое требуется для чтения данных с диска, чтобы оптимизировать потоковую передачу.)

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

1 голос
/ 30 октября 2009

Учитывая, что это хобби-проект, многие ваши вопросы сводятся к тому, «на какие аспекты проблемы вы больше всего обращаете внимание и изучаете?» Если вам интересно узнать, как инструменты синтаксического анализа соответствуют проблеме ассемблеров (особенно в том, что касается обработки макросов и т. П.), Вы должны их использовать. С другой стороны, если вы не слишком заинтересованы в этих вопросах и просто хотите заняться вопросами упаковки и размещения инструкций и хотите иметь минимальный ассемблер без макросов, то, вероятно, быстрый и грязный анализ путь.

Для однопроходного или многопроходного режима - вам интересно играть с очень быстрым ассемблером с минимальным использованием памяти? Если это так, этот вопрос становится актуальным. Если нет, просто забросьте всю программу в память, разберитесь с ней, создав изображение объекта в памяти, а затем запишите это. Нет необходимости беспокоиться о «пропусках» как таковых. В этой модели вам будет проще поиграть с разными порядками, чтобы увидеть, каковы компромиссы, что является главной целью хобби-проекта.

1 голос
/ 30 октября 2009

Вам потребуется написать лексер и парсер для чтения в исходном коде и вывода абстрактного синтаксического дерева (AST). Затем можно пройти AST для генерации вывода байтового кода.

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

Вы также можете прочитать об инструменте ANTLR . Для работы лексера / парсера могут потребоваться грамматические правила и код вывода на разных языках.

Для однопроходного или двухпроходного: вам понадобится двухпроходный компилятор для разрешения прямых ссылок. Если это не важно, тогда подойдет один проход. Я рекомендую вам сделать это простым, поскольку это ваш первый компилятор.

0 голосов
/ 31 октября 2009

Я написал пару парсеров. Я написал пару парсеров ручной работы и тоже попробовал парсеры типа yacc ....

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

Мой парсер, созданный вручную, будет содержать токенизатор / лексер, и я буду проходить через массив токенов с циклом for и выполнять некоторую обработку событий, помещая операторы if или case в цикл и проверяя текущий токен или следующий / предыдущий , Возможно, я бы использовал отдельный синтаксический анализатор для выражений ... Я бы поместил код перевода в массив строк и «записал» невычисленные части переведенного кода, чтобы программа могла прийти к ним позже и заполнить пробелы. Могут быть пробелы, и не все заранее известно, когда кто-то анализирует код. Например. расположение прыжков.

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

Существуют и другие синтаксические анализаторы, чем Yacc, и они обещают большую гибкость и меньше «ошибок», но это не значит, что вы не получите ошибок, они не будут такими заметными и могут быть не такими быстрыми. Если это важно.

Кстати, если бы токены были сохранены, можно даже подумать о смешанном парсере yacc и hand-made.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...