2016-08-18 9 views
1

Я пытаюсь загрузить/сохранить память из/в массив указателей символов, используя 128-битный регистр в 32-разрядной операционной системе.Загрузка регистров XMM из адреса

То, что я пытался очень просто:

int main() { 
    char *data = new char[33]; 
    for (int i = 0; i < 32; i++) 
     data[i] = 'a'; 
    data[32] = 0; 
    ASM 
    { 
     movdqu xmm0,[data] 
    } 

    delete[] data; 
} 

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

XMM0 = 0024F8380000000000F818E30055F158

второй раз, когда я отлажена это я получил:

XMM0 = 0043FD6800000000002C18E3008CF158

Так что должно быть что-то с линией:

movdqu xmm0,[data] 

Я попытался использовать это вместо того, чтобы:

movdqu xmm0,data 

, но я получил тот же результат.

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

Я также пробовал некоторые другие инструкции, которые я нашел в Интернете, но с тем же результатом.

Это то, как я передаю указатель, или я что-то не понимаю о основах xmm?

Важное решение с объяснением будет оценено.

Даже если я нашел решение (в конце концов, после трех часов), я бы до сих пор, как объяснение:

ASM 
    { 
     push eax 
     mov eax,data 
     movdqu xmm0,[eax] 
     pop eax 
    } 

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

+0

Обратите внимание, что 'data' является указателем. –

+0

Можете ли вы попробовать, если локальная переменная 'char data [33];' вместо нового/delete с указателем может использоваться напрямую, как в исходном сообщении с '[data]'? Я не могу отлаживать сейчас, но я думаю, что это может сработать, поскольку я могу представить скомпилированный источник. Что меня озадачивает, какова разница на C++ от 'char * data'. С точки зрения C++ они выглядят эквивалентными. Я, вероятно, что-то пропускаю. (и в этой второй версии, что 'mov eax, data' скомпилирован в' mov eax, [data] ', right?) – Ped7g

+4

x86 не имеет режима« косвенной адресации »памяти. Вы загружаете указатель в 'xmm0'. Поскольку 'xmm0' больше, чем указатель, вы также читаете байты мусора в памяти за пределами того места, где хранится указатель. –

ответ

1
#include <iostream> 

int main() 
{ 
    char *dataptr = new char[33]; 
    char datalocal[33]; 
    dataptr[0] = 'a'; dataptr[1] = 0; 
    datalocal[0] = 'a'; datalocal[1] = 0; 
    printf("%p %p %c\n", dataptr, &dataptr, dataptr[0]); 
    printf("%p %p %c\n", datalocal, &datalocal, datalocal[0]); 
    delete[] dataptr; 
} 

Выход:

0xd38050 0x7635bd709448 a 
0x7635bd709450 0x7635bd709450 a 

Как мы можем видеть, динамический указатель data действительно переменная указатель (32 бит или 64 бит в 0x7635BD709448), содержащий указатель на кучу, 0xD38050.

Локальная переменная - это буфер длиной 33 символа, выделенный по адресу 0x7635BD709450.

Но datalocal работает также как значение char *.

Я немного смущен, что формальное объяснение этого С ++.При написании кода на C++ это кажется вполне естественным, и dataptr [0] - первый элемент в кучевой памяти (т. Е. Разыменование dataptr дважды), но в ассемблере вы видите истинный характер dataptr, который является адресом переменной указателя. Таким образом, вы должны сначала загрузить указатель кучи на mov eax,[data] = загружает eax с 0xD38050, а затем вы можете загрузить содержимое 0xD38050 в XMM0, используя [eax].

С локальной переменной нет переменной с ее адресом; символ datalocal уже является адресом первого элемента, поэтому тогда будет работать movdqu xmm0,[data].

В «неправильном» случае вы все еще можете сделать movdqu xmm0,[data]; это не проблема CPU для загрузки 128 бит из 32-битной переменной. Он просто продолжит чтение за 32 бита и прочитает еще 96 бит, принадлежащих другим переменным/кодом. Если вы находитесь вокруг границы памяти, и это последняя страница памяти приложения, она выйдет из строя с недопустимым доступом.


Выравнивание упоминалось несколько раз в комментариях. Это действительная точка; для доступа к памяти через movdqu он должен быть выровнен. Проверьте свои встроенные компиляторы C++. Для Visual Studio это должно работать:

__declspec(align(16)) char datalocal[33]; 
char *dataptr = _aligned_malloc(33, 16); 
_aligned_free(dataptr); 

