ASM ограничивает побочные эффекты - PullRequest
2 голосов
/ 16 марта 2019

Мне трудно понять некоторые эффекты некоторых специфических ограничений в GCC для inline-сборки.

В приведенном ниже примере, если я запускаю с "= X" на выходе и "X"на всех входах вывод 2 отпечатков

0x562f39629260, 100

0x14, 100

Что указывает на указатель на буфер, который я выделилизменилсяПриведение к Segfault - это попытка прочитать содержимое буфера после кода моей сборки.

Наоборот, если я поставлю «+ X» на выходе или «m» на входах, то адреса останутсято же самое, вывод распечаток:

0x55571bb83260, 100

0x55571bb83260, 100

И я могу безопасно читать мой буфер без сегфагов.

Я не понимаю, как или почему этот указатель должен / может быть изменен?Есть ли способ безопасно выбрать ограничения?Онлайновая документация по gcc не дает большой информации об этом.

Большое спасибо,

int main() {
    long size = 100;
    char * buffer = (char*)malloc(size*sizeof(char));

    printf("%p, %d\n",buffer, size);

    __asm__(
    "mov %[out], %%rcx \n"
    "mov %[size], %%rbx \n"
    "loop: \n"
    "movb $1, (%%rcx) \n"
    "add $1, %%rcx \n"
    "sub $1, %%rbx \n"
    "jnz loop \n"
    : "=X"(buffer) //outputs
    : [out]"X"(buffer), [size]"X"(size) //inputs
    : "rbx", "rcx" //clobbers
    );

    printf("%p, %d\n",buffer, size);

    return 0;
}

1 Ответ

2 голосов
/ 16 марта 2019

= в =X означает, что это ограничение ТОЛЬКО ДЛЯ ВЫХОДА (в отличие от ограничения на обновление с +). Это означает, что ассемблерный код должен что-то записывать в операнд (в %0), и это будет иметь значение, которое выводится. Но так как ваш ассемблерный код никогда не записывает в %0, вы получаете все, что происходит с мусором в этом месте (вероятно, регистр, который выбрал распределитель регистров).

Попробуйте добавить строку mov %%rcx,%0 в код asm, чтобы увидеть, что на самом деле происходит.

Скорее всего, что вы действительно хотите, это что-то вроде:

__asm__ volatile (
"mov %[size], %%rbx \n"
"loop: \n"
"movb $1, (%[out]) \n"
"add $1, %[out] \n"
"sub $1, %%rbx \n"
"jnz loop \n"
: [out]"+r"(buffer) //outputs
: [size]"X"(size) //inputs
: "rbx", "memory" //clobbers
);

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

__asm__ volatile (
"loop: \n"
"movb $1, (%[out]) \n"
"add $1, %[out] \n"
"sub $1, %[size] \n"
"jnz loop \n"
: [out]"+r"(buffer), [size]"+X"(size) //outputs
: //inputs
: "memory" //clobbers
);

Хотя было бы еще проще (и лучше для оптимизатора) вообще не использовать asm:

do { *buffer++ = '\1'; } while (--size);

Итак, чтобы подвести итог всем комментариям ниже, вам может понадобиться что-то вроде:

long size = 100;
char buffer[100];
char *temp;
__asm__(
"loop: \n"
"movb $1, (%[out]) \n"
"add $1, %[out] \n"
"sub $1, %[size] \n"
"jnz loop \n"
: [out]"=r"(temp), [size]"+X"(size), "=m"(buffer) //outputs
: "0"(buffer) // inputs
)  // no clobbers
  • использование ограничения "=m" на весь буфер вместо сабблеров памяти и энергозависимости означает, что он может быть исключен из мертвого кода, если не используется ни один из результатов
  • использование временного указателя для перемещения указателя над буфером означает, что исходное значение буфера (начальное) может быть сохранено.
  • если вы должны использовать malloc для буфера, "=m"(*(char (*)[100])buffer) может использоваться для получения ограничения на весь буфер.

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

...