Требуется ли 2 прохода исходного файла для ассемблера и компоновщика? - PullRequest
1 голос
/ 30 августа 2011

Я много раз слышал, что ассемблер и компоновщик должны проходить через входной файл как минимум 2 раза, действительно ли это необходимо?Почему это нельзя сделать за один проход?

Ответы [ 3 ]

2 голосов
/ 30 августа 2011

Ассемблер переводит символический язык ассемблера в двоичное представление.

На языке ввода (ассемблере) метки также являются символическими.

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

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

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

Возможен однопроходный ассемблер, но вы сможете перейти только к тем меткам, которые вы уже объявили ("bacK"), а не вперед.

1 голос
/ 31 августа 2011

это может занять даже больше двух.

here:
  ...
  jmp outside
  ...
  jmp there
  ...
  jmp here
  ...
there:

В частности, для наборов команд, которые имеют некоторую форму ближнего прыжка и некоторую форму дальнего прыжка.Ассемблер не всегда хочет тратить дальний прыжок на каждую ветку / jmp.Возьмите приведенный выше код, например, когда он попадает в строку jmp here, он знает, сколько инструкций находится между меткой here и инструкцией jump to here.он может дать довольно хорошую оценку, если потребуется кодировать его как ближний или дальний скачок.Обычно дальний вариант перехода - это случай, когда для реализации требуется больше байтов, вызывая смещение всех инструкций и меток, следующих за ним.

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

Допустим, переход за пределы не разрешаетсяэтикетка.Теперь, в зависимости от набора инструкций, ассемблер должен ответить.Некоторые наборы команд, скажем, msp430, где дальний переход просто означает абсолютный адрес в памяти, весь объем памяти, без сегментов или ничего подобного.Ну, вы могли бы просто выполнить дальний переход и оставить адрес для компоновщика, чтобы заполнить его позже.В некоторых наборах команд, таких как ARM, вы должны выделить немного памяти в пределах досягаемости инструкции.часто прячется за безусловными ветками (это может быть плохо и потерпеть неудачу).По сути, вам нужно выделить место, где можно ссылаться на весь адрес внешнего элемента, закодировать инструкцию для загрузки из этого места рядом с памятью и позволить компоновщику заполнить адрес позже.

Назад туда-сюда,Что если на первом проходе вы предположили, что все неизвестные переходы были рядом, и на первом проходе вычислили адреса, основанные на этом.И если на этом проходе здесь было ровно 128 байтов от инструкции jmp here для набора инструкций, который имеет только 128 байтов.Таким образом, вы предполагаете, что jmp здесь тоже рядом, и сделать это болезненно, что если, если там был найден прыжок, то было 127 байтов, что было вашим максимальным прыжком вперед.Но снаружи не найдено!он должен быть далеко, поэтому вам нужно сжечь еще несколько байтов, теперь здесь для jmp здесь слишком далеко, ему нужно больше байтов, теперь для jmp слишком далеко и должно быть больше байтов.Сколько проходов данных потребовалось, чтобы понять эти три вещи?Более, чем два.Один проход, чтобы начать.второй проход отмечается снаружи как далеко, предположение имеет jmp там как близко на втором проходе, когда он достигает jmp здесь, он обнаруживает, что должен быть дальний прыжок, вызывающий изменение адреса там.На третьем проходе обнаруживается, что jmp должен быть далеко, и это влияет на все после этой инструкции.Для этого простого кода это все решено.

Подумайте о пузырьковой сортировке.Вы продолжаете циклически перебирать данные, делая перестановки, пока у вас не появится флаг, который говорит: «Я не внес никаких изменений в этот последний проход, показывая, что все решено, мы закончили».Вы должны играть в ту же игру с ассемблером.Для наборов команд, таких как ARM, вам нужно делать такие вещи, как попытаться найти места, где можно убрать адреса и константы / непосредственные объекты, которые не кодируются в одну инструкцию.Это если ассемблер хочет сделать эту работу за вас.Вы можете легко объявить об ошибке и сказать, что пункт назначения находится слишком далеко для выбранной инструкции.Сборщики манипуляторов позволяют вам быть ленивыми и делать такие вещи, как:

  ldr r0,=0x1234567
  ...
  ldr r1,=lab7
  ...
lab7:

Ассемблер смотрит на это = и знает, что он должен определить, могу ли я закодировать эту константу / немедленно в инструкции (изменяя ваш ldrmov для вас) или мне нужно найти место, заклинившее в вашем коде, чтобы поместить слово, а затем закодировать инструкцию с помощьюблизкое смещение адреса.

Даже не имея дело с ближним и дальним, просто разрешая адреса, снаружи, там, здесь, пример выше, занимает два прохода. Первый проход читает все, прыжок здесь, случается, знает, где здесь находится на первом проходе. но вы должны сделать второй проход через программу (не обязательно с диска, можете сохранить информацию в памяти), возможно, здесь есть переход, который предшествует метке here :. второй проход найдет переход снаружи и узнает, что в программе нет внешней метки, помечающей его на втором проходе как неразрешенный или внешний в зависимости от правил ассемблера. Второй шаг разрешает переход туда как известный ярлык, а второй проход не связывает скачок здесь, потому что он разрешил его первый проход Это ваша классическая двухпроходная задача / решение.

У компоновщика та же проблема: он должен пройти через все источники, представить каждый объект как сложную строку в исходном коде. он находит все метки, как найденные в объектах, так и не разрешенные в объекте. Если он находит нужную «внешнюю» метку во втором файле из 10 файлов, он должен пройти через все 10 файлов, а затем вернуться к данным либо в файле, либо в памяти, чтобы разрешить все метки с прямой ссылкой. При первом появлении jmp outside он не будет знать, что не было внешней метки, при втором проходе, когда он находит jmp снаружи, просматривает список, в котором хранятся найденные метки (что можно считать третьим проходом), не находит снаружи пометить и объявить ошибку.

1 голос
/ 30 августа 2011

Один из примеров, когда это необходимо, - это когда две функции вызывают друг друга.

int sub_a(int v);
int sub_b(int v);

int sub_a(int v) {
    int u = v;
    if ( 0 < u ) {
        u = sub_b( v - 1 );
    }
    return u - 1;
}

int sub_b(int v) {
    int u = v;
    if ( 0 < u ) {
        u = sub_a( v - 1 );
    }
    return u - 1;
}

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

...