2016-02-18 7 views
0

Я пытаюсь проверить любые неудовлетворенные чтения в моей программе. Включить невыровненное исключение процессора доступа через (используя x86_64 на г ++ на Linux ядре 3.19):любой способ остановить неровный доступ из стандартной библиотеки C++ на x86_64?

asm volatile("pushf \n" 
      "pop %%rax \n" 
      "or $0x40000, %%rax \n" 
      "push %%rax \n" 
      "popf \n" ::: "rax"); 

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

char fullpath[eMaxPath]; 
    snprintf(fullpath, eMaxPath, "%s/%s", "blah", "blah2"); 

StackTrace показывает сбой через __memcpy_sse2 что приводит меня подозревать, что стандартная библиотека использует SSE, чтобы выполнить свою тетсру, но это не понимает, что я теперь сделал неприглаженные чтения неприемлемыми.

Я считаю, что это правильно, и существует ли какой-либо путь вокруг этого (т. Е. Можно ли использовать стандартную библиотеку для несвязанного безопасного sprintf/memcpy)?

благодарит

+0

Будьте осторожны с 'push' /' pop' inline asm. Это небезопасно, когда ABI включает в себя [красную зону ниже стека] (https://en.wikipedia.org/wiki/Red_zone_ (вычисления)) (например, x86-64 SysV делает), поскольку [gcc предполагает, что операторы asm не сжимают red-zone] (http://stackoverflow.com/a/34522750/224132). Это повредит все, что gcc пролило там. С красной зоной, я думаю, вам просто нужно добавить $ -128,% rsp' перед использованием стека или использовать reg для сохранения/восстановления памяти стека, которую вы собираетесь clobber. Вы можете объявить регистр clobber, но вы не можете объявить '-8 (% rsp)' clobber:/ –

+0

Итак, возможно, 'asm (" add $ -128,% rsp; pushf; orl $ 0x40000, (% rsp); popf; sub $ -128,% rsp ");' Или даже 'orb $ 0x4, 2 (% rsp) 'для сохранения двух байтов: P. используйте 'orq', чтобы избежать переадресации хранилища, когда' popf' считывает 8 байтов. Я знаю, что это не то, что нужно оптимизировать, но ненужная загрузка/хранение в rax выскочила на меня. (используя pop вместо mov на самом деле отлично) –

ответ

2

Как я прокомментировал вопрос, что asm небезопасен, потому что он steps on the red-zone. Вместо этого используйте

asm volatile ("add $-128, %rsp\n\t" 
    "pushf\n\t" 
    "orl $0x40000, (%rsp)\n\t" 
    "popf\n\t" 
    "sub $-128, %rsp\n\t" 
    ); 

-128 (припадки в знак удлиненных 8bit немедленных, но 128 не, следовательно, используя add $-128 вычитать 128.)

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

asm("stac"); // Set AC flag 
asm("stac"); // Clear AC flag 

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

Учитывая, что в любом случае вы не должны стремиться полностью исключить все неприсоединившиеся обращения, я не думаю, что это самый простой способ найти те, которые у вас есть.

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

Что вы можете попробовать смотрит на счетчики производительности для некоторых из этих событий:

misalign_mem_ref.loads  [Speculative cache line split load uops dispatched to L1 cache] 
    misalign_mem_ref.stores [Speculative cache line split STA uops dispatched to L1 cache] 

    ld_blocks.store_forward [This event counts loads that followed a store to the same address, where the data could not be forwarded inside the pipeline from the store to the load. 
          The most common reason why store forwarding would be blocked is when a load's address range overlaps with a preceeding smaller uncompleted store. 
          See the table of not supported store forwards in the Intel? 64 and IA-32 Architectures Optimization Reference Manual. 
          The penalty for blocked store forwarding is that the load must wait for the store to complete before it can be issued.] 

(от ocperf.py list output на моем SandyBridge CPU).

Возможно, есть другие способы обнаружения неравномерного доступа к памяти. Может быть, valgrind? Я искал на valgrind detect unaligned и нашел this mailing list discussion from 13 years ago. Наверное, все еще не реализовано.


рука, оптимизированные функций библиотеки использует невыровненные доступы, потому что это самый быстрый способ для них, чтобы получить их работу. например копирование байтов с 6 по 13 строки в другое место может и должно выполняться только с одним 8-байтовым загрузчиком/хранилищем.

Так что да, вам понадобится специальная медленная & безопасные версии библиотечных функций.

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

Esp. если это не код SSE, поскольку устаревший SSE без AVX может только сбрасывать нагрузки в операнды памяти для инструкций ALU, когда выравнивание гарантировано.

Преимущество наличия достаточной аппаратной поддержки для неосновных операционных систем памяти заключается в том, что программное обеспечение может быть быстрее в выровненном корпусе. Он может оставить обработку выравнивания для аппаратного обеспечения вместо выполнения дополнительных инструкций для обработки указателей, которые, вероятно, выровнены. (У Linus Torvalds были интересные сообщения об этом на форумах http://realworldtech.com/, но они не доступны для поиска, поэтому я не могу найти его.

+0

да, следуя примеру из другого вопроса im, пытающегося спуститься по пути проверки misalign_mem_refs.loads через perf, предполагая, что я могу заставить перфоманс работать в моей системе и предполагаю, что могу заставить его правильно сообщать счетчик о неуравновешенном доступе, который я Думаю, r0105 – skimon

+0

@skimon: 'ocperf.py' - хорошая обложка, которая дает вам символические имена для большего количества событий, связанных с uarch. Вот почему я связал это. –

2

Вы не собираетесь это нравится, но есть только один ответ: не связать против стандартных библиотек. Изменяя этот параметр, вы изменили ABI, и стандартная библиотека ему не нравится. memcpy и друзья - это рукописная сборка, поэтому не обязательно, чтобы компилятор мог убедить компилятор сделать что-то еще.

2

Хотя я ненавижу препятствовать восхитительному представлению, вы играете с огнем, мой друг.

Это не просто sse2 доступ но любой нестандартный доступ. Даже простая выборка int.


Вот тестовая программа:

#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <malloc.h> 

void *intptr; 

void 
require_aligned(void) 
{ 
    asm volatile("pushf \n" 
      "pop %%rax \n" 
      "or $0x00040000, %%eax \n" 
      "push %%rax \n" 
      "popf \n" ::: "rax"); 
} 

void 
relax_aligned(void) 
{ 
    asm volatile("pushf \n" 
      "pop %%rax \n" 
      "andl $0xFFFBFFFF, %%eax \n" 
      "push %%rax \n" 
      "popf \n" ::: "rax"); 
} 

void 
msg(const char *str) 
{ 
    int len; 

    len = strlen(str); 
    write(1,str,len); 
} 

void 
grab(void) 
{ 
    volatile int x = *(int *) intptr; 
} 

int 
main(void) 
{ 

    setlinebuf(stdout); 

    // minimum alignment from malloc is [usually] 8 
    intptr = malloc(256); 
    printf("intptr=%p\n",intptr); 

    // normal access to aligned pointer 
    msg("normal\n"); 
    grab(); 

    // enable alignment check exception 
    require_aligned(); 

    // access aligned pointer under check [will be okay] 
    msg("aligned_norm\n"); 
    grab(); 

    // this grab will generate a bus error 
    intptr += 1; 
    msg("aligned_except\n"); 
    grab(); 

    return 0; 
} 

Выходной сигнал этого:

intptr=0x1996010 
normal 
aligned_norm 
aligned_except 
Bus error (core dumped) 

Программа генерируется это просто из-за попытки 4 байта int выборки из адреса 0x1996011 [который is нечетные и не a кратность 4].

Итак, как только вы включите флаг AC [выравнивание], даже простые вещи сломаются.


ИМО, если вы действительно есть некоторые вещи, которые не выровненные оптимально и пытаются найти их, используя printf, инструментирование код с отладки Утверждает, или с помощью gdb с некоторыми специальными watch команд или контрольных точек с заявления состояния являются лучше/безопаснее путем


UPDATE:

Я использую собственный собственный распределитель, готовя свой код к работе над архитектурой, которая не поддерживает неровные чтения/записи, поэтому я хочу убедиться, что мой код не сломается на этой архитектуре.

Ярмарка достаточно.

Примечание стороны: Мое любопытство получило лучше меня, как только [основных] арки я могу вспомнить [в данный момент], которые имеют этот вопрос являются Motorola mc68000 и старых мэйнфреймы IBM (например, IBM System 370).

Одна из практических причин для моего любопытства заключается в том, что для определенных арк (например, ARM/android, MIPS) есть доступные эмуляторы. Вы можете перестроить эмулятор из источника, добавив дополнительные проверки, если это необходимо. В противном случае выполнение вашей отладки под эмулятором может быть вариантом.

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

Я могу сказать вам по опыту, что попытка возобновить работу с обработчиком сигнала после этого не работает слишком хорошо [если вообще]. Использование gdb - лучшая ставка, если вы можете устранить ложные срабатывания, если AC выключен в стандартных функциях [см. Ниже].

В идеале я предполагаю, что хотел бы использовать что-то вроде perf, чтобы показать мне стоп-сигналы, которые были смещены, но до сих пор нет кубиков.

Это возможно, но вам нужно будет подтвердить, что perf даже сообщает о них. Чтобы увидеть, вы можете попробовать perf против моей первоначальной тестовой программы выше. Если он работает, «счетчик» должен быть равен нулю до и после.


Чистейший путь может быть поперчить ваш код с «утверждающими» макросами [которые могут быть скомпилированы в и с -DDEBUG выключателем].

Однако, поскольку вы столкнулись с проблемой закладки фундамента, возможно, стоит посмотреть, может ли метод переменного тока работать.

Поскольку вы пытаетесь отлаживать ваш аллокатора памяти, вам нужно только AC на в ваших функций. Если одна из ваших функций вызывает libc, отключите AC, вызовите функцию, а затем повторно включите AC.

Распределитель памяти довольно низкий, поэтому он не может полагаться на слишком много стандартных функций. Большинство стандартных функций полагаются на возможность вызова malloc. Таким образом, вы также можете рассмотреть интерфейс vtable для остальной [стандартной] библиотеки.

Я закодировал несколько немного отличающихся функций бит/бит AC. Я помещал их в функцию .S, чтобы устранить проблемы inline asm.

Я закодировал простой пример использования в трех файлах.

Вот AC набор/четкие функции:

// acbit/acops.S -- low level AC [alignment check] operations 

#define AC_ON  $0x00040000 
#define AC_OFF  $0xFFFFFFFFFFFBFFFF 

    .text 

// acpush -- turn on AC and return previous mask 
    .globl acpush 
acpush: 
    // get old mask 
    pushfq 
    pop  %rax 

    mov  %rax,%rcx     // save to temp 
    or  AC_ON,%ecx     // turn on AC bit 

    // set new mask 
    push %rcx 
    popfq 

    ret 

// acpop -- restore previous mask 
    .globl acpop 
acpop: 
    // get current mask 
    pushfq 
    pop  %rax 

    and  AC_OFF,%rax     // clear current AC bit 

    and  AC_ON,%edi     // isolate the AC bit in argument 
    or  %edi,%eax     // lay it in 

    // set new mask 
    push %rax 
    popfq 

    ret 

// acon -- turn on AC 
    .globl acon 
acon: 
    jmp  acpush 

// acoff -- turn off AC 
    .globl acoff 
acoff: 
    // get current mask 
    pushfq 
    pop  %rax 

    and  AC_OFF,%rax     // clear current AC bit 

    // set new mask 
    push %rax 
    popfq 

    ret 

Вот это файл заголовка, который имеет прототипы функций и некоторые «вспомогательные» макросы:

// acbit/acbit.h -- common control 

#ifndef _acbit_acbit_h_ 
#define _acbit_acbit_h_ 

#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <malloc.h> 

typedef unsigned long flags_t; 

#define VARIABLE_USED(_sym) \ 
    do { \ 
     if (1) \ 
      break; \ 
     if (!! _sym) \ 
      break; \ 
    } while (0) 

#ifdef ACDEBUG 
#define ACPUSH \ 
    do { \ 
     flags_t acflags = acpush() 

#define ACPOP \ 
     acpop(acflags); \ 
    } while (0) 

#define ACEXEC(_expr) \ 
    do { \ 
     acoff(); \ 
     _expr; \ 
     acon(); \ 
    } while (0) 
#else 
#define ACPUSH   /**/ 
#define ACPOP   /**/ 
#define ACEXEC(_expr) _expr 
#endif 

void *intptr; 

flags_t 
acpush(void); 

void 
acpop(flags_t omsk); 

void 
acon(void); 

void 
acoff(void); 

#endif 

Ниже приведен пример программы, которая использует все выше:

// acbit/acbit2 -- sample allocator 

#include <acbit.h> 

// mymalloc1 -- allocation function [raw calls] 
void * 
mymalloc1(size_t len) 
{ 
    flags_t omsk; 
    void *vp; 

    // function prolog 
    // NOTE: do this on all "outer" (i.e. API) functions 
    omsk = acpush(); 

    // do lots of stuff ... 
    vp = NULL; 

    // encapsulate standard library calls like this to prevent false positives: 
    acoff(); 
    printf("%p\n",vp); 
    acon(); 

    // function epilog 
    acpop(omsk); 

    return vp; 
} 

// mymalloc2 -- allocation function [using helper macros] 
void * 
mymalloc2(size_t len) 
{ 
    void *vp; 

    // function prolog 
    ACPUSH; 

    // do lots of stuff ... 
    vp = NULL; 

    // encapsulate standard library calls like this to prevent false positives: 
    ACEXEC(printf("%p\n",vp)); 

    // function epilog 
    ACPOP; 

    return vp; 
} 

int 
main(void) 
{ 
    int x; 

    setlinebuf(stdout); 

    // minimum alignment from malloc is [usually] 8 
    intptr = mymalloc1(256); 
    intptr = mymalloc2(256); 

    x = *(int *) intptr; 

    return x; 
} 

UPDATE # 2:

Мне нравится идея отключить проверку перед любой библиотеки вызовов.

Если работы AC H/W и завершают библиотечные вызовы, это не должно давать не ложных срабатываний. Единственное исключение было бы, если бы компилятор сделал вызов своей внутренней вспомогательной библиотеке (например, выполнил разделение 64 бит на 32-битной машине и т. Д.).

Помните/опасайтесь загрузчика ELF (например, /lib64/ld-linux-x86-64.so.2), делая динамическое разрешение символа на «ленивых» привязках символов. Не должно быть большой проблемой. Существуют способы принудительного перемещения перемещений при запуске программы, если это необходимо.

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

Совершенный код в ядре достаточно сложный, что может быть больше проблем, чем того стоит. Он должен общаться с перфомансной программой с трубой [IIRC]. Кроме того, выполнение функции AC [вероятно] необычно, что код кода ядра для этого не очень хорошо протестирован.

Im использует ocperf с misalign_mem_ref.loads и сохраняет, но в любом случае счетчики не коррелируют вообще. Если я записывать и смотреть на callstacks я получить совершенно неузнаваемые callstacks для этих счетчиков, так что я подозреваю, что либо не работает счетчик на моей аппаратной/перфорация или оленья кожа на самом деле рассчитывать, что я думаю, что он считает

честно, я не знайте, правильно ли обрабатывают перфорированные манипуляции с разными ядрами [или нет] - это должно [ИМО]. Но использование sched_setaffinity для блокировки вашей программы в одном ядре может помочь.

Но, используя бит AC более прямой и окончательный, ИМО. Я думаю, что это лучше.

Я говорил о добавлении макросов «assert» в код.

Я закодировал некоторые из них ниже. Это то, что я буду использовать. Они не зависят от кода AC. Но они также могут использоваться в сочетании с битным кодом AC в подходе «пояс и подтяжки».

Эти макросы имеют одно отличное преимущество.При правильной установке [и либерально] они могут проверять значения плохого указателя в момент их вычисления . То есть, гораздо ближе к истинному источнику проблемы.

С переменным током вы можете рассчитать неправильное значение, но AC будет только в [sometime] позже, когда указатель dereferenced [что вообще не может быть в вашем коде API].

Я сделал полный распределитель памяти до [с проверками переполнения и страницами охраны и т. Д.]. Макро-подход - это то, что я использовал. И, если бы у меня был только один инструмент для этого, это тот, который я использовал бы. Поэтому я рекомендую это прежде всего.

Но, как я уже сказал, его можно использовать и с кодом переменного тока.

Вот файл заголовка для макросов:

// acbit/acptr.h -- alignment check macros 

#ifndef _acbit_acptr_h_ 
#define _acbit_acptr_h_ 

#include <stdio.h> 

typedef unsigned int u32; 

// bit mask for given width 
#define ACMSKOFWID(_wid) \ 
    ((1u << (_wid)) - 1) 

#ifdef ACDEBUG2 
#define ACPTR_MSK(_ptr,_msk) \ 
    acptrchk(_ptr,_msk,__FILE__,__LINE__) 
#else 
#define ACPTR_MSK(_ptr,_msk)  /**/ 
#endif 

#define ACPTR_WID(_ptr,_wid) \ 
    ACPTR_MSK(_ptr,(_wid) - 1) 

#define ACPTR_TYPE(_ptr,_typ) \ 
    ACPTR_WID(_ptr,sizeof(_typ)) 

// acptrfault -- pointer alignment fault 
void 
acptrfault(const void *ptr,const char *file,int lno); 

// acptrchk -- check pointer for given alignment 
static inline void 
acptrchk(const void *ptr,u32 msk,const char *file,int lno) 
{ 
#ifdef ACDEBUG2 

#if ACDEBUG2 >= 2 
    printf("acptrchk: TRACE ptr=%p msk=%8.8X file='%s' lno=%d\n", 
     ptr,msk,file,lno); 
#endif 

    if (((unsigned long) ptr) & msk) 
     acptrfault(ptr,file,lno); 
#endif 
} 

#endif 

Вот "ошибка" Функция обработчика:

// acbit/acptr -- alignment check macros 

#include <acbit/acptr.h> 
#include <acbit/acbit.h> 
#include <stdlib.h> 

// acptrfault -- pointer alignment fault 
void 
acptrfault(const void *ptr,const char *file,int lno) 
{ 

    // NOTE: it's easy to set a breakpoint on this function 

    printf("acptrfault: pointer fault -- ptr=%p file='%s' lno=%d\n", 
     ptr,file,lno); 

    exit(1); 
} 

И, вот пример программы, которая использует их:

// acbit/acbit3 -- sample allocator using check macros 

#include <acbit.h> 
#include <acptr.h> 

static double static_array[20]; 

// mymalloc3 -- allocation function 
void * 
mymalloc3(size_t len) 
{ 
    void *vp; 

    // get something valid 
    vp = static_array; 

    // do lots of stuff ... 
    printf("BEF vp=%p\n",vp); 

    // check pointer 
    // NOTE: these can be peppered after every [significant] calculation 
    ACPTR_TYPE(vp,double); 

    // do something bad ... 
    vp += 1; 
    printf("AFT vp=%p\n",vp); 

    // check again -- this should fault 
    ACPTR_TYPE(vp,double); 

    return vp; 
} 

int 
main(void) 
{ 
    int x; 

    setlinebuf(stdout); 

    // minimum alignment from malloc is [usually] 8 
    intptr = mymalloc3(256); 

    x = *(int *) intptr; 

    return x; 
} 

Вот выход программы:

BEF vp=0x601080 
acptrchk: TRACE ptr=0x601080 msk=00000007 file='acbit/acbit3.c' lno=22 
AFT vp=0x601081 
acptrchk: TRACE ptr=0x601081 msk=00000007 file='acbit/acbit3.c' lno=29 
acptrfault: pointer fault -- ptr=0x601081 file='acbit/acbit3.c' lno=29 

В этом примере я оставил код AC. В вашей реальной целевой системе разыскание intptr в main было бы/должно быть ошибкой при выравнивании, но обратите внимание, сколько позже это находится на временной шкале выполнения.

+0

Спасибо за ваш ответ. Похоже, что вы намеренно принудительно нелицензировали доступ сюда, увеличивая указатель на не-int-размер, а затем возвращаясь к int * (именно то, что я делаю в своем тесте, это его рабочий код), что на самом деле является видом ошибок Я пытаюсь поймать. Возможно, вы правы, что это не лучший способ проверить это. Другие предложили использовать perf или valgrind, которые могут быть приятными, если я могу заставить его работать. – skimon

+0

У меня есть _lots_ отладочного опыта для подобных вещей. Если вы можете отредактировать свой вопрос, чтобы подробно описать реальную проблему, я могу что-то предложить. Так как вы его не поймали в ловушку, как вы знаете, что у вас нет равных? Можете ли вы связать его с рядом переменных или функций? Вы получаете повреждение данных? –

+0

В основном я использую собственный собственный распределитель, который готовит мой код для работы в архитектуре, которая не поддерживает неровные чтения/записи, поэтому я хочу убедиться, что мой код не сломается на этой архитектуре. Я могу ловить негласное чтение/запись, используя либо asm, либо через gdb, но оба они вызывают SIGBUS, которые я не могу продолжить из gdb, и im получаю слишком много ложных срабатываний из библиотеки std (в том смысле, что их реализация будет выравниваться только на мишень).В идеале я предполагаю, что хотел бы использовать что-то вроде перфоманса, чтобы показать мне стоп-сигналы, которые были смещены, но до сих пор нет кубиков. – skimon