Как использовать инструкцию 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");
}
Здесь важно, чтобы пункт назначения был
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