Расширенная версия asm GCC - PullRequest
4 голосов
/ 30 июня 2009

Я никогда не думал, что буду публиковать вопрос о сборке. :-)

В GCC существует расширенная версия функции asm . Эта функция может принимать четыре параметра: код сборки, список вывода, список ввода и список перезаписи.

Мой вопрос: Обнулены ли регистры в списке перезаписи ? Что происходит со значениями, которые ранее были там (из другого выполняющегося кода).

Обновление : Рассматривая мои ответы до сих пор (спасибо!), Я хочу добавить, что хотя регистр указан в списке clobber, он (в моем случае) используется в команда pop (popl). Других ссылок нет.

Ответы [ 3 ]

7 голосов
/ 01 июля 2009

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

Например, на x86 инструкция cpuid возвращает информацию в четырех частях, используя четыре фиксированных регистра: %eax, %ebx, %ecx и %edx, на основе входного значения %eax. Если бы нас интересовали только результаты в %eax и %ebx, то мы могли бы (наивно) написать:

int input_res1 = 0; // also used for first part of result 
int res2;
__asm__("cpuid" : "+a"(input_res1), "=b"(res2) ); 

Это получит первую и вторую части результата в переменных C input_res1 и res2; однако , если GCC использовал %ecx и %edx для хранения других данных; они будут перезаписаны инструкцией cpuid без указания gcc . Чтобы предотвратить это; мы используем список clobber :

int input_res1 = 0; // also used for first part of result 
int res2;
__asm__("cpuid" : "+a"(input_res1), "=b"(res2)
                : : "%ecx", "%edx" );

Поскольку мы сказали GCC, что %ecx и %edx будут перезаписаны этим вызовом asm, он может правильно обработать ситуацию - либо не используя %ecx или %edx, либо сохранив их значения в стек до функции asm и восстановление после.

Обновление:

Что касается вашего второго вопроса (почему вы видите регистр, указанный в списке clobber для инструкции popl) - при условии, что ваш asm выглядит примерно так:

__asm__("popl %eax" : : : "%eax" );

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

int trash // don't ever use this.
__asm__("popl %0" : "=r"(trash));

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

5 голосов
/ 30 июня 2009

Если под «обнулением» вы подразумеваете «значения в регистрах заменены на 0, чтобы я не знал, что делает какая-то другая функция», то нет, регистры не обнуляются перед использованием. Но это не должно иметь значения, потому что вы говорите GCC, что вы планируете хранить информацию там, а не то, что вы хотите читать информацию, которая в данный момент там есть.

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

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

Обновление

GCC спроектирован как многопроходный компилятор. Многие из пропусков на самом деле совершенно разные программы. Набор программ, образующих «компилятор», переводит ваш исходный код из C, C ++, Ada, Java и т. Д. В код сборки. Затем отдельная программа (gas для GNU Assembler) берет этот ассемблерный код и превращает его в двоичный файл (а затем ld и collect2 делают больше вещей с двоичным файлом). Сборочные блоки существуют для прямой передачи текста в gas, и существует список clobber (и входной список), так что компилятор может делать все, что нужно для передачи информации между сторонами C, C ++, Ada, Java и т. Д. вещей и стороны вещей gas, а также для гарантии того, что любая важная информация, находящаяся в настоящее время в регистрах, может быть защищена от сборочного блока путем копирования ее в память перед запуском сборочного блока (и последующего копирования обратно из памяти).

Альтернативой может быть сохранение и восстановление каждого регистра для каждого блока кода сборки. На RISC-машине с большим количеством регистров, которые могут стоить дорого (например, Itanium имеет 128 регистров общего назначения, еще 128 регистров с плавающей запятой и 64 1-битных регистра).

Прошло много времени с тех пор, как я написал любой ассемблерный код. И у меня гораздо больше опыта использования функции именованных регистров в GCC, чем при работе с конкретными регистрами. Итак, глядя на пример:

#include <stdio.h>

long foo(long l)
{
    long result;
    asm (
        "movl %[l], %[reg];"
        "incl %[reg];"
        : [reg] "=r" (result)
        : [l] "r" (l)
    );
    return result;
}

int main(int argc, char** argv)
{
    printf("%ld\n", foo(5L));
}

Я запросил выходной регистр, который я назову reg внутри кода сборки, и этот GCC автоматически скопирует в переменную result по завершении. Нет необходимости присваивать этой переменной разные имена в C-коде по сравнению с ассемблерным; Я только сделал это, чтобы показать, что это возможно. Какой физический регистр GCC решит использовать - будь то %%eax, %%ebx, %%ecx и т. Д. - GCC позаботится о копировании любых важных данных из этого регистра в память при вводе блока сборки, чтобы я мог полностью использовать этот регистр до конца блока сборки.

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

Что если я добавлю строку в код сборки? Скажи:

"addl %[reg], %%ecx;"

Поскольку компилятор GCC не проверяет код сборки, он не защитит данные в %%ecx. Если мне повезет, %%ecx может оказаться одним из регистров, которые GCC решил использовать для %[reg] или %[l]. Если мне не повезет, я «загадочным образом» изменил значение в какой-то другой части моей программы.

4 голосов
/ 30 июня 2009

Я подозреваю, что список перезаписи просто дает GCC подсказку не хранить ничего ценного в этих регистрах через вызов ASM; поскольку GCC не анализирует, какой ASM вы ему предоставляете, и у некоторых инструкций есть побочные эффекты, которые касаются других регистров, не указанных в коде явно, это способ сообщить GCC об этом.

...