GCC/X86, Проблемы с относительными прыжками
Я пытаюсь сделать относительный переход в сборке x86, но я не могу заставить его работать. Кажется, что по какой-то причине мой прыжок продолжает переписываться как абсолютный прыжок или что-то в этом роде.
Простая примерная программа для того, что я пытаюсь сделать, это следующее:
.global main
main:
jmp 0x4
ret
Так как команда jmp имеет длину 4 байта, а относительный скачок смещен от адреса прыжка + 1, это должно быть причудливым no-op. Однако компиляция и запуск этого кода вызовет ошибку сегментации.
Реальным головоломщиком для меня является то, что компиляция его на уровне объекта, а затем дизассемблирование объектного файла показывает, что похоже, что ассемблер правильно выполняет относительный переход, но после того, как файл компилируется, компоновщик меняет его на другой тип прыжка.
Например, если приведенный выше код был в файле asmtest.s:
$gcc -c asmtest.s
$objdump -D asmtest.o
... Some info from objdump
00000000 <main>:
0: e9 00 00 00 00 jmp 5 <main+0x5>
5: c3 ret
Похоже, что ассемблер правильно сделал относительный прыжок, хотя подозрительно, что команда jmp заполнена 0.
Затем я использовал gcc, чтобы связать его, затем разобрал его и получил следующее:
$gcc -o asmtest asmtest.o
$objdump -d asmtest
...Extra info and other disassembled functions
08048394 <main>:
8048394: e9 6b 7c fb f7 jmp 4 <_init-0x8048274>
8048399: c3 ret
Мне кажется, что компоновщик переписал оператор jmp или заменил 5 на другой адрес.
Итак, мой вопрос сводится к тому, что я делаю неправильно?
Я неправильно указываю смещение? Я не понимаю, как работают относительные прыжки? Gcc пытается убедиться, что я не делаю опасных вещей в своем коде?
Ответы
Ответ 1
Собственно, ассемблер думал, что вы пытаетесь совершить абсолютный прыжок. Однако код операции jmp
находится на уровне металла относительно. Следовательно, ассемблер не мог знать, что писать после байта 0xe9, потому что ассемблер не знает, по какому адресу ваш код закончится.
Ассемблер не знает, но компоновщик делает это. Поэтому ассемблер писал в заголовках asmtest.o
где-то запрос для компоновщика, что-то вроде этого: "когда вы знаете, на каком адресе код загрузится, настройте эти байты сразу после 0xe9, чтобы они были подходящими для переход от этой точки (с относительной адресацией) к абсолютному адресу" 4 ". Линкер сделал именно это. Он увидел, что 0xe9 был по адресу 0x08048394 и следующий код операции на 0x08048399, и он вычислил: перейти от 0x08048399 к 0x00000004, нужно вычесть 0x08048395, что эквивалентно добавлению (на 32-разрядных машинах) 0xf7fb7c6b. Следовательно, ваша последовательность" 6b 7c fb f7" в полученном двоичном файле.
Вы можете закодировать относительный прыжок "вручную" следующим образом:
.global main
main:
.byte 0xe9
.long 0x4
ret
Таким образом, ассемблер не заметит, что ваш 0xe9 действительно является jmp
, и он не попытается перехитрить вас. В двоичном коде вы получите желаемую последовательность "e9 04 00 00 00" и взаимодействие с линкером.
Обратите внимание, что приведенный выше код может быть сбой, поскольку относительное смещение подсчитывается с адреса сразу после смещения (т.е. адрес следующего кода операции, здесь ret
). Это скажется на 4 байтах без человека после ret
, а segfault или что-то странное кажется вероятным.
Ответ 2
Если вы используете ассемблер GCC GAS, который по умолчанию использует синтаксис AT & T, синтаксис относительной адресации использует точку ('.
') для представления текущего собираемого адреса (так же, как псевдосимвол $
используется в синтаксисе сборки Intel/MASM). Вы должны иметь возможность получить относительный прыжок, используя что-то вроде:
jmp . + 5
Например, следующая функция:
void foo(void)
{
__asm__ (
"jmp . + 5\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
);
}
Получает сборку:
71 0000 55 pushl %ebp
72 0001 89E5 movl %esp, %ebp
74 LM2:
75 /APP
76 0003 EB03 jmp . + 5
77 0005 90 nop
78 0006 90 nop
79 0007 90 nop
80 0008 90 nop
81 0009 90 nop
82
84 LM3:
85 /NO_APP
86 000a 5D popl %ebp
87 000b C3 ret
Ответ 3
Я думаю, что ассемблер принимает абсолютный адрес и вычисляет смещение адреса для вас. Нули в первом случае, вероятно, существуют потому, что часть таблицы fixup и смещение вычисляются в фазе ссылки.
Мои навыки ассемблерного языка немного ржавые, но я думаю, вы могли бы просто сделать это:
.global main
main:
jmp getouttahere
getouttahere:
ret
Или, если вы действительно хотите, чтобы он выглядел относительно:
.global main
main:
jmp .+5
ret
Пожалуйста, будьте осторожны, если я ошибаюсь; это было долгое время.