#c #visual-c #assembly #c-preprocessor #inline-assembly
#c #visual-c #сборка #c-препроцессор #встроенная сборка
Вопрос:
Я пытаюсь достичь логической арифметики вместе с _asm _emit
в Visual Studio 2015, хочу вставить некоторые коды операций x64 в программу x86, я должен выполнить много mov
команд в памяти, поэтому я попытался создать какой-то макрос, который принимает адрес и выдает в строчном порядке, так что:
#define EmitDword(x)
{
_asm _emit (x amp; 0x000000FF)
_asm _emit ((x >> 8) amp; 0x000000FF)
_asm _emit ((x >> 16) amp; 0x000000FF)
_asm _emit ((x >> 24) amp; 0x000000FF)
}
Но я получаю сообщение об ошибке inline assembler syntax error in 'first operand';
Идея в том, чтобы передать адрес переменной макросу, чтобы он передавался непосредственно в машинный код, тогда я мог бы сделать что-то вроде этого:
#define EmitDword(x)
{
_asm _emit (x amp; 0x000000FF)
_asm _emit ((x >> 8) amp; 0x000000FF)
_asm _emit ((x >> 16) amp; 0x000000FF)
_asm _emit ((x >> 24) amp; 0x000000FF)
}
/* mov qword ptr [addr],reg */
#define X64_MovToMem(addr,reg)
{
_asm _emit 0x48
_asm _emit 0x89
_asm _emit reg
_asm _emit 0x25
EmitDword(addr)
}
#define _rax 4
void test()
{
DWORD64 someData;
X64_MovToMem(amp;someData,_rax);
}
Есть ли какой-либо способ добиться выдачи непостоянных значений с использованием встроенной сборки препроцессора?
Комментарии:
1. Нет, каждый байт, который вы передаете в asm-вывод компилятора, должен быть константой времени компиляции. Я сомневаюсь, что вам повезет создать полезный код x86-64 с этой попыткой обойти MSVC, поддерживающий только
_asm
32-разрядный режим. Вероятно, вам следует просто писать функции в asm и вызывать их из C , потому что нет способа сообщить компилятору о действиях любых инструкций, которые вы вставляете.2. Также обратите внимание, что адреса являются константами времени соединения, а не во время компиляции, поэтому измельчение адреса с помощью макроса не сработает: компилятор и ассемблер не могут знать каждый байт отдельно, и им нужно просто оставить пробел и поместить перемещение в таблицу символов объектного файла. Значение будет заполнено компоновщиком во время компоновки.
3. Если вам просто нужны инструкции asm в виде данных, и вы кодируете их самостоятельно, почему бы не использовать struct и memcpy?
Ответ №1:
Вам не нужно возиться с 64-разрядным кодом в 32-разрядном процессе, чтобы получить доступ к 64-разрядному блоку Process Environment. Вы можете получить его адрес, используя 32-разрядный код, и он находится в пределах 32-разрядного адресного пространства. Вам нужно будет использовать 64-разрядный код, только если вам нужно получить доступ к памяти, выделенной за пределами 32-разрядного адресного пространства, и я не думаю, что Windows когда-либо сделает это в 32-разрядном процессе.
Если вам действительно нужно было иметь 64-разрядную функцию в 32-разрядном исполняемом файле, есть лучший способ сделать это, чем использовать _asm _emit
. Первое, что нужно сделать, это написать всю вашу 64-разрядную функцию в простой сборке и собрать ее с помощью обычного внешнего ассемблера. Например, вот функция, которая считывает данные из 64-разрядного указателя в синтаксисе MASM:
_TEXT SEGMENT
__read64ptr:
mov rax, [rsp 8]
mov eax, [rax]
mov edx, [rax 4]
retf
_TEXT ENDS
END
Эта простая функция принимает 64-разрядный указатель в качестве аргумента в стеке. 64-разрядное значение, расположенное по указанному адресу, помещается в EAX и EDX. Эта функция предназначена для вызова с помощью 32-разрядной инструкции far call.
Обратите внимание, что возвращаемое значение занимает два 32-разрядных слота стека, один для 32-разрядного смещения адреса возврата, а другой для селектора. Несмотря на то, что команда RETF выполняется в 64-разрядном режиме, по умолчанию она использует 32-разрядный размер стека (в отличие от 64-разрядной команды near RET) и будет корректно работать с 32-разрядным адресом возврата far, сохраненным в стеке.
К сожалению, мы не можем использовать этот файл сборки напрямую с инструментами, поставляемыми с Visual Studio. 64-разрядная версия MASM создает только 64-разрядные объектные файлы, а компоновщик не позволяет нам смешивать 32-разрядные и 64-разрядные объектные файлы. Должна быть возможность собрать 64-разрядный код в 32-разрядный объект с помощью NASM и связать с компоновщиком Microsoft, но можно использовать код косвенно, используя только инструменты Microsoft.
Для этого соберите файл и скопируйте машинный код вручную в массив C, который находится в .text
разделе:
#pragma code_seg(push, ".text")
#pragma code_seg(pop)
char const __declspec(allocate(".text")) _read64ptr[] = {
0x48, 0x8b, 0x44, 0x24, 0x08, /* mov rax, [rsp 8] */
0x8b, 0x00, /* mov eax. [rax] */
0x8b, 0x50, 0x04, /* mov edx, [rax 4] */
0xcb /* retf */
};
Чтобы вызвать его, вам просто нужно использовать код, подобный этому:
struct {
void const *offset;
unsigned short selector;
} const _read64ptr_ind = { _read64ptr, 0x33 };
unsigned long long
read64ptr(unsigned long long address) {
unsigned long long value;
_asm {
push DWORD PTR [address 4]
push DWORD PTR [address]
call FWORD PTR [_read64ptr_ind]
add esp, 8
mov DWORD PTR [value], eax
mov DWORD PTR [value 4], edx
}
return value;
}
Косвенное обращение с помощью _read64ptr_ind
необходимо, потому что нет способа написать call 33h:_read64ptr
во встроенной сборке Microsoft. Также обратите внимание, что 64-разрядный селектор кода 0x33
жестко запрограммирован в этом примере, надеюсь, он не изменится.
Вот пример, в котором приведенный выше код используется для считывания адреса 64-разрядного PEB-файла из 64-разрядного TEB-файла (хотя оба расположены в 32-разрядном адресном пространстве):
unsigned long long
readgsqword(unsigned long off) {
unsigned long long value;
_asm {
mov edx, [off]
mov eax, gs:[edx]
mov edx, gs:[edx 4]
mov DWORD PTR [value], eax
mov DWORD PTR [value 4], edx
}
return value;
}
int
main() {
printf("32-bit TEB address lxn",
__readfsdword(offsetof(NT_TIB, Self)));
printf("32-bit PEB address lxn", __readfsdword(0x30));
unsigned long long teb64 = readgsqword(offsetof(NT_TIB64, Self));
printf("64-bit TEB address 6llxn", teb64);
printf("64-bit PEB address 6llxn", readgsqword(0x60));
printf("64-bit PEB address 6llxn", read64ptr(teb64 0x60));
}
Запуск его на моем компьютере генерирует следующий результат:
32-bit TEB address 7efdd000
32-bit PEB address 7efde000
64-bit TEB address 000000007efdb000
64-bit PEB address 000000007efdf000
64-bit PEB address 000000007efdf000
Как вы можете видеть, доступ ко всем структурам возможен с использованием 32-разрядных указателей и без какого-либо 64-разрядного кода. В частности, в примере показано, как получить 32-разрядный указатель на 64-разрядный PEB, используя только 32-разрядный код.
И последнее замечание: нет гарантии, что Windows правильно обработает 64-разрядный код, выполняемый в 32-разрядном процессе. Если в любой момент во время выполнения 64-разрядного кода произойдет прерывание, процесс может закончиться сбоем.
Комментарии:
1. Здравствуйте, спасибо за ответ. Я приму этот ответ, поскольку он является лучшим, и, похоже, единственное, что действительно хорошо сделать, это написать код 64 masm, собрать его и скопировать напрямую. Дело в том, что мне нужно получить доступ к ячейкам памяти x64. Я хочу выполнить цикл через PEB_LDR_DATA, а в Windows 10 его адрес равен 64 битам. Также некоторые записи с буферами wchar с именами загруженных модулей (UNICODE_STRING64) также имеют адреса x64. У меня есть еще один вопрос, можете ли вы объяснить о возможном системном прерывании, которое может прервать выполнение процесса wow64 в случае его срабатывания?
2. @Vlad: Поскольку ядро считает, что это 32-разрядный процесс, он может вернуться в пространство пользователя в 32-разрядном режиме совместимости, даже если прерывание произошло, когда пространство пользователя находилось в 64-разрядном режиме long.
3. Возможно, вам удастся создать правильную кодировку для прямого удаленного вызова, используя
push whatever
/db 9Ah
/dd _read64ptr
/dw 33h
в MSVC inline-asm. В отличие от попытки операционной системы сократить значение времени соединения во время компиляции, это должно позволить компоновщику позаботиться о заполнении 4-байтового адреса.4. @PeterCordes К сожалению, по какой-либо причине директивы DB, DD и т.д. Не работают во встроенной сборке Microsoft. Вместо этого нужно использовать
_emit
, но это работает только с байтовыми значениями.
Ответ №2:
Извините, но то, что вы пытаетесь сделать, не имеет смысла.
- «вставьте некоторые коды операций x64 в программу x86» — если это программа x86, она не будет запускать коды операций x64.
- «_asm _emit (x amp; 0x000000FF)» — Вы читали документы по emit? Вы можете выдавать только байты, но не C-код.
- «emit emit испускать» — Почему вы думаете, что выполнение 4 операций записи по 1 байту каждая (чего этот код все равно не делает) будет быстрее, чем выполнение 1 операции записи по 4 байта?
- «Есть ли какой-либо способ добиться выдачи непостоянных значений» — Если вы используете emit, то вам нужно будет знать, в каком регистре в данный момент хранится значение, которое вы пытаетесь записать (что практически невозможно выполнить, поскольку компилятор может использовать разные регистры при каждом изменении кода, и значения может даже не быть в регистре).
Я пытаюсь не обращать внимания на то, как вы это пишете, чтобы решить вашу первоначальную проблему. Однако:
Я понимаю, что существует тенденция думать, что «asm быстрее». Но помните, что код C переводится на ассемблер. Компилятор уже генерирует asm-код, который почти наверняка намного эффективнее того, что вы можете скомпоновать с помощью emit.
И если использование 64-битных инструкций приведет к созданию лучшего кода (что, безусловно, возможно), вам следует создать 64-битный исполняемый файл.
Если вы убеждены, что можете создать asm-код, более эффективный, чем компилятор C, рассмотрите возможность создания целой asm-подпрограммы, затем вызовите ее из своего C-кода. Обратите внимание, что вы не сможете связать ассемблер x64 с вашей программой x86.
Комментарии:
1. Я думаю, что он пытается передать 4 байта адреса в порядке младших порядков, а не выдавать инструкцию, которая на самом деле вычисляет
x amp; 0x000000FF
. Я согласен, что это, похоже, не имеет смысла. Я не совсем уверен, что он хочет запускать выданный машинный код, учитывая, что он говорит «выдавать коды операций x64 в программу x86», поэтому я предложил просто использовать struct или что-то в этом роде. Я думал о публикации ответа, но я не думал, что вопрос имеет достаточно смысла, чтобы отвечать.2. Его замечание о
i must do a lot of mov to memory commands
звучит так, будто целью является повышение производительности. Убеждение, что «все знают, что asm быстрее, чем C», в сочетании с ограниченным пониманием того, как работает asm, может привести к возникновению подобного вопроса. Попытка использовать x64 была бы еще одной попыткой ускорить работу.3. О да, я пытался найти способ интерпретации вопроса, отличный от того, где ответ «конечно, нет, это никак не может сработать». Но я думаю, что вы, возможно, правы, основываясь на этом предложении в вопросе.
4. Я пытаюсь выполнить 64-разрядный код в контексте процесса x86 во время выполнения. Это возможно, выполнив дальний переход к селектору
0x33
, который обозначает сегмент кода x64.5. Это потому, что вы считаете, что вызывать x64-код из x86-кода круто, и вы хотите попробовать это? Или вы надеетесь ускорить работу? Или есть какая-то другая цель?