Gcc удаляет встроенный код ассемблера

Кажется, что gcc 4.6.2 удаляет код, который он считает неиспользованным из функций.

test.c

int main(void) {
  goto exit;
  handler:
    __asm__ __volatile__("jmp 0x0");
  exit:
  return 0;
}

Разборка main()

   0x08048404 <+0>:     push   ebp
   0x08048405 <+1>:     mov    ebp,esp
   0x08048407 <+3>:     nop    # <-- This is all whats left of my jmp.
   0x08048408 <+4>:     mov    eax,0x0
   0x0804840d <+9>:     pop    ebp
   0x0804840e <+10>:    ret

Параметры компилятора

Нет оптимизаций, просто gcc -m32 -o test test.c (-m32, потому что я на 64-битной машине).

Как я могу остановить это поведение?

Изменить: Предпочтительно, используя параметры компилятора, а не путем изменения кода.

Ответы

Ответ 1

Обновление 2012/6/18

Просто подумав об этом, можно положить goto exit в блок asm, а это значит, что нужно изменить только одну строку кода:

int main(void) {
  __asm__ ("jmp exit");

  handler:
    __asm__ __volatile__("jmp $0x0");
  exit:
  return 0;
}

Это значительно чище, чем мое другое решение ниже (и, возможно, более приятное, чем текущее значение @ugoren).


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

int main (void) {
  int x = 0;
  __asm__ __volatile__ ("" : "=r"(x));
  // compiler can't tell what the value of x is now, but it always 0

  if (x) {
handler:
    __asm__ __volatile__ ("jmp $0x0");
  }

  return 0;
}

Даже с -O3 сохраняется jmp:

    testl   %eax, %eax   
    je      .L2     
.L3:
    jmp $0x0
.L2:
    xorl    %eax, %eax 
    ret

(Это кажется действительно изворотливым, поэтому я надеюсь, что есть лучший способ сделать это. отредактируйте просто класть volatile перед x работает, поэтому вам не нужно делать встроенную азбуку asm.)

Ответ 2

Похоже, это так, как есть. Когда gcc видит, что код внутри функции недоступен, он удаляет его. Другие компиляторы могут отличаться. В gcc ранняя фаза компиляции - это построение "графика потока управления" - графика "базовых блоков", каждая из которых свободна от условий, связанных ветвями. При испускании фактического кода части графика, которые недоступны из корня, отбрасываются.
Это не является частью фазы оптимизации и поэтому не зависит от параметров компиляции.

Таким образом, любое решение предполагало бы, что gcc считает, что код доступен.

Мое предложение:

Вместо того, чтобы помещать код сборки в недостижимое место (где GCC может удалить его), вы можете поместить его в доступное место и пропустить проблемную инструкцию:

int main(void) {
     goto exit;

     exit:
     __asm__ __volatile__ (
        "jmp 1f\n"
        "jmp $0x0\n"
        "1:\n"
    );
    return 0;
}

Также см. этот поток об этой проблеме.

Ответ 3

Будет ли это работать, сделайте так, чтобы gcc не мог знать его недостижимый

int main(void)  
{ 
    volatile int y = 1;
    if (y) goto exit;
handler:
    __asm__ __volatile__("jmp 0x0");  
exit:   
    return 0; 
}

Ответ 4

Я никогда не слышал о том, как предотвратить gcc от удаления недостижимого кода; кажется, что независимо от того, что вы делаете, когда gcc обнаруживает недостижимый код, он всегда удаляет его (используйте параметр gcc -Wunreachable-code, чтобы увидеть, что он считает недостижимым).

Тем не менее, вы все равно можете поставить этот код в статическую функцию, и он не будет оптимизирован:

static int func()
{
    __asm__ __volatile__("jmp $0x0");
}

int main(void)
{
    goto exit;

handler:
    func();

exit:
    return 0;
}

P.S
Это решение особенно удобно, если вы хотите избежать избыточности кода при имплантации одного и того же кода кода "обработчик" в более чем одном месте в исходном коде.

Ответ 5

gcc может дублировать операторы asm внутри функций и удалять их во время оптимизации (даже при -O0), поэтому это никогда не будет работать надежно.

один из способов сделать это надежно - использовать глобальный оператор asm (т.е. оператор asm вне любой функции). gcc скопирует это прямо на выход, и вы можете без проблем использовать глобальные метки.

Ответ 6

Не могли бы вы попытаться добавить параметры -O0 в компилятор, который должен удалить любую оптимизацию, ket me know!

pedr0