GCC/x86 inline asm: Как вы сообщаете gcc, что секция сборки inline будет изменять% esp?

При попытке повторного использования какого-то старого кода (https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387, FWIW) я обнаружил, что некоторые из семантики gcc кажутся изменился довольно тонким, но все же опасным образом в течение последних 10-15 лет...: P

Код, который хорошо подходит для старых версий gcc, например 2.95. Во всяком случае, вот код:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
    tag_type *identification)
{
    return_type return_value;

    asm volatile("pushl %2\n"
                 "pushl %3\n"
                 "pushl %4\n"
                 "lcall %5, $0"
                 : "=a" (return_value),
                   "=g" (*service_parameter)
                 : "g" (identification),
                   "g" (service_parameter),
                   "g" (protocol_name),
                   "n" (SYSTEM_CALL_SERVICE_GET << 3));

    return return_value;
}

Проблема с вышеприведенным кодом заключается в том, что gcc (4.7 в моем случае) скомпилирует это со следующим кодом asm (синтаксис AT & T):

# 392 "../system/system_calls.h" 1
pushl 68(%esp)  # This pointer (%esp + 0x68) is valid when the inline asm is entered.
pushl %eax
pushl 48(%esp)  # ...but this one is not (%esp + 0x48), since two dwords have now been pushed onto the stack, so %esp is not what the compiler expects it to be
lcall $456, $0

# Restoration of %esp at this point is done in the called method (i.e. lret $12)

Проблема: переменные (identification и protocol_name) находятся в стеке в вызывающем контексте. Таким образом, gcc (с оптимизацией получилось, не зная, имеет ли значение), просто получит значения оттуда и передаст его в секцию inline asm. Но, поскольку я нажимаю вещи в стеке, смещения, которые вычисляются gcc, будут отключены на 8 в третьем вызове (pushl 48(%esp)).:)

Мне потребовалось много времени, чтобы понять, сначала это было не все очевидно.

Самый простой способ - это, конечно, использовать входное ограничение r, чтобы убедиться, что это значение находится в регистре. Но есть ли другой, лучший способ? Разумеется, одним из очевидных способов было бы переписать весь интерфейс системного вызова, чтобы не вставлять вещи в стек в первую очередь (и вместо этого использовать регистры, например, Linux), но это не рефакторинг, который мне нравится делать сегодня вечером...

Есть ли способ сообщить gcc inline asm, что "стек неустойчив"? Как вы, ребята, занимались такими вещами в прошлом?


Обновление позже в тот же вечер. Я нашел соответствующий поток gcc ML (https://gcc.gnu.org/ml/gcc-help/2011-06/msg00206.html) но, похоже, это не помогло. Кажется, что указание %esp в списке clobber должно заставить его делать смещения от %ebp вместо этого, но это не работает, и я подозреваю, что эффект -O2 -fomit-frame-pointer имеет здесь эффект. У меня есть оба этих флажка.

Ответ 1

Что работает, а что нет:

  • Я пробовал пропустить -fomit-frame-pointer. Никакого эффекта. Я включил %esp, esp и sp в список clobbers.

  • Я пробовал пропустить -fomit-frame-pointer и -O3. Это действительно создает код, который работает, поскольку он полагается на %ebp, а не на %esp.

    pushl 16(%ebp)
    pushl 12(%ebp)
    pushl 8(%ebp)
    lcall $456, $0
    
  • Я пробовал только с -O3, а не -fomit-frame-pointer, указанным в моей командной строке. Создает плохой, сломанный код (полагается на %esp постоянным во всем блоке сборки, т.е. Нет кадра стека).

  • Я пробовал пропустить -fomit-frame-pointer и просто использовать -O2. Сломанный код, нет кадра стека.

  • Я пытался использовать только -O1. Сломанный код, нет кадра стека.

  • Я попробовал добавить cc как clobber. Нет, это не имеет никакого значения.

  • Я попытался изменить ограничения ввода на ri, указав код ввода и вывода ниже. Это, конечно, работает, но немного менее изящно, чем я надеялся. Опять же, perfect является противником хорошего, поэтому, возможно, мне придется жить с этим на данный момент.

Код входа C:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
    tag_type *identification)
{
    return_type return_value;

    asm volatile("pushl %2\n"
                 "pushl %3\n"
                 "pushl %4\n"
                 "lcall %5, $0"
                 : "=a" (return_value),
                   "=g" (*service_parameter)
                 : "ri" (identification),
                   "ri" (service_parameter),
                   "ri" (protocol_name),
                   "n" (SYSTEM_CALL_SERVICE_GET << 3));

    return return_value;
}

Выходной код asm. Как видно, использование регистров вместо этого всегда должно быть безопасным (но, возможно, несколько менее результативным, поскольку компилятор должен перемещать вещи):

#APP
# 392 "../system/system_calls.h" 1
pushl %esi
pushl %eax
pushl %ebx
lcall $456, $0