Подтвердить что ты не робот

Неравномерные указатели на x86

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

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

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

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

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

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


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

Btw, мой собственный тестовый код выглядел так (теперь также используйте 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 вызывает a SIGBUS или нет? Я не мог заставить его работать на моем двухъядерном ноутбуке Intel под Windows XP, и ни одна из версий GCC, которые я тестировал (MinGW-3.4.5, MinGW-4.3.0, Cygwin-3.4.4), в то время как кодологические и Джонатан Леффлер упомянутые сбои на x86...

4b9b3361

Ответ 1

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

Ответ 2

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

  • Возможно, вы не считаете это проблемой x86, но операции SSE должны иметь дело с правильно выровненными данными или производительностью, сильно пострадали (хотя это не создает исключения, что, как я думал, произошло). По-видимому, Intel исправила это, чтобы снизить производительность, начиная с процессоров "Penryn", но все же не так эффективно, как правильно выровненные данные, но влияние гораздо менее серьезное. Благодаря kquinn для указания этого.

  • блокированные операции должны работать с согласованными данными, чтобы обеспечить их атомарность в многопроцессорных системах (см. https://blogs.msdn.com/oldnewthing/archive/2004/08/30/222631.aspx)

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

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

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

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

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

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

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

Ответ 3

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

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

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

НТН

Ответ 4

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

int bar = *(int *)(foo + 1);

Ответ 5

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

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

Ответ 6

#include <stdio.h>

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

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

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

EDIT: довольно легко поднять 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;
}

Глядя на основную разборку в 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)

Во всяком случае, Кристоф, ваша тестовая программа не работает под 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));

Ответ 7

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

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

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