О моей интерпретации C++: Может быть, я получил это неправильно с самого начала.

dataptr - это значение символа dataptr, то есть этого адреса кучи. Затем dataptr[0] разыменовывает адрес кучи, обращаясь к первому элементу выделенной памяти. &dataptr - это адрес значения dataptr. Это имеет смысл также с синтаксисом, например dataptr = nullptr;, где вы храните значение nullptr в переменной dataptr, а не переписываете адрес символа dataptr.

С datalocal[] нет в принципе нет смысла в получении доступа к чистой datalocal, как в datalocal = 'a';, так как это переменная массива, так что вы всегда должны обеспечить индекс []. И &datalocal - это адрес такого массива. Чистый datalocal - это сглаженный ярлык для более простой математики точек с массивами и т. Д., Также имеющий тип char *, но если чистый datalocal будет вызывать синтаксическую ошибку, все же можно будет написать код C++ (используя &datalocal для указателя, datalocal[..] для элементов), и это полностью соответствовало бы логике dataptr.

Заключение: У вас ваш пример неправильно с самого начала, потому что на языке ассемблера [data] загружается значение data, который является указателем на куче возвращенного new.

Это мое собственное объяснение, и теперь эксперт некоторыхов C++ придет и разорвать его на куски с формальной точки зрения ... :)))

+0

В большинстве контекстов (например, [передача как функция arg] (http://stackoverflow.com/questions/38800044/what-kind-of-c11-data-type-is-an-array-according-to-the- amd64-abi # comment64984890_38800044), или при использовании с такими операторами, как '+' или '[]'), массивы работают как указатели. Однако адрес не хранится нигде; это больше похоже на постоянную константу. Или смещение времени компиляции с указателем стека. Но указательная переменная * действительно хранит указатель в памяти или в регистре.BTW, '& datalocal' дает предупреждение, но компилируется с тем же кодом, что и' & datalocal [0] '. https://godbolt.org/g/05S5XS –

+0

Я думал, что «movdqu' был неглавным доступом? Если это так, то не требуется выравнивание. Если он, как известно, выровнен, тогда я бы предложил 'movdqa' –

3

Проблемы с кодом data является указателем. Код сборки movdqu xmm0,[data] загружает 16 байтов по адресу data в регистр xmm0. Это означает, что 4 или 8 байтов содержат значение указателя и любые байты, которые следуют в памяти. Вам повезло, что адрес указателя правильно выровнен в памяти, иначе вы получите ошибку сегментации. Ничто не гарантирует это выравнивание.

Альтернативы с использованием автоматического массива char data[33]; бы решить проблему адресации (movqdu будет загружать данные из массива), но не вопрос выравнивания, вы можете все еще получить нарушения в зависимости от того, как компилятор выравнивает массив с автоматическим хранением. Опять же, нет гарантии правильного выравнивания.

Решение, которое вы нашли, вероятно, является хорошим подходом, но в отличие от malloc(), я не уверен, верен ли указатель new для любого выравнивания.

Это должно работать во всех случаях:

#include <stdlib.h> 

int main(void) { 
    char *data = malloc(33); 
    for (int i = 0; i < 32; i++) { 
     data[i] = 'a'; 
    } 
    data[32] = 0; 
    __asm { 
     mov eax, data 
     movdqu xmm0, [eax] 
    } 
    free(data); 
    return 0; 
} 

Как отметил Питер Кордес, гораздо лучше использовать для этого встроенные функции рода вещи, а именно mm_loadu_si128. Есть две основные причины: во-первых, встроенная сборка не поддерживается для 64-битных сборок, поэтому, используя встроенные функции, ваш код становится немного более портативным. Во-вторых, компилятор делает относительно плохую работу по оптимизации встроенной сборки и, в частности, имеет тенденцию делать много бессмысленных хранилищ и нагрузок памяти. Компилятор делает намного лучшую оптимизацию работы, которая заставляет ваш код работать быстрее (что является целым рядом с использованием встроенной сборки!).

+0

извините за то, что не сдался, но не имеет 15 rep: X – user2377766

+0

@ user2377766: это должно прийти быстро ;-) – chqrlie

+2

Не используйте push/pop внутри оператор inline-asm. MSVC читает ваш asm и сохраняет/восстанавливает любые регистры, которые вы используете. Что еще более важно, не используйте MSVC inline asm для этого вообще. Вы получите лучшие результаты с внутренними характеристиками. –