Портирование упаковщиков inb / outb AT & T inline-asm для работы с gcc -masm = intel - PullRequest
0 голосов
/ 02 марта 2019

Я сейчас работаю на своей x86 OS.Я попытался реализовать функцию inb из здесь , и это дает мне Error: Operand type mismatch for `in'.

Это также может быть то же самое с outb или io_wait.

IЯ использую синтаксис Intel (-masm=intel), и я не знаю, что делать.

Код:

#include <stdint.h>
#include "ioaccess.h"

uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}

С синтаксисом AT & T это работает.


Для outb У меня возникла другая проблема после обращения операндов:

void io_wait(void)
{
    asm volatile ( "outb $0x80, %0" : : "a"(0) );
}

Error: operand size mismatch for `out'

Ответы [ 2 ]

0 голосов
/ 02 марта 2019

Можно написать код, который работает с или без -masm=intel, используя альтернативы диалекта для встроенного asm GNU C https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html (Это хорошая идея для заголовков, которые могут включать другие люди.)

Работает как "{at&t stuff | intel stuff}": компилятор выбирает, какую сторону | сохранить, основываясь на текущем режиме.

Основное различие между AT & T и синтаксисом Intel заключается в том, чтосписок операндов перевернут, поэтому обычно у вас есть что-то вроде "inb {%1,%0 | %0,%1}".

Это версия хороших функций @ MichaelPetch с использованием альтернатив диалекта :

// make this a header: these single instructions can inline more cheaply
// than setting up args for a function call
#include <stdint.h>

static inline
uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb {%1, %0 | %0, %1}"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}

static inline
void outb(uint16_t port, uint8_t byte)
{
    asm volatile ( "outb {%1, %0 | %0, %1}"
                   :
                   : "a"(byte),
                     "Nd"(port) );
}

static inline
void io_wait(void) {
    outb (0x80, 0);
}

Макросы Linux / Glibc sys/io.h иногда используют %w1 для расширения ограничения на 16-битное имя регистра, но использование типов правильного размера также работает.

Если вы хотите версию с барьером памятииз них, чтобы воспользоваться преимуществом того факта, что in / out (более или менее) сериализуются как инструкция lock ed или mfence, добавьте Clobber "memory", чтобы остановить переупорядочение доступа к памяти во время компиляциичерез это.

Если порт ввода-вывода может инициировать чтение DMA из некоторой другой памяти, которую вы недавно написали, , вам также может понадобиться "memory" clobber для этого .(Современный x86 имеет согласованный DMA, поэтому вам не пришлось бы явно сбрасывать его, но вы не можете позволить компилятору переупорядочить его после outb или даже оптимизировать, по-видимому, мертвое хранилище.)


В GAS нет поддержки для сохранения старого режима, поэтому использование .intel_syntax noprefix внутри встроенного ассемблера не дает вам никакого способа узнать, переключиться ли обратно на .att_syntax или нет.

Но это не будетобычно все равно достаточно: вам нужно, чтобы компилятор форматировал операнды так, чтобы они соответствовали режиму синтаксиса при заполнении шаблона.например, номер порта необходимо увеличить до $imm или %dx (AT & T) против dx или imm без префикса $.

Или для операнда памяти, [rdi + rax*4 + 8] или8(%rdi, %rax, 4).

Но вам все равно нужно позаботиться о том, чтобы изменить список операндов с помощью { | } самостоятельно;компилятор не пытается сделать это за вас.Это просто текстовая подстановка в шаблон по простым правилам.

0 голосов
/ 02 марта 2019

Если вам нужно использовать -masm=intel, вам нужно убедиться, что встроенная сборка соответствует синтаксису Intel.Синтаксис Intel - dst, src (синтаксис AT & T обратный).Этот несколько связанный ответ содержит некоторую полезную информацию о некоторых различиях между вариантом Intel NASM 1 (не вариант GAS) и синтаксисом AT & T:

Информация о том, как выможно перейти к переводу синтаксиса NASM Intel в синтаксис AT & T GAS, который можно найти в этом ответе Stackoverflow , и много полезной информации предоставлено в этой статье IBM .

[snip]

Как правило, самые большие различия:

  • С синтаксисом AT & T источник находится слева, а пункт назначения - справа, а Intel - наоборот.
  • В синтаксисе AT & T к именам регистров добавляется %
  • В синтаксисе AT & T к немедленным значениям добавляется $
  • Операнды памяти, вероятно, являются самой большой разницей.NASM использует [сегмент: disp + base + index * scale] вместо синтаксиса GAS в сегмент: disp (base, index, scale) .

Проблема в вашем коде заключается в том, что исходные и целевые операнды должны быть обращены от исходного синтаксиса AT & T, с которым вы работали.Этот код:

asm volatile ( "inb %1, %0"
               : "=a"(ret)
               : "Nd"(port) );

Должен быть:

asm volatile ( "inb %0, %1"
               : "=a"(ret)
               : "Nd"(port) );

