GCC не работает при получении адреса аргумента в ARM7TDMI? - PullRequest
4 голосов
/ 26 августа 2008

Мой фрагмент кода C принимает адрес аргумента и сохраняет его в энергозависимой памяти (предварительно обработанный код):

void foo(unsigned int x) {
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&x);
}

int main() {
    foo(1);
    while(1);
}

Я использовал SVN-версию GCC для компиляции этого кода. В конце функции foo я ожидал бы, что в стеке будет храниться значение 1, а при 0x40000d4 адрес будет указывать на это значение. Когда я компилирую без оптимизации, используя флаг -O0, я получаю ожидаемый вывод сборки ARM7TMDI (закомментирован для вашего удобства):

        .align  2
        .global foo
        .type   foo, %function
foo:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 0, uses_anonymous_args = 0
        @ link register save eliminated.
        sub     sp, sp, #8
        str     r0, [sp, #4]     @ 3. Store the argument on the stack
        mov     r3, #67108864
        add     r3, r3, #212
        add     r2, sp, #4       @ 4. Address of the stack variable
        str     r2, [r3, #0]     @ 5. Store the address at 0x40000d4
        add     sp, sp, #8
        bx      lr
        .size   foo, .-foo
        .align  2
        .global main
        .type   main, %function
main:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        stmfd   sp!, {r4, lr}
        mov     r0, #1           @ 1. Pass the argument in register 0
        bl      foo              @ 2. Call function foo
.L4:
        b       .L4
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.0 20080820 (experimental)"

Он явно сохраняет аргумент сначала в стеке, а оттуда сохраняет его в 0x40000d4. Когда я компилирую с оптимизацией, используя -O1, я получаю нечто неожиданное:

        .align  2
        .global foo
        .type   foo, %function
foo:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 0, uses_anonymous_args = 0
        @ link register save eliminated.
        sub     sp, sp, #8
        mov     r2, #67108864
        add     r3, sp, #4        @ 3. Address of *something* on the stack
        str     r3, [r2, #212]    @ 4. Store the address at 0x40000d4
        add     sp, sp, #8
        bx      lr
        .size   foo, .-foo
        .align  2
        .global main
        .type   main, %function
main:
        @ Function supports interworking.
        @ args = 0, pretend = 0, frame = 0
        @ frame_needed = 0, uses_anonymous_args = 0
        stmfd   sp!, {r4, lr}
        mov     r0, #1           @ 1. Pass the argument in register 0
        bl      foo              @ 2. Call function foo
.L4:
        b       .L4
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.0 20080820 (experimental)"

На этот раз аргумент никогда не сохраняется в стеке, даже если что-то из стека все еще хранится в 0x40000d4.

Это просто ожидаемое / неопределенное поведение? Я сделал что-то не так или действительно нашел ошибку компилятора & trade;?

Ответы [ 11 ]

7 голосов
/ 16 ноября 2008

Как только вы вернетесь с foo(), x исчезнет, ​​и любые указатели на него будут недействительными. Впоследствии использование такого указателя приводит к тому, что стандарт C любит называть «неопределенным поведением», что означает, что компилятору абсолютно разрешено предполагать, что вы не будете разыменовывать его, или (если вы все равно настаиваете на этом) не нужно создавать код, который делает что-то отдаленно, как вы могли ожидать. Если вы хотите, чтобы указатель на x оставался действительным после возврата foo(), вы не должны выделять x в стеке foo, точка - даже если вы знаете , что в принципе ничто не имеет никакой причины забить это - потому что это просто не разрешено в C, независимо от того, как часто это происходит, что вы ожидаете.

Самое простое решение - сделать x локальной переменной в main() (или в любой другой функции, имеющей достаточно долгосрочную область действия) и передать адрес в foo. Вы также можете сделать x глобальной переменной или выделить ее в куче, используя malloc(), или выделить для нее память более экзотическим способом. Вы даже можете попытаться выяснить, где вершина стека каким-то (надеюсь) более переносимым способом и явно сохранить ваши данные в какой-то части стека, если вы уверены, что вам больше ничего не понадобится, и вы Вы уверены, что это то, что вам действительно нужно сделать. Но метод, который вы использовали для этого, недостаточно надежен, как вы обнаружили.

4 голосов
/ 16 сентября 2008

Я на самом деле не думаю, что компилятор не прав, хотя это странный случай.

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

Ключевое слово "volatile" на самом деле мало что делает в C, особенно в отношении нескольких потоков или аппаратного обеспечения. Это просто говорит компилятору, что он должен делать доступ. Однако, поскольку нет пользователей со значением x в соответствии с потоком данных, нет причины хранить «1» в стеке.

Это, вероятно, сработало бы, если бы вы написали

void foo(unsigned int x) {
    volatile int y = x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)(&y);
}

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

4 голосов
/ 10 сентября 2008

Итак, вы помещаете адрес локальной переменной стека в используемый контроллер DMA, а затем возвращаетесь из функции, в которой доступна переменная стека?

Хотя может работать с вашим примером main () (поскольку вы больше не записываете в стек), позже он не будет работать в «реальной» программе - это значение будет перезаписано до или когда DMA обращается к нему, когда вызывается другая функция и стек используется снова.

У вас должна быть структура или глобальная переменная, которую вы можете использовать для хранения этого значения, пока DMA обращается к нему - в противном случае оно просто будет забито!

-Adam

3 голосов
/ 16 ноября 2008

Следует отметить, что в соответствии со стандартом приведенные значения являются r-значениями. GCC раньше разрешал это, но в последних версиях стал немного приверженцем стандартов.

Я не знаю, будет ли это иметь значение, но вы должны попробовать это:

void foo(unsigned int x) {
    volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4);
    *ptr = (unsigned int)(&x);
}

int main() {
    foo(1);
    while(1);
}

Кроме того, я сомневаюсь, что вы предполагали это, но вы храните адрес функции local x (которая является копией переданного вами int). Вы, вероятно, захотите, чтобы foo взял "unsigned int *" и передал адрес того, что вы действительно хотите сохранить.

Так что я думаю, что более правильное решение было бы так:

void foo(unsigned int *x) {
    volatile unsigned int* ptr = (unsigned int*)(0x4000000 + 0xd4);
    *ptr = (unsigned int)(x);
}

int main() {
    int x = 1;
    foo(&x);
    while(1);
}

РЕДАКТИРОВАТЬ: наконец, если ваш код ломается с оптимизацией, это обычно признак того, что ваш код делает что-то не так.

1 голос
/ 15 марта 2011

Томи Кёстиля написал

разработка для Game Boy Advance. Я читал о его системе DMA и Я экспериментировал с этим, создавая одноцветные растровые изображения. Идея должен был иметь индексированный цвет передан в качестве аргумента функции который будет использовать DMA для заполнения плитки с этим цветом. Адрес источника для передачи DMA хранится в 0x40000d4.

Это совершенно правильная вещь для вас, и я вижу, как (неожиданный) код, полученный с оптимизацией -O1, не будет работать.

Я вижу (ожидаемый) код, который вы получили с оптимизацией -O0, выполняет то, что вы ожидаете - он помещает значение нужного вам цвета в стек и указатель на этот цвет в регистре передачи DMA.

Однако даже (ожидаемый) код, полученный с оптимизацией -O0, также не будет работать. К тому времени, когда аппаратное обеспечение DMA начинает использовать этот указатель и использовать его для считывания желаемого цвета, это значение в стеке (вероятно) уже давно перезаписано другими подпрограммами или обработчиками прерываний или обоими. И поэтому и ожидаемый, и неожиданный код приводят к одному и тому же - DMA (вероятно) собирается получить неправильный цвет.

Я думаю, что вы действительно намеревались хранить значение цвета в каком-то месте, где оно остается безопасным, пока DMA не закончит его читать. Таким образом, глобальная переменная или локальная статическая переменная функции, такая как

// Предупреждение: Программист трех звезд на работе

// Warning: untested code.
void foo(unsigned int x) {
    static volatile unsigned int color = x; // "static" so it's not on the stack
    volatile unsigned int** dma_register =
        (volatile unsigned int**)(0x4000000 + 0xd4);
    *dma_register = &color;
}

int main() {
    foo(1);
    while(1);
}

Это работает для вас?

Вы видите, что я использую "volatile" дважды, потому что я хочу заставить два значения записываться в этом конкретном порядке.

1 голос
/ 26 августа 2008

Я проклят, если смогу найти ссылку в данный момент, но я на 99% уверен, что вы всегда должны иметь возможность принимать адрес аргумента, и это до компилятор для уточнения деталей соглашений о вызовах, использования регистра и т. д.

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

Лично я мог бы попробовать это, чтобы проверить, лучше ли он скомпилирован:

void foo(unsigned int x) 
{
    volatile unsigned int* pArg = &x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg;
}
0 голосов
/ 29 апреля 2009

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

Кстати, использование * (volatile blah *) 0xabcd или любой другой метод для программирования регистров в конечном итоге укусит вас. У gcc и большинства других компиляторов есть такой странный способ точно знать, когда наступит самое худшее время.

Скажи день, когда ты изменишься с этого

*(volatile unsigned int *)0x12345 = someuintvariable;

до

*(volatile unsigned int *)0x12345 = 0x12;

Хороший компилятор поймет, что вы храните только 8 бит, и нет никакой причины тратить на это 32-битное хранилище, в зависимости от архитектуры, которую вы указали, или архитектуры по умолчанию для этого компилятора в тот день, так что он находится в пределах его права оптимизировать это для strb вместо str.

После того, как gcc и другие десятки раз сожгли его, я прибег к решению этой проблемы:

.globl PUT32
PUT32:
   str r1,[r0]
   bx lr





   PUT32(0x12345,0x12);

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

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

0 голосов
/ 11 ноября 2008

В общем, я бы сказал, что это действительная оптимизация. Если вы хотите заглянуть глубже, вы можете скомпилировать с -da Это генерирует .c.Number.Passname, где вы можете взглянуть на rtl (промежуточное представление в gcc). Там вы можете увидеть, какой проход делает какую оптимизацию (и, возможно, отключить только тот, который вам не нужен)

0 голосов
/ 10 сентября 2008

Не ответ, а просто дополнительная информация для вас. Мы работаем с 3.4.5 20051201 (Red Hat 3.4.5-2) на моей повседневной работе.

Мы также заметили, что часть нашего кода (который я не могу опубликовать здесь) перестает работать, когда мы добавляем флаг -O1. Нашим решением было убрать флаг на данный момент: (

0 голосов
/ 26 августа 2008

искр написал

Если вы считаете, что нашли ошибку в GCC списки рассылки будут рады вам заскочил, но обычно они находят какая-то дыра в ваших знаниях заключается в винить и беспощадно издеваться: (

Я решил, что сначала попытаю счастья, прежде чем отправиться в список рассылки GCC, чтобы показать свою некомпетентность:)


Адам Дэвис написал

Из любопытства, что вы пытаетесь выполнить?

Я пробовал разработку для Game Boy Advance. Я читал о его системе DMA и экспериментировал с ней, создавая одноцветные растровые изображения. Идея заключалась в том, чтобы индексированный цвет передавался в качестве аргумента функции, которая будет использовать DMA для заполнения плитки этим цветом. Адрес источника для передачи DMA хранится в 0x40000d4.


Уилл Дин написал

Лично я мог бы попробовать это, чтобы увидеть если компилируется лучше:

void foo(unsigned int x) 
{
    volatile unsigned int* pArg = &x;
    *(volatile unsigned int*)(0x4000000 + 0xd4) = (unsigned int)pArg;
}

С -O0, который работает так же хорошо, и с -O1, оптимизированным до точно такой же сборки -O1, которую я разместил в моем вопросе.

...