ошибка сегментации (дамп ядра) ошибка при использовании встроенной сборки
Я использую встроенную сборку в GCC. Я хочу повернуть содержимое переменной на 2 бита влево (я переместил переменную в регистр rax, а затем повернул ее 2 раза). Я написал приведенный ниже код, но столкнулся с ошибкой сегментации (дамп ядра). Буду признателен, если вы мне поможете.
uint64_t X = 13835058055282163712U;
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
printf("%" PRIu64 "\n" , X);
1 ответ
Ключом к пониманию встроенного asm является понимание того, что каждый оператор asm состоит из двух частей:
Текст фактического материала ассемблера, в котором компилятор будет делать текстовые замены, но не понимает.
Это AssemblerTemplate в документации (все до первого
:
в__asm__()
).Описание того, что делает ассемблер, в терминах, понятных компилятору.
Это
: OutputOperands : InputOperands : Clobbers
в документации.Это должно сообщить компилятору, как ассемблер вписывается во весь код, который компилятор создает вокруг него. Генерация кода занята распределением регистров для хранения значений, принятием решения, в каком порядке выполнять действия, выводом вещей из циклов, удалением неиспользуемых фрагментов кода, отбрасыванием значений, которые ему больше не нужны, и так далее.
Фактический ассемблер - это черный ящик, который принимает описанные здесь входные данные, производит описанные выходные данные и в качестве побочного эффекта может "затирать" некоторые регистры и / или память. Это должно быть полное описание того, что делает ассемблер... иначе сгенерированный компилятором asm вокруг вашего шаблона будет конфликтовать с ним и будет полагаться на ложные предположения.
Вооружившись этой информацией, компилятор может решить, какие регистры может использовать ассемблер, и вы должны позволить ему это сделать.
Итак, ваш фрагмент:
asm volatile(
"movq %0 , %%rax\n"
"rol %%rax\n"
"rol %%rax\n"
:"=r"(X)
:"r"(X)
);
есть несколько "проблем":
- вы могли выбрать
%rax
для результата на основании того, что asm() подобен функции, и можно ожидать, что он вернет результат в%rax
- но это не так. - вы пошли дальше и использовали
%rax
, который компилятор может (хорошо) уже выделил для чего-то еще... так что вы, по сути, `` затираете ''%rax
но вы не смогли сообщить об этом компилятору! - вы указали
=r(X)
(OutputOperand), который сообщает компилятору ожидать вывода в некотором регистре, и этот вывод будет новым значением переменной.X
. В%0
в AssemblerTemplate будет заменен регистром, выбранным для вывода. К сожалению, ваша сборка лечит%0
как вход:-(А выход, собственно, в%rax
- о чем, как и выше, компилятор не знает. вы также указали
r(X)
(InputOperand), который сообщает компилятору организовать текущее значение переменнойX
быть помещенным в какой-либо регистр для использования ассемблером. Это было бы%1
в AssemblerTemplate. К сожалению, ваша сборка не использует этот ввод.Несмотря на то, что операнды вывода и ввода относятся к
X
, компилятор может не сделать%0
тот же регистр, что и%1
. (Это позволяет использовать блок asm в качестве неразрушающей операции, которая оставляет исходное значение ввода неизменным. Если ваш шаблон работает не так, не пишите его таким образом.- обычно вам не нужен
volatile
когда все входы и выходы должным образом описываются ограничениями. Одна из замечательных вещей, которые сделает компилятор, - это отброситьasm()
если (все) выход (ы) не используются...volatile
говорит компилятору не делать этого (и сообщает ему ряд других вещей... см. руководство).
Кроме того, все было замечательно. Следующее безопасно и позволяет избежатьmov
инструкция:
asm("rol %0\n"
"rol %0\n" : "+r"(X));
где "+r"(X)
говорит, что требуется один комбинированный регистр ввода и вывода, принимая старое значение X
и вернув новый.
Теперь, если вы не хотите заменять X
, то предполагая Y
в результате вы можете:
asm("mov %1, %0\n"
"rol %0\n"
"rol %0\n" : "=r"(Y) : "r"(X));
Но лучше предоставить компилятору решать, нужно ли ему mov
или может ли он просто позволить уничтожить ввод.
Есть несколько правил относительно InputOperands, о которых стоит упомянуть:
Ассемблер не должен перезаписывать ни один из InputOperands - компилятор отслеживает, какие значения он имеет в каких регистрах, и ожидает сохранения InputOperands.
Компилятор ожидает, что все InputOperand будут прочитаны перед записью любого OutputOperand. Это важно, когда компилятор знает, что данный InputOperand больше не используется после
asm()
, и поэтому он может выделить регистр InputOperand для OutputOperand. Есть штука под названием Earlyclobber (=&r(foo)
), чтобы избавиться от этой маленькой морщинки.
В приведенном выше примере, если вы на самом деле не используете X
снова компилятор мог выделить %0
а также %1
в тот же реестр! Но (лишний)mov
все равно будет собрана - помня, что компилятор действительно не понимает AssemblerTemplate. Итак, вам в целом лучше перемешивать значения в C, а не вasm()
. См. https://gcc.gnu.org/wiki/DontUseInlineAsm и Лучшие практики для операций кругового сдвига (поворота) в C++
Итак, вот четыре варианта темы и сгенерированный код (gcc -O2):
// (1) uses both X and Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi # address of format string
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi # X = 99
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # 2nd asm, compiler using it as a copy-and-rotate
) ; rol %rdx
rol %rdx
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt> # tailcall printf
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (2) uses both X and Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi # 1st asm
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rdx # compiler-generated mov
) ; rol %rdx # 2nd asm
rol %rdx
Y = X ; jmpq 0x4010a0 <printf@plt>
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx %lx\n", X, Y) ;
}
// (3) uses only Y in the printf() -- does mov %1, %0 in asm()
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) mov %rsi,%rsi # redundant instruction because of mov in the asm template
) ; rol %rsi
rol %rsi
__asm__("\t mov %1, %0\n" jmpq 0x4010a0 <printf@plt>
"\t rol %0\n"
"\t rol %0\n" : "=r"(Y) : "r"(X)
) ;
printf("%lx\n", Y) ;
}
// (4) uses only Y in the printf() -- does Y = X in 'C'
void Never_Inline footle(void) Dump of assembler code for function footle:
{ mov $0x492782,%edi
unsigned long X, Y ; xor %eax,%eax
mov $0x63,%esi
X = 99 ; rol %rsi
__asm__("\t rol %0\n" rol %rsi
"\t rol %0\n" : "+r"(X) rol %rsi # no wasted mov, compiler picked %0=%1=%rsi
) ; rol %rsi
jmpq 0x4010a0 <printf@plt>
Y = X ;
__asm__("\t rol %0\n"
"\t rol %0\n" : "+r"(Y)
) ;
printf("%lx\n", Y) ;
}
который, как мы надеемся, демонстрирует, как компилятор активно распределяет значения по регистрам, отслеживает, какие значения ему необходимо удерживать, минимизирует регистры / перемещения регистров и, как правило, является умным.
Итак, фокус в том, чтобы работать с компилятором, понимая, что :
OutputOperands:
InputOperands:
Clobbers - это то место, где вы описываете, что делает ассемблер.