Как использовать инструкцию ins с ассемблером GNU

Как использовать x86 insинструкция с ассемблером GNU? Ссылка на инструкцию предлагает синтаксис INS m8/16/32, DX где например m16 (Я предполагаю) - это любой 16-битный регистр общего назначения, единственная цель которого - указать, следует ли читать байт / слово / двойное слово, верно?

Теперь, к сожалению, as отвергает ins %ax,%dx с участием Error: operand type mismatch for 'ins', это почему?

Для записи, я знаю, что могу просто использовать insb и т. д., но я вызываю эту инструкцию через встроенную сборку внутри программы на C++, а размер считываемого ввода зависит от параметра шаблона (а обработка строк во время компиляции не очень практична).

РЕДАКТИРОВАТЬ: вот что у меня сейчас, для справки (мне не очень нравится макрос)

#define INS(T) \
  __asm__ __volatile__("repne \n\t" \
                       "ins" T \
                       : "=D" (dest), "=c" (count) \
                       : "d" (port), "0" (dest), "1" (count) \
                       : "memory", "cc")

template<typename T>
void ins(uint16_t port, uint32_t dest, uint32_t count);

template<>
void ins<uint8_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("b"); }

template<>
void ins<uint16_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("w"); }

template<>
void ins<uint32_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("l"); }

2 ответа

Решение

Это должна быть ссылка на память, а не на регистр. Идея синтаксиса Intel заключается в том, что вы можете написать ins dword ptr [rdi], dx для 32-битного чтения (также известного как insd), ins word ptr [rdi], dx для 16-битного insw читать и т. д. Вы могли бы даже написать ins dword ptr [foo], dx и получить 32-битное чтение, но данные будут записаны в [rdi]так или иначе. Тем не менее синтаксис ассемблера AT&T не поддерживает это вообще, и единственный способ указать размер - это суффикс размера операнда.

Во встроенной сборке GCC вы можете получить суффикс размера операнда b,w,l,q соответствие размера операнда (в зависимости от его типа) с zмодификатор операнда. См. Руководство GCC, раздел "Extended Asm" в разделе "Модификаторы операндов x86". Поэтому, если вы правильно используете типы и добавляете операнд, который относится к фактическому назначению (а не к указателю на него), вы можете сделать следующее:

template<typename T>
void ins(uint16_t port, T *dest, uint32_t count) {
    asm volatile("rep ins%z2"
        : "+D" (dest), "+c" (count), "=m" (*dest)
        : "d" (port)
        : "memory");
}

Попробуйте на Godbolt

Здесь важно, чтобы пункт назначения был T * вместо общего uint32_t поскольку размер выводится из типа T.

Я также заменил ваши дублированные операнды ввода и вывода на +ограничение чтения-записи. И чтобы придраться к этому, "cc" clobber не нужен, потому что rep ins не влияет на какие-либо флаги, но это избыточно на x86, потому что предполагается, что каждый встроенный asm в любом случае сбивает флаги.

Этот ответ является ответом на первую версию вопроса, который был задан перед основным редактированием OP. Это обращается к синтаксису AT&T ассемблера GNU для INS инструкция.

Справочник по набору инструкций для INS / INSB / INSW / INSD - Ввод из порта в строку показывает, что на самом деле существует только 3 формы инструкции INS. Тот, который принимает байт (B), слово (W) или двойное слово (D). В конечном итоге БАЙТ (b), СЛОВО(w) или DWORD (l) читается из порта в DX и записывается в ES: RDI, ES: EDI, ES: DI. Нет формы INS инструкции, которые принимают регистр в качестве пункта назначения, в отличие от IN который может писать в AL / AX / EAX.

Примечание: с IN инструкция порт считается источником и является первым параметром в синтаксисе AT&T, где формат instruction src, dest.

В ассемблере GNU проще всего использовать любую из этих трех форм:

insb      # Read BYTE from port in DX to [RDI] or ES:[EDI] or ES:[DI]
insw      # Read WORD from port in DX to [RDI] or ES:[EDI] or ES:[DI]
insl      # Read DWORD from port in DX to [RDI] or ES:[EDI] or ES:[DI]

В 16-битном коде эта инструкция будет делать:

insb      # Read BYTE from port in DX to ES:[DI]
insw      # Read WORD from port in DX to ES:[DI]
insl      # Read DWORD from port in DX to ES:[DI]

В 32-битном коде эта инструкция будет делать:

insb      # Read BYTE from port in DX to ES:[EDI]
insw      # Read WORD from port in DX to ES:[EDI]
insl      # Read DWORD from port in DX to ES:[EDI]

В 64-битном коде эта инструкция будет делать:

insb      # Read BYTE from port in DX to [RDI]
insw      # Read WORD from port in DX to [RDI]
insl      # Read DWORD from port in DX to [RDI]

Почему ассемблеры также поддерживают длинную форму? В основном для целей документации, но есть небольшое изменение, которое можно выразить с помощью длинной формы, и это размер адреса памяти (а не размер данных для перемещения). В ассемблере GNU это поддерживается в 16-битном коде:

insb (%dx),%es:(%di)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)     # also applies to INSW and INSL

16-битный код может использовать 16-битные или 32-битные регистры для формирования операнда памяти, и именно так вы можете переопределить его (есть другой вариант использования addrпереопределения, описанные ниже). В 32-битном коде это возможно:

insb (%dx),%es:(%di)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)     # also applies to INSW and INSL

Можно использовать 16-битные регистры в операнде памяти в 32-битном коде. Очень мало случаев, когда это особенно полезно, но процессор поддерживает это. В 64-битном коде вам разрешено использовать 32-битные или 64-битные регистры в операнде памяти, поэтому в 64-битном коде это возможно:

insb (%dx),%es:(%rdi)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)      # also applies to INSW and INSL

В ассемблере GNU есть более короткий способ изменить размер адреса памяти, и он использует INSB/INSW/INSL с addr16, addr32, и addr64отменяет. В качестве примера в 16-битном коде они эквивалентны:

addr32 insb               # Memory address is %es:(%edi). also applies to INSW and INSL
insb (%dx),%es:(%edi)     # Same as above

В 32-битном коде они эквивалентны:

addr16 insb               # Memory address is %es:(%di). also applies to INSW and INSL
insb (%dx),%es:(%di)      # Same as above

В 64-битном коде они эквивалентны:

addr32 insb               # Memory address is %es:(%edi). also applies to INSW and INSL
insb (%dx),%es:(%edi)     # Same as above
Другие вопросы по тегам