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