Неправильные указатели на x86 - PullRequest
19 голосов
/ 14 февраля 2009

Может ли кто-нибудь привести пример того, что при приведении указателя из одного типа в другой происходит сбой из-за неправильного выравнивания?

В комментариях к этому ответу оба заявляют, что делают что-то вроде

char * foo = ...;
int bar = *(int *)foo;

может привести к ошибкам даже на x86, если включена проверка выравнивания.

Я попытался создать состояние ошибки после установки флага проверки выравнивания с помощью set $ps |= (1<<18) в GDB, но ничего не произошло.

Как выглядит рабочий (т.е. нерабочий;)) пример?


Ни один из фрагментов кода из ответов не работает в моей системе - позже я попробую его с другой версией компилятора и на другом компьютере.

Кстати, мой собственный тестовый код выглядел следующим образом (теперь также используется asm для установки флага AC и чтения и записи без выравнивания):

#include <assert.h>

int main(void)
{
    #ifndef NOASM
    __asm__(
        "pushf\n"
        "orl $(1<<18),(%esp)\n"
        "popf\n"
    );
    #endif

    volatile unsigned char foo[] = { 1, 2, 3, 4, 5, 6 };
    volatile unsigned int bar = 0;

    bar = *(int *)(foo + 1);
    assert(bar == 0x05040302);

    bar = *(int *)(foo + 2);
    assert(bar == 0x06050403);

    *(int *)(foo + 1) = 0xf1f2f3f4;
    assert(foo[1] == 0xf4 && foo[2] == 0xf3 && foo[3] == 0xf2 &&
        foo[4] == 0xf1);

    return 0;
}

Утверждение проходит без проблем, даже несмотря на то, что сгенерированный код определенно содержит не выровненный доступ mov -0x17(%ebp), %edx и movl $0xf1f2f3f4,-0x17(%ebp).


Так будет ли установка AC вызвать SIGBUS или нет? Я не смог заставить его работать на моем двухъядерном ноутбуке Intel под Windows XP без какой-либо из протестированных мной версий GCC (MinGW-3.4.5, MinGW-4.3.0, Cygwin-3.4.4), тогда как codelogic и Jonathan Leffler упомянутые сбои на x86 ...

Ответы [ 8 ]

21 голосов
/ 14 февраля 2009

