Относительный vs абсолютный jmp в сборке

Мне было интересно следующее. Я заметил при написании ассемблера, что IA32 разработан таким образом, чтобы способствовать использованию относительных прыжков, т. е. прыгать количество байтов смещения против использования абсолютных прыжков, т. е. изменять eip на определенный адрес в памяти. Какова логика этого?

2 ответов


большинство прыжков к целям не далеко от инструкции прыжка. Поскольку инструкции перехода предоставляются, которые принимают подписанное 16-битное значение, они могут быть меньше байтов, чем необходимо для абсолютного перехода (обычно 4 байта плюс сама инструкция).

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


относительные скачки позволяют компиляторам генерировать мобильные код, который означает, что код будет работать в любом месте в памяти; он не привязан к фиксированной точке. Это критическая концепция библиотек: вы можете написать код один раз и скомпилировать его в relocatable объект файл, который может быть связан как-в любую программу. Компоновщику необходимо назначить абсолютные адреса только для функций, доступных извне (чтобы ваш собственный код мог найти они); все "внутренние" прыжки относительны и не должны меняться от одного исполняемого файла к другому.

это стоит вашего времени, чтобы написать некоторый код на языке высокого уровня, как C, компилятор генерировать код сборки (проверьте -S опция для gcc), а затем прочитайте выходные данные сборки. Обратите особое внимание на условные обозначения и петли, такие как if, for и while, и вы увидите, что все они генерируют относительные прыжки.

вот надуманный пример использование фиктивных инструкций по сборке:

// Sample C code         Address  Assembly Code       Comments

if (x < 10) {            0000     CMP x,#10           ; Compare x to 10
   do_true_stuff();      0003     JGE +7              ; Skip 7 bytes to 000A if x>=10
} else {                 0005     SUB do_true_stuff   ; x<10
   do_false_stuff();     0008     JMP +5              ; Skip 5 bytes to 000D
}                        000A     SUB do_false_stuff  ; else-branch (x>=10)
do_more_stuff();         000D     SUB do_more_stuff   ; Finished if/else

здесь компилятор генерирует относительные скачки (JGE и JMP), чтобы пропустить неисполненную ветку if-else блок. Эти скачки будут работать независимо от того, где в памяти компоновщик помещает код. Если бы они были абсолютными скачками, компоновщику пришлось бы пересчитывать адреса каждый раз, когда он связывал код.

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

надеюсь, что это поможет!