Как НЕ использовать стек с вызовом функции в GCC?

#assembly #gcc #arm #stack #callstack

#сборка #gcc #arm #стек #callstack

Вопрос:

Вот контекст для моего вопроса:

  • Я использую архитектуру ARM7 (ARM720T TDMI …)
  • Скомпилируйте с использованием GCC codesourcedy (arm-none-eabi версия 4.5.2)
  • Я новичок в архитектуре gcc и ARM, но проработал в embedded 5 лет 😉

В моем проекте функция C вызывается из файла сборки до инициализации стека. Поскольку стек не инициализирован, функция не должна использовать стек.

Возможно ли с помощью какой-либо «подобной прагме» команды заставить gcc не использовать стек с этой конкретной функцией?

Дополнительная информация: Цель моей работы — преобразовать проект, ранее скомпилированный с помощью ARMASM, в gcc. Итак, в ARMASM вызов этой функции C до инициализации стека работал. Возможно, окончательный ответ будет заключаться в том, что это невозможно сделать в gcc…

Ниже приведена выдержка из списка ELF для функции C, вызываемой в коде сборки (как вы можете видеть, я пробовал always_inline, но при вызове из сборки этого, похоже, недостаточно):

 000049d0 <CInit_Init>:
__attribute__((always_inline)) extern void CInit_Init(void) {
49d0:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
49d4:   e28db000    add fp, sp, #0

__attribute__((always_inline)) void COM1_Init(void);

__attribute__((always_inline)) extern inline void COM1_Init_I(void) {
// Skip if already enabled
if (TEST_BIT_CLR(HwrSysControl1, HwSysControl1UartEnable)) {
49d8:   e59f3038    ldr r3, [pc, #56]   ; 4a18 <CInit_Init 0x48>
49dc:   e5933000    ldr r3, [r3]
49e0:   e2033c01    and r3, r3, #256    ; 0x100
49e4:   e3530000    cmp r3, #0
49e8:   1a000007    bne 4a0c <CInit_Init 0x3c>
    HwrUart1Control = (
49ec:   e59f3028    ldr r3, [pc, #40]   ; 4a1c <CInit_Init 0x4c>
49f0:   e59f2028    ldr r2, [pc, #40]   ; 4a20 <CInit_Init 0x50>
49f4:   e5832000    str r2, [r3]
            HwUartControlDataLength8|
            HwUartControlFifoEnable|
            HwUartControlRate115200);
    BIT_SET(HwrSysControl1, HwSysControl1UartEnable);
49f8:   e59f3018    ldr r3, [pc, #24]   ; 4a18 <CInit_Init 0x48>
49fc:   e59f2014    ldr r2, [pc, #20]   ; 4a18 <CInit_Init 0x48>
4a00:   e5922000    ldr r2, [r2]
4a04:   e3822c01    orr r2, r2, #256    ; 0x100
4a08:   e5832000    str r2, [r3]

COM1_Init_I();
}
4a0c:   e28bd000    add sp, fp, #0
4a10:   e49db004    pop {fp}        ; (ldr fp, [sp], #4)
4a14:   e12fff1e    bx  lr
4a18:   80000100    .word   0x80000100
4a1c:   800004c0    .word   0x800004c0
4a20:   00070001    .word   0x00070001
  

Комментарии:

1. Если вы выполняете вызов из сборки, вы можете легко настроить очень временный стек, скажем, 32-128 байт, указав регистр стека на некоторый статический символ [] или аналогичный.

Ответ №1:

просто настройте стек. В противном случае, если вы не хотите, чтобы в стеке не было локальных переменных и было достаточно мало кода, чтобы у вас не заканчивались регистры, Не вызывайте никаких функций из этой функции и т. Д. Если вам действительно нужны переменные, используйте глобальные переменные. Компилятор НЕ СМОЖЕТ сгенерировать ваш код без использования стека, если вы приведете к тому, что в коде закончатся регистры и глобальные переменные. Ни один переключатель компилятора не изобретет хранилище, и я бы не стал доверять компилятору, у которого есть какой-то нестандартный трюк, не относящийся к стеку, который он может попытаться использовать..

Простой, без локальных переменных, даже не нужен промежуточный регистр:

 unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a b);
}
  

нет стека:

 00000000 <fun>:
   0:   e0810000    add r0, r1, r0
   4:   e12fff1e    bx  lr
  

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

 .globl _start
_start:
    b reset
reset:
        ldr sp,=0x20008000
        bl more_fun
        b .

.globl fun
fun:
    bx lr

.globl fun_out
fun_out:
    bx lr



unsigned int fun ( unsigned int , unsigned int );
void fun_out ( unsigned int, unsigned int, unsigned int, unsigned int );
unsigned int more_fun ( unsigned int a, unsigned int b, unsigned int c )
{
    unsigned int d;
    d = fun(a,b);
    fun_out(a,b c,b,a c);
}
  

по инструкции, два слова, и вы можете вызвать C

    4:   e59fd00c    ldr sp, [pc, #12]   ; 18 <fun_out 0x4>
   8:   eb000003    bl  1c <more_fun>

...

  18:   20008000    andcs   r8, r0, r0

0000001c <more_fun>:
  1c:   e92d4070    push    {r4, r5, r6, lr}
  20:   e1a05002    mov r5, r2
  24:   e1a06000    mov r6, r0
  28:   e1a04001    mov r4, r1
  2c:   ebfffff7    bl  10 <fun>
  30:   e1a00006    mov r0, r6
  34:   e0853006    add r3, r5, r6
  38:   e0851004    add r1, r5, r4
  3c:   e1a02004    mov r2, r4
  40:   ebfffff3    bl  14 <fun_out>
  44:   e8bd8070    pop {r4, r5, r6, pc}
  

Комментарии:

1. Спасибо! Я изучу ваше решение и вернусь к вам через пару дней. Мне особенно нравится идея установки временного указателя стека…

2. Наконец, я настраиваю sp перед вызовом моей функции C. Но я все еще не понимаю, почему / как тот же код сборки с другим компилятором смог вызвать функцию C без использования указателя стека…

3. посмотрите на разборку, либо не использовали стек, либо, возможно, в системе, где стек был настроен для вас и т. Д., И, Вероятно, Он включал вызовы второго уровня и т. Д.

4. Обратите внимание, что даже ваш simple fun зависит от компиляции с включенной оптимизацией. В противном случае это приведет к переносу аргументов регистра в стек. Возможно __attribute__((optimize("O2"))) , этого можно избежать. Или просто посмотрите на это как на еще одну причину никогда не вызывать сгенерированный компилятором код без действительного стека.

Ответ №2:

У вас есть примерно два варианта:

  1. Настройте стек и забудьте об этом. Вы все равно должны установить указатель стека в сборке, поскольку вы не можете этого сделать в C (если только компилятор не поддерживает некоторые нестандартные расширения, позволяющие прямой доступ к регистрам).
  2. Закомментируйте исходную функцию C, дизассемблирование которой вы представили выше, скопируйте код дизассемблирования, замените push / pop / bx на str / ldr / b соответствующим образом (вам нужно будет выделить глобальную переменную для сохранения / восстановления этих энергонезависимых регистров (fp?)), исправьте другие вещипри необходимости и перекомпилируйте его в модуле сборки. РЕДАКТИРОВАТЬ: с другой стороны, вместо того, чтобы выполнять разборку, переведите эту функцию в сборку, используя, например, переключатель — gcc -c cfile.c -S -o asmfile.s S. Это, вероятно, сэкономит вам немного работы.

Я бы предпочел иметь действительный стек немедленно.

Комментарии:

1. Я понимаю, что я должен настроить стек раньше. Но я хотел бы сохранить исходный файл. Я попробую переключить -S с помощью curiosity!