Ситуации необычны, когда доступ без выравнивания вызовет проблемы на x86 (помимо того, что доступ к памяти займет больше времени). Вот некоторые из тех, о которых я слышал:

  1. Вы не можете считать это проблемой x86, но операции SSE выигрывают от выравнивания. Выровненные данные могут быть использованы в качестве операнда источника памяти для сохранения инструкций. Инструкции с невыровненной загрузкой, такие как movups, медленнее, чем movaps на микроархитектурах до Nehalem, но на Nehalem и более поздних (и семейства AMD Bulldozer) 16-байтовые загрузки / хранилища без выравнивания примерно так же эффективны, как 8-байтовые загрузки без выравнивания магазины; один моп и никаких штрафов вообще, если данные выровнены во время выполнения или не пересекают границу строки кэша, в противном случае эффективная аппаратная поддержка для разделения строки кэша. Разделение 4k очень дорого (~ 100 циклов) до Skylake (до ~ 10 циклов, как разделение строки кэша). См. https://agner.org/optimize/ и ссылки на производительность в x86 вики для получения дополнительной информации.

  2. взаимосвязанные операции (например, lock add [mem], eax) являются очень медленными, если они недостаточно выровнены, особенно если они пересекают границу строки кэша, поэтому они не могут просто использовать блокировка внутри ядра процессора. В старых (с ошибками) системах SMP они могут фактически не быть атомарными (см. https://blogs.msdn.com/oldnewthing/archive/2004/08/30/222631.aspx).

  3. и еще одна возможность, обсуждаемая Рэймондом Ченом, связана с устройствами, имеющими аппаратную банковскую память (по общему признанию, странная ситуация) - https://blogs.msdn.com/oldnewthing/archive/2004/08/27/221486.aspx

  4. Я вспоминаю (но у меня нет ссылки - поэтому я не уверен насчет этого) аналогичные проблемы с не выровненным доступом, которые пересекают границы страниц, которые также включают сбой страницы. Я посмотрю, смогу ли я найти ссылку для этого.

И, изучив этот вопрос, я узнал что-то новое (мне было интересно узнать о команде "$ps |= (1<<18)", которая упоминалась в нескольких местах). Я не осознавал, что процессоры x86 (кажется, начиная с 486) могут вызывать исключение при неправильном доступе.

Из книги Джеффри Рихтера "Приложения для программирования для Windows, 4-е издание":

Давайте подробнее рассмотрим, как процессор x86 выполняет выравнивание данных. Процессор x86 содержит специальный битовый флаг в своем регистре EFLAGS, который называется AC (проверка выравнивания). По умолчанию этот флаг устанавливается в ноль, когда процессор впервые получает питание. Когда этот флаг равен нулю, CPU автоматически делает все, что ему нужно, для успешного доступа к смещенным значениям данных. Однако, если этот флаг установлен в 1, ЦП выдает прерывание INT 17H всякий раз, когда предпринимается попытка получить доступ к смещенным данным. Версии x 2000 для Windows 2000 и Windows 98 никогда не изменяют этот бит флага ЦП. Поэтому вы никогда не увидите исключение смещения данных в приложении, когда оно работает на процессоре x86.

Это было для меня новостью.

Конечно, большая проблема со смещенным доступом заключается в том, что когда вы в конечном итоге собираетесь скомпилировать код для не-x86 / x64-процессора, вам в конечном итоге придется отследить и исправить целую кучу вещей, поскольку практически все остальные Процессоры с битовой или большей частотой чувствительны к проблемам выравнивания.

8 голосов
/ 22 сентября 2011

Если вы читаете об архитектуре Core I7 (в частности, об их литературе по оптимизации), Intel фактически добавила в нее тонну аппаратного обеспечения, чтобы сделать доступ к памяти со смещением почти свободным. Насколько я могу судить, только несоответствие, которое пересекает границу строки кэша, имеет какую-либо дополнительную стоимость - и даже в этом случае оно минимально. Насколько я помню, у AMD также очень мало проблем со смещенным доступом (по циклу) (* это было давно).

Для чего бы то ни было, я установил этот флаг в eflags (проверка выравнивания битов AC), когда увлекся оптимизацией проекта, над которым я работал. Оказывается, что Windows ПОЛНОСТЬЮ неправильно выровненных обращений - так много, что я не смог найти какие-либо не выровненные обращения к памяти в нашем коде, я был засыпан таким количеством выровненных обращений в библиотеках и коде Windows, что у меня не было времени продолжить.

Возможно, мы сможем узнать, что когда процессоры делают вещи бесплатными или очень дешевыми, программисты успокаиваются и делают вещи, которые требуют немного дополнительных затрат. Возможно, инженеры Intel провели часть этого исследования и обнаружили, что типичное программное обеспечение для настольных компьютеров x86 делает миллионы ошибочно настроенных обращений в секунду, поэтому они устанавливают в CoreI7 невероятно быстрое аппаратно смещенное оборудование доступа.

НТН

3 голосов
/ 18 декабря 2009

Существует дополнительное условие, не упомянутое, чтобы EFLAGS.AC действительно вступил в силу. CR0.AM должен быть установлен для предотвращения отключения INT 17h на более старых ОС, предшествующих 486, у которых нет обработчика для этого исключения. К сожалению, Windows не устанавливает его по умолчанию, вам нужно написать драйвер режима ядра, чтобы установить его.

3 голосов
/ 14 февраля 2009

char * foo, вероятно, выровнен по границам int. Попробуйте это:

int bar = *(int *)(foo + 1);
2 голосов
/ 14 февраля 2009
#include <stdio.h>

int main(int argc, char **argv)
{
  char c[] = "a";

  printf("%d\n", *(int*)(c));
}

Это дает мне SIGBUS после установки set $ps |= (1<<18) в GDB, которое, по-видимому, выбрасывается, если выравнивание адресов некорректно (среди прочих причин).

РЕДАКТИРОВАТЬ: довольно легко поднять SIGBUS:

int main(int argc, char **argv)
{
    /* EDIT: enable AC check */
    asm("pushf; "
        "orl $(1<<18), (%esp); "
        "popf;");

    char c[] = "1234567";
    char d[] = "12345678";
    return 0;
}

Просмотр разборки main в gdb:

Dump of assembler code for function main:
....
0x08048406 <main+34>:   mov    0x8048510,%eax
0x0804840b <main+39>:   mov    0x8048514,%edx
0x08048411 <main+45>:   mov    %eax,-0x10(%ebp)
0x08048414 <main+48>:   mov    %edx,-0xc(%ebp)
0x08048417 <main+51>:   movl   $0x34333231,-0x19(%ebp)   <== BAM! SIGBUS
0x0804841e <main+58>:   movl   $0x38373635,-0x15(%ebp)
0x08048425 <main+65>:   movb   $0x0,-0x11(%ebp)

Как бы то ни было, ваша тестовая программа Christoph завершается неудачно в Linux, вызывая SIGBUS, как и должно быть. Это, вероятно, вещь для Windows?


Вы можете включить бит проверки выравнивания в коде, используя этот фрагмент:

/* enable AC check */
asm("pushf; "
    "orl $(1<<18), (%esp); "
    "popf;");

Также убедитесь, что флаг действительно установлен:

unsigned int flags;
asm("pushf; "
    "movl (%%esp), %0; "
    "popf; " : "=r"(flags));
fprintf(stderr, "%d\n", flags & (1<<18));
2 голосов
/ 14 февраля 2009
char *foo = "....";
foo++;
int *bar = (int *)foo;

Компилятор помещает foo на границу слова, а затем, когда вы увеличиваете его, он равен слову + 1, что недопустимо для указателя типа int.

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

Чтобы воспользоваться исключением, позвоните SetErrorMode с SEM_NOALIGNMENTFAULTEXCEPT:

int main(int argc, char* argv[])
{
   SetErrorMode(GetErrorMode() | SEM_NOALIGNMENTFAULTEXCEPT);
   ...
}

Подробнее см. Выравнивание данных Windows на IPF, x86 и x64 .

0 голосов
/ 26 июля 2018

gcc, когда автоматическая векторизация предполагает, что uint16_t* выровнен по 2-байтовой границе. Если вы нарушите это предположение, вы можете получить segfault: Почему при выравнивании доступа к памяти mmap в AMD64 иногда возникает ошибка выравнивания?

Таким образом, соблюдение правил выравнивания С имеет значение даже при нацеливании на x86.


Используйте это, чтобы эффективно выразить невыровненную нагрузку в C:

static inline
uint32_t load32(char *p)     // char*  is allowed to alias anything
    uint32_t tmp;
    memcpy(&tmp, p, sizeof(tmp));
    return tmp;
}

В x86 он будет компилироваться в один ожидаемый mov (или векторизацию или что-то еще), но в SPARC или MIPS до MIPS64r6 или в том виде, в котором он будет компилироваться в любую последовательность команд, необходимую для невыровненной загрузки. Такое использование memcpy полностью оптимизирует цели, которые поддерживают невыровненные нагрузки.

т.е. ваш компилятор знает, поддерживает ли целевой ISA невыровненные нагрузки или нет, и будет генерировать asm, который их выполняет, или нет, как он считает нужным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...