Подтвердить что ты не робот

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 пытается убедиться, что я не делаю опасных вещей в своем коде?

4b9b3361

Ответ 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

Пожалуйста, будьте осторожны, если я ошибаюсь; это было долгое время.