Что касается вашего обновления: проблема в том, что в синтаксисе Intel к непосредственным значениям не добавляется $.Эта строка является проблемой:

asm volatile ( "outb $0x80, %0" : : "a"(0) );

Это должно быть:

asm volatile ( "outb 0x80, %0" : : "a"(0) );

Если бы у вас была правильная функция outb, вы могли бы сделать что-то подобное вместо этого:

#include <stdint.h>
#include "ioaccess.h"

uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %0, %1"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}

void outb(uint16_t port, uint8_t byte)
{
    asm volatile ( "outb %1, %0"
                   :
                   : "a"(byte),
                     "Nd"(port) );
}

void io_wait(void)
{
    outb (0x80, 0);
}

Немного более сложная версия, поддерживающая диалекты AT & T и Intel :

Несколько диалектов на ассемблере в шаблонах asm На таких объектах, как x86, GCC поддерживаетнесколько диалектов ассемблера.Опция -masm определяет, какой диалект GCC используется по умолчанию для встроенного ассемблера.Специфичная для цели документация для опции -masm содержит список поддерживаемых диалектов, а также диалект по умолчанию, если опция не указана.Эта информация может быть важна для понимания, поскольку ассемблерный код, который работает правильно при компиляции с использованием одного диалекта, скорее всего потерпит неудачу, если скомпилируется с использованием другого.См. Параметры x86.

Если ваш код должен поддерживать несколько диалектов ассемблера (например, если вы пишете публичные заголовки, которые должны поддерживать различные параметры компиляции), используйте конструкции этой формы:

{dialect0 |dialect1 |dialect2 ...}

Для целей x86 и x86-64 есть два диалекта.Dialect0 - это синтаксис AT & T, а Dialect1 - это синтаксис Intel.Функции могут быть переработаны следующим образом:

#include <stdint.h>
#include "ioaccess.h"

uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb {%[port], %[retreg] | %[retreg], %[port]}"
                   : [retreg]"=a"(ret)
                   : [port]"Nd"(port) );
    return ret;
}

void outb(uint16_t port, uint8_t byte)
{
    asm volatile ( "outb {%[byte], %[port] | %[port], %[byte]}"
                   :
                   : [byte]"a"(byte),
                     [port]"Nd"(port) );
}

void io_wait(void)
{
    outb (0x80, 0);
}

Я также дал символические имена ограничений вместо использования %0 и %1, чтобы упростить чтение и обслуживание встроенной сборки. Из GCCВ документации каждое ограничение имеет вид:

[[asmSymbolicName]] ограничение (cvariablename)

Где:

asmSymbolicName

Определяет символическое имя для операнда.Назовите имя в шаблоне ассемблера, заключив его в квадратные скобки (т. Е. «% [Value]»).Область имени - это оператор asm, который содержит определение.Допустимо любое допустимое имя переменной C, включая имена, уже определенные в окружающем коде.Никакие два операнда в одном и том же операторе asm не могут использовать одно и то же символическое имя.

Если не используется asmSymbolicName, используйте позицию операнда (начиная с нуля) в списке операндов в шаблоне ассемблера.Например, если есть три выходных операнда, используйте «% 0» в шаблоне для ссылки на первый, «% 1» для второго и «% 2» для третьего.

Thisверсия должна работать 2 независимо от того, компилируете ли вы с -masm=intel или -masm=att опциями


Сноски

  • 1 Хотя NASM Intel диалект и GAS (GNU Assembler) синтаксис Intel похожи, есть некоторые различия.Во-первых, в синтаксисе NASM Intel используется [сегмент: disp + base + index * scale] , где сегмент может быть указан внутри [], а для синтаксиса Intel GAS требуется сегмент снаружи с сегментом : [disp + base + index * scale] .
  • 2 Несмотря на то, что код будет работать, вы должны поместить все эти основные функции непосредственно в файл ioaccess.h и удалить их из.c файл, который содержит их.Поскольку вы поместили эти основные функции в отдельный файл .c (внешняя связь), компилятор не может оптимизировать их так хорошо, как мог бы.Вы можете изменить функции типа static inline и поместить их в заголовок напрямую.Компилятор будет тогда иметь возможность оптимизировать код, удаляя служебные вызовы функции и уменьшая потребность в дополнительных нагрузках и хранилищах.Вы захотите скомпилировать с оптимизацией выше -O0.Рассмотрим -O2 или -O3.
  • Особые замечания по разработке ОС :
    1. Существует множество игрушечных ОС (примеры, учебные пособия и даже код в OSDev Wiki ), которые не работают с оптимизацией.Многие сбои происходят из-за плохой / плохой встроенной сборки или из-за неопределенного поведения.Встроенная сборка должна использоваться в качестве крайней меры.Если ваше ядро ​​не работает с оптимизациями, оно, вероятно, не является ошибкой в ​​компиляторе (это возможно, просто маловероятно).
    2. Прислушайтесь к совету в ответе @PeterCordes относительно доступа к порту, который может вызвать чтение DMA.
...