Ответ 1
Существует отличная бесплатная электронная книга (pdf) о том, как построить сборщики и загрузчики Дэвида Саломона. Вы можете найти его по адресу:
Мне интересно написать ассемблер x86 для хобби.
Сначала мне показалось довольно прямолинейным, но чем больше я читал в нем, тем больше вопросов остались без ответа. Я не совсем неопытен: я использовал сборку MIP, и я написал игрушечный компилятор для подмножества C в школе.
Моя цель - написать простой, но функциональный ассемблер x86. Я не собираюсь делать коммерчески жизнеспособный ассемблер, а просто хобби-проект, чтобы укрепить мои знания в определенных областях. Поэтому я не возражаю, если я не реализую все доступные функции и операции.
У меня есть много вопросов, таких как: следует ли использовать однопроходный или двухпроходный метод? Должен ли я использовать ad-hoc для синтаксического анализа или определять формальные грамматики и использовать синтаксический анализатор для моих инструкций? На какой стадии и как мне разрешать адреса моих символов?
Учитывая мои требования, может ли кто-нибудь предложить некоторые общие рекомендации по методам, которые я должен использовать в своем ассемблере домашних животных?
Существует отличная бесплатная электронная книга (pdf) о том, как построить сборщики и загрузчики Дэвида Саломона. Вы можете найти его по адресу:
Вы можете найти книгу драконов.
Фактическое название Составители: принципы, методы и инструменты (amazon.com).
Ознакомьтесь с Руководствами разработчиков программного обеспечения Intel Architectures для полной документации наборов инструкций IA-32 и IA-64.
Технические документы архитектуры AMD доступны на его веб-сайте.
Линкеры и загрузчики (amazon.com) - это хорошее введение в форматы объектов и проблемы с связыванием. (неотредактированная оригинальная рукопись также доступна онлайн.)
В то время как многие люди предлагают ad hoc parsers, я думаю, что в эти дни нужно использовать генератор парсера, потому что это действительно упрощает проблему построения всего сложного синтаксиса, необходимого для интересного современного ассемблера. См. Мой пример BNF/ответ на fooobar.com/questions/338116/....
"Один проход" против "Два прохода" означает, что вы дважды читаете исходный код. Вы всегда можете сделать ассемблер с одним проходом. Здесь два пути:
1) Генерировать бинарные результаты "на лету" (подумайте об этом как о парных темах, которые имеют тенденцию иметь монотонно возрастающие адреса), и испускают обратные исправления как исправления, когда вы найдете информацию, которая позволяет вам разрешать прямые ссылки (подумайте об этом как просто пары, где адреса используются для перезаписывания ранее выпущенных местоположений). Для JMP необходимо зафиксировать тип/размер кода операции JMP, когда вы его встретите. Значение по умолчанию может быть коротким или длинным в зависимости от вкуса или даже варианта ассемблера. Небольшой бит синтаксиса, введенный кодером, говорящий "использовать другой вид" или "я настаиваю на этом виде", достаточно (например, " JMP long target" ) для обработки тех случаев, когда выбор по умолчанию для ассемблера неправильно. (Это ассемблер, его ОК, чтобы иметь фанковые правила).
2) На (первом) проходе генерируют данные в буферы в памяти. Стандартные JMP (и другие зависимые от диапазона инструкции) для коротких смещений. Запишите местоположения всех JMP (инструкции, зависящие от диапазона и т.д.). В конце этого прохода вернитесь к JMP и переработайте те, которые "слишком коротки", чтобы быть длиннее; перетасовать код и настроить другие JMP. Уловкой схемой для этого и достижения почти оптимальных наборов коротких JMP является документ в этой статье 1978 года: Сборка кода для машин с зависимыми от диапазона инструкциями/Szymanski
Чтобы ответить на один из ваших вопросов, один проход не является жизнеспособным, если вы не испускаете код после прохождения.
Представьте себе следующее:
JMP some_label
.. code here
some_label:
что вы испускаете в качестве значения расстояния для инструкции JMP? Какую инструкцию JMP вы испускаете, те, которые требуют близкого значения, или ярлык далеко?
Таким образом, два прохода должны быть минимальными.
Вам нужно будет написать лексер и парсер для чтения в исходном коде и вывода абстрактного синтаксического дерева (AST). Затем AST может быть пройден для генерации вывода байтового кода.
Я рекомендую исследовать книги по написанию компилятора. Обычно это класс колледжа, поэтому должно быть много книг. Извините, я не могу рекомендовать его в частности.
Вы также можете прочитать инструмент ANTLR. Он может принимать правила грамматики и код вывода на разных языках, чтобы выполнить работу с лексиром/парсером.
В однопроходном или двухпроходном режиме вам понадобится двухпроходный компилятор для разрешения прямых ссылок. Если это не важно, то один проход пройдет. Я рекомендую вам сохранить его простым, поскольку это ваш первый компилятор.
Учитывая, что это хобби-проект, многие ваши вопросы действительно сводятся к тому, "какие аспекты проблемы вам больше всего интересны в изучении и изучении?" Если вам интересно узнать, как инструменты синтаксического анализа сопоставляются с проблемой ассемблеров (особенно, когда речь идет о макрообработке и т.п.), Вы должны их использовать. С другой стороны, если вы не слишком заинтересованы в этих вопросах и просто хотите заняться вопросами упаковки и компоновки инструкций и довольствуетесь минимальным ассемблером без макросов, то быстрый и грязный для синтаксического анализа, вероятно, является путь.
Для однопроходных и многопроходных - вы заинтересованы в том, чтобы играть с быстрым сборщиком с минимальным размером памяти? Если это так, этот вопрос становится актуальным. Если нет, просто разложите всю программу в память, обработайте ее там, создав изображение объекта в памяти, а затем напишите это. Не нужно беспокоиться о "проходах" как таковых. В этой модели вы можете более легко поиграть с делами в разных порядках, чтобы увидеть, что такое компромиссы, что в значительной степени связано с хобби.
Я часто фантазировал о попытке построить (еще один) компьютер высокого уровня. Целью было бы попытаться надавить на конверт быстроты развития и на результат результата. Я бы попытался создать библиотеки как минимум минимальных, довольно высоко оптимизированных операций, а затем попытаться разработать языковые правила таким образом, чтобы любое выражение или выражение, выражаемое на языке, приводило к оптимальному коду.., если бы не было выражено просто изначально субоптимальный.
Он будет компилировать байтовый код, который будет распространен, а затем машинный код при установке или когда изменится среда процессора. Поэтому, когда исполняемый файл загружен, будет существовать кусок загрузчика, который будет проверять процессор и несколько байтов данных управления в объекте, а если они совпадают, то исполняемая часть объекта может быть загружена сразу, но если нет, тогда байт-код для этого объекта должен быть перекомпилирован, а исполняемая часть обновлена. (Так что это не Just In Time компиляция - это On Program Install или на CPU Измененная компиляция.) Часть загрузчика была бы очень короткой и сладкой, она была бы в коде 386, поэтому ее не нужно было компилировать. Он загрузил бы только компилятор байтового кода, если бы это было необходимо, и если да, то он загрузил бы объект компилятора, который был бы небольшим и плотным, и оптимизирован для обнаруженной архитектуры. В идеале загрузчик и компилятор останутся постоянными, после загрузки, и будет только один экземпляр обоих.
В любом случае, я хотел ответить на мысль, что у вас должно быть как минимум два прохода - я не думаю, что вполне согласен. Да, я бы использовал второй проход через скомпилированный код, но не через исходный код.
Что вы делаете, когда вы сталкиваетесь с символом, проверьте свою хэш-таблицу символов, и если там нет записи, создайте ее и сохраните маркер "прямой ссылки" в скомпилированном коде с указателем на запись таблицы, Когда вы сталкиваетесь с определениями меток и символов, обновите (или поместите новые данные) в свою таблицу символов.
Индивидуальные скомпилированные объекты никогда не должны быть настолько большими, чтобы они занимали очень много памяти, поэтому, безусловно, весь скомпилированный код должен храниться в памяти, пока все не будет готово к выписке. То, как вы сохраняете свой малый размер памяти, - это просто иметь дело только с одним объектом за раз, и никогда не оставлять в памяти не более одного небольшого буфера, заполненного исходным кодом. Может быть, 64k или 128k или что-то еще. (Что-то достаточно большое, чтобы накладные расходы, связанные с выполнением вызова для загрузки буфера с диска, были небольшими по сравнению с временем, затрачиваемым на чтение данных с диска, так что оптимизация потоковой передачи.)
Итак, один проход через исходный поток для объекта, затем вы соединяете свои фрагменты вместе, собирая необходимую информацию о перенаправлении из хэш-таблицы по ходу дела, а если данных нет - это ошибка компиляции. В этом процессе у меня возникнет соблазн попробовать.
Возьмите таблицы NASM и попробуйте выполнить более основные инструкции, используя таблицы для декодирования
Я написал пару парсеров. Я написал пару парсеров, сделанных вручную, и я тоже пробовал синтаксический анализатор yacc...
Ручные парсеры обеспечивают большую гибкость. Yacc предоставляет фреймворк, который нужно адаптировать или терпеть неудачу. Анализатор Yacc по умолчанию дает быстрый парсер, но после сдвига/уменьшения и уменьшения/уменьшения может потребоваться довольно много усилий, если вы не знакомы с одним из этих средств, а среда анализатора не является Лучший. О преимуществе Yacc. Это дает вам систему, если вам это нужно. Ручной парсер дает вам свободу, но можете ли вы его использовать? Язык сборки кажется достаточно простым для обработки yacc или аналогичных парсеров.
Мой обработчик ручной работы будет содержать токенизатор/лексер, и я бы прошел через массив токенов с циклом for и выполнил какую-либо обработку событий, разместив if или case-оператор в цикле и рассмотрев текущий токен или следующий/предыдущий, Возможно, я бы использовал отдельный синтаксический анализатор для выражений... Я бы поставил код перевода в массив строк и "зачеркнул" нерасчетные части переведенного кода, чтобы программа могла прийти к ним позже и заполнить пробелы. Там могут быть пробелы, и не все известно заранее, когда вы разбираете код. Например. расположение прыжков.
С другой стороны, независимо от того, как вы впервые выполняете свой синтаксический анализатор, и у вас есть время, вы можете преобразовать ваш синтаксический анализатор из одного типа в другой. В зависимости от того, кем вы являетесь, вам может даже понравиться.
Существуют другие синтаксические анализаторы, чем Yacc, и они обещают большую гибкость и меньше "ошибок", но это не означает, что вы не получаете ошибок, они не будут настолько заметны, и это может быть не так быстро. Если это важно.
Кстати, если бы были сохранены жетоны, то даже можно было бы подумать о смешанном анализе yacc и hand-made.