2009-10-30 4 views
16

Я заинтересован в написании ассемблера x86 для проекта хобби.Каков наилучший способ написать простой ассемблер x86?

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

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

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

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

+1

Это напоминает мне о тесте программист с 1980-х годов, учитывая дискету с только command.com и debug.com на него, какой тип среды разработки будет создавать для себя. Я знаю, как ответили ребята из Форта. – zumalifeguard

ответ

1

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

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

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

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

+0

Если вы прочитали мой вопрос, это не мой первый компилятор. Раньше я использовал Lex/Yacc, и у меня есть общее понимание ANTLR. Кажется, много ресурсов в Интернете и даже на SO предлагают использовать Ad-hoc-синтаксический анализ при написании ассемблера. Ты согласен или несогласен? – mmcdole

+0

Хорошо, я думаю, что не совсем понял, о чем вы просите. Хотя, если вы написали lexer/parser для C-подобного языка, ассемблер x86 должен быть cinch. На первый взгляд я бы сказал, что узлы AST будут содержать метаданные для каждого примитива, такие как смещение байтов, ссылки на символы и т. Д., Например. оператор ветки ссылается на узел метки, который содержит его смещение. – spoulson

6

Возможно, вам понадобится dragon book.

Фактическое название: Compilers: Principles, Techniques, and Tools (amazon.com).

Заполните Intel Architectures Software Developer's Manuals для полной документации на наборы инструкций IA-32 и IA-64.

AMD's architecture technical documents можно найти на своем веб-сайте.

Linkers and Loaders (amazon.com) - хорошее введение в форматы объектов и проблемы с связями. (unedited original manuscript также доступен онлайн.)

+6

Хотя я уважаю книгу Дракона как окончательный текст компиляторов, я не думаю, что это очень полезно при написании ассемблера. Проблемы с анализом, связанные с ассемблерами, сильно отличаются от реальных компиляторов, а генерация кода - это, по сути, операторы ассемблера no-op —, которые сопоставляются друг с другом машинным инструкциям. –

+0

Даже не компиляторы. Это больше парсеров. Остальные части получают одну главу max. –

1

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

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

2

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

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

JMP some_label 
    .. code here 
some_label: 

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

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

+0

Один проход в порядке. См. Мой ответ. –

1

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

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

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

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

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

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

0

Я написал пару парсеров. Я написал пару парсеров, сделанных вручную, и я также попытался использовать парные анализаторы yacc ....

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

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

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

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

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

1

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

4

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

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

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

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

+0

Ну, если это просто игрушечный проект, ему может не понадобиться поддерживать все, что должен сделать современный ассемблер. Более того, мы не знаем (поскольку он не говорит), если одна из областей, которые OP может улучшить, - это синтаксический анализ. –

+0

Проблема заключается в том, что ассемблеры x86 имеют грязный синтаксис для операндов команд. Генератор парсеров, особенно для хобби, имеет смысл; не так много узнать об ассемблере. –

Смежные вопросы