Сборка NASM x86_64 в 32-разрядном режиме. Почему эта инструкция создает код относительной адресации RIP? - PullRequest
3 голосов
/ 03 апреля 2012
[bits 32]
    global _start

    section .data
    str_hello       db  "HelloWorld", 0xa
    str_hello_length    db      $-str_hello

    section .text

    _start:

        mov ebx, 1              ; stdout file descriptor
        mov ecx, str_hello      ; pointer to string of characters that will be displayed        
        mov edx, [str_hello_length] ; count outputs Relative addressing
        mov eax, 4              ; sys_write
        int 0x80                ; linux kernel system call

        mov ebx, 0  ; exit status zero
        mov eax, 1  ; sys_exit
        int 0x80    ; linux kernel system call

Фундаментальная вещь здесь заключается в том, что мне нужно иметь длину строки приветствия для передачи в системный вызов linux sys_write. Теперь я хорошо знаю, что могу просто использовать EQU, и он будет работать нормально, но я действительно пытаюсь понять, что здесь происходит.

Итак, в основном, когда я использую EQU, он загружает значение, и это нормально.

str_hello_length equ $-str_hello
...
...
mov edx, str_hello_length

Однако, если я использую эту строку с БД

str_hello_length db $-str_hello
...
...
mov edx, [str_hello_length]     ; of course, without the brackets it'll load the address, which I don't want. I want the value stored at that address

вместо загрузки значения по этому адресу, как я ожидал, ассемблер выводит RIP-Relative Addressing, как показано в отладчике gdb, и мне просто интересно, почему.

mov    0x6000e5(%rip),%edx        # 0xa001a5

Теперь я попытался использовать вместо этого регистр eax (а затем переместить eax в edx), но затем у меня возникла другая проблема. Я получаю ошибку сегментации, как указано в GDB:

movabs 0x4b8c289006000e5,%eax

так что, очевидно, разные регистры производят разный код. Думаю, мне нужно каким-то образом обрезать верхние 32-битные символы, но я не знаю, как это сделать.

Хотя вроде как нашел «решение» и оно выглядит так: загрузите eax с адресом str_hello_length, а затем загрузите содержимое адреса, на который указывает eax, и все будет просто ужасно.

mov eax, str_hello_length       
mov edx, [eax]  ; count


; gdb disassembly
mov    $0x6000e5,%eax
mov    (%rax),%edx

очевидно, попытка косвенной загрузки значения из адреса mem дает другой код? Я действительно не знаю.

Мне просто нужна помощь в понимании синтаксиса и операций этих инструкций, чтобы я мог лучше понять, почему, как загружать эффективные адреса. Да, я полагаю, что мог бы просто переключиться на EQU и быть на моем веселом пути, но я действительно чувствую, что не могу продолжать, пока не пойму, что происходит с объявлением DB и загрузкой с его адреса.

Ответы [ 3 ]

6 голосов
/ 03 апреля 2012

Ответ - нет. x86-64 не имеет RIP-относительной адресации в режиме 32-битной эмуляции (это должно быть очевидно, потому что RIP не существует в 32-битной). Происходит то, что nasm компилирует вам несколько прекрасных 32-битных кодов операций, которые вы пытаетесь использовать как 64-битные. GDB разбирает ваши 32-битные коды операций как 64-битные и сообщает вам, что в 64-битных байтах эти значения означают RIP-относительный тип mov. 64-битные и 32-битные коды операций на x86-64 во многом перекрываются, чтобы использовать общую логику декодирования в кремнии, и вы запутываетесь, потому что код, который разбирает GDB, похож на 32-битный код, который вы написали , но на самом деле вы просто выбрасываете байты мусора на процессор.

Это не имеет ничего общего с носом. Вы используете неверную архитектуру для процесса, в котором находитесь. Либо используйте 32-разрядный нос в 32-разрядном процессе, либо скомпилируйте код сборки для [BITS 64].

1 голос
/ 06 марта 2018

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

Итак, вы видите различия между кодировкой команд в x86-32 и x86-64. то есть Это то, что происходит, когда вы декодируете 32-битный машинный код как 64-битный .


mov 0x6000e5(%rip),%edx # 0xa001a5

Ключевым в этом случае является то, что 32-разрядный x86 имеет два избыточных способа кодирования 32-разрядного абсолютного адреса (без регистров): с байтом SIB или без него. 32-битный режим не имеет RIP-относительной (или EIP-относительной) адресации.

x86-64 переназначил более короткую (ModR/M + disp32) форму в качестве режима относительной RIP , в то время как 32-разрядная абсолютная адресация все еще доступна с более длинным кодированием ModR/M + SIB + disp32. (Конечно, с байтом SIB, который не кодирует ни базовый регистр, ни индексный регистр).

Обратите внимание, что смещение от RIP на самом деле является абсолютным статическим адресом, на котором размещены ваши данные (в 64-битном коде), 0x6000e5.

Комментарий - дизассемблер, показывающий вам эффективный абсолютный адрес; RIP-относительная адресация отсчитывается от байта после инструкции, то есть начала следующей инструкции.


movabs 0x4b8c289006000e5,%eax

Когда регистром назначения является EAX, ваш ассемблер (в 32-битном режиме) выбирает более короткую кодировку mov, которая загружает eax с 32-битного абсолютного адреса без байта ModR / M, просто A1 disp32. Руководство Intel называет это moffs (смещение памяти) вместо эффективного адреса.

В режиме x86-64 этот код операции принимает 64-битный абсолютный адрес. (И уникален тем, что может загружать / хранить с 64-битного абсолютного (не относящегося к RIP) адреса без предварительной записи адреса в регистр). Таким образом, декодирование потребляет часть следующей инструкции как часть 64-битного адреса, и вот откуда берутся некоторые из старших байтов в адресе. 0x6000e5 в младших 32 битах является правильным, и это то, как он будет декодироваться как 32-битный машинный код.


Изменено [bits 32] на [bit 64]

См. Что произойдет, если вы используете 32-битный int 0x80 Linux ABI в 64-битном коде? .

Лучше создать 32-битный исполняемый файл, если вы не собираетесь использовать собственные 64-битные системные вызовы. Используйте nasm -felf32 и ссылку с gcc -m32 -nostdlib -static.

0 голосов
/ 03 апреля 2012

Возможно, проблема в том, что смещение str_hello_length превышает 32 бита. IA-32 не поддерживает смещения более 32 бит. Обходной путь - использовать относительную RIP-адресацию в предположении (часто правильном), что расстояние между RIP и адресом, который вы пытаетесь достичь, умещается в 32 бита. В этом случае основание равно RIP, а индекс - это длина инструкции, поэтому, если инструкция уже имеет базу или индекс, RIP-Relative не может использоваться.

Давайте рассмотрим ваши различные попытки:

str_hello_length equ $-str_hello
...
...
mov edx, str_hello_length

Здесь нет доступа к памяти, только простое перемещение с немедленным обращением, поэтому вообще нет адресации.

Далее:

mov eax, str_hello_length       
mov edx, [eax]  ; count

Теперь первая инструкция - это шаг с немедленным, который по-прежнему не является доступом к памяти. Вторая инструкция имеет доступ к памяти, но она использует eax в качестве основы, и смещения нет. Относительный RIP релевантен только при смещении, поэтому здесь нет относительного RIP.

Наконец:

str_hello_length db $-str_hello
...
...
mov edx, [str_hello_length]     ; of course, without the brackets it'll load the address, which I don't want. I want the value stored at that address

Здесь вы используете str_hello_length в качестве смещения. Как я объяснил выше, это приведет к RIP-относительной адресации.

...