Я пытаюсь сделать относительный переход в x86 блоке, однако я не могу заставить его работать. Кажется, что по некоторым причинам мой переход продолжает переписываться как переход в абсолютных адресах или что-то.
Простой пример программы для того, что я пытаюсь сделать, является этим:
.global main
main:
jmp 0x4
ret
Так как jmp инструкция 4 байта длиной, и относительный переход смещается от адреса перехода + 1, это должно быть воображением нет. Однако компиляция и выполнение этого кода вызовут отказ сегментации.
Реальный трудный вопрос для меня - то, что компиляция его к уровню объектов и затем разборка объектного файла показывают, что похоже, что ассемблер правильно делает относительный переход, но после того, как файл компилируется, компоновщик изменяет его в другой тип перехода.
Например, если вышеупомянутый код был в файле, названном 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 инструкция заполнена 0s.
Я затем использовал 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 пытается удостовериться, что я не делаю опасных вещей в своем коде?
На самом деле, ассемблер подумал, что вы пытаетесь сделать абсолютный переход. Однако опкод 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 00', и никакого взаимодействия с компоновщиком.
Обратите внимание, что приведенный выше код может завершиться аварийно, поскольку относительное смещение отсчитывается от адреса сразу после смещения (т.е. адреса следующего опкода, здесь ret
). Это приведет к прыжку в "ничейную землю" через 4 байта после ret
, и сегфаулт или что-то странное кажется вероятным.
Если вы используете ассемблер GCC GCC, который по умолчанию использует синтаксис 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
Я думаю, что ассемблер берет абсолютный адрес и вычисляет смещение адреса для вас. Нули в первом случае, вероятно, потому, что это часть таблицы исправления, а смещение вычисляется на этапе соединения.
Мои знания языка ассемблера немного подпортились, но я думаю, что вы можете сделать вот так:
.global main
main:
jmp getouttahere
getouttahere:
ret
Или, если вы действительно хотите, чтобы это выглядело относительно:
.global main
main:
jmp .+5
ret
Пожалуйста, будьте нежны, если я ошибаюсь; прошло много времени.