ошибка сегментации (основной дамп) ошибка при использовании встроенной сборки - PullRequest
0 голосов
/ 15 февраля 2020

Я использую встроенную сборку в G CC. Я хочу повернуть содержимое переменной на 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 Ответ

3 голосов
/ 15 февраля 2020

Ключом к пониманию встроенного asm является понимание того, что каждое выражение asm состоит из двух частей:

  1. Текст фактического ассемблера, в котором компилятор будет выполнять текстовые замены, но не понимает .

    Это шаблон ассемблера в документации (все до первого : в __asm__()).

  2. Описание того, что делает ассемблер, в терминах, которые компилятор понимает .

    Это : OutputOperands : InputOperands : Clobbers в документация .

    Это должно сказать компилятору, как ассемблер вписывается во весь код, который генерирует компилятор вокруг него. Генерация кода занята тем, что выделяет регистры для хранения значений, решает, в каком порядке их выполнять, перемещает объекты из циклов, удаляет неиспользуемые фрагменты кода, отбрасывает значения, которые больше не нужны, и так далее.

    Фактический ассемблер - это черный ящик, который принимает описанные здесь входы, производит описанные выходы и в качестве побочного эффекта может «заглушить» некоторые регистры и / или память. Это должно быть полным описанием того, что делает ассемблер ... в противном случае сгенерированный компилятором ассемблер вокруг вашего шаблона возьмет с собой sh и будет полагаться на ложные предположения.

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

Итак, ваш фрагмент:

 asm volatile(
            "movq %0 , %%rax\n"
            "rol %%rax\n"
            "rol %%rax\n"
            :"=r"(X)
            :"r"(X)
         );

имеет несколько «проблем»:

  • вы, возможно, выбрали %rax для результата на основании того, что asm () похожа на функцию, и можно ожидать, что она вернет результат в %rax - но это не так.
  • вы пошли дальше и использовали %rax, который компилятор мог (ну) уже выделил чему-то другому ... так что вы, по сути, 'clobbering' %rax, но вы не сообщили об этом компилятору!
  • вы указали =r(X) ( OutputOperand ), который указывает компилятору ожидать вывода в некотором регистре и что вывод будет новым значением переменной X. %0 в шаблоне Assembler будет заменен регистром, выбранным для вывода. К сожалению, ваша сборка воспринимает %0 как входные данные :-( А выходные данные, на самом деле, находятся в %rax - о чем, как и выше, компилятору ничего не известно.
  • вы также указан r(X) ( InputOperand ), который указывает компилятору упорядочить текущее значение переменной X для помещения в некоторый регистр для использования ассемблером. Это будет %1 в AssemblerTemplate . К сожалению, ваша сборка не использует этот ввод.

    Несмотря на то, что оба операнда вывода и ввода относятся к X, компилятор может не сделать %0 таким же регистром, как %1. (Это позволяет использовать блок asm в качестве неразрушающей операции, при которой исходное значение ввода остается неизменным. Если это не так, как работает ваш шаблон, не пишите его таким образом.

  • , как правило, вам не нужен volatile, когда все входы и выходы правильно описаны ограничениями. Одна из прекрасных вещей, которые сделает компилятор, - отбросить asm(), если (все) выходные данные ( с) не являются used ... 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 должен быть сохранен.

  • Компилятор ожидает, что все InputOperands будут прочитаны до any OutputOperand is написано. Это важно, когда компилятор знает, что данный InputOperand больше не используется после asm(), и поэтому он может выделить регистр InputOperand для OutputOperand . Есть вещь под названием earlyclobber (=&r(foo)), чтобы справиться с этой маленькой морщинкой.

В приведенном выше примере, если вы фактически не используете X снова, компилятор может выделить %0 и %1 для одного и того же регистра! Но (избыточный) mov все равно будет собран - помня, что компилятор действительно не понимает AssemblerTemplate . Таким образом, в целом вам лучше переставлять значения в C, а не asm(). См. https://gcc.gnu.org/wiki/DontUseInlineAsm и Лучшие практики для операций кругового сдвига (поворота) в C ++


Итак, здесь есть четыре варианта темы и сгенерированный код (g cc -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 где вы описываете, что делает ассемблер.

...