Получить длину строки в inline GNU Assembler

Я переучиваю ассемблер, который я использовал на очень старых машинах MS-DOS!!!

Это мое понимание того, как должна выглядеть эта функция. Он компилируется, но сбой SIGSEGV при попытке установить 0xffffffff в ecx.

Код запускается в виртуальной машине с 32-разрядным Debian 9. Любая помощь будет оценена.

    int getStringLength(const char *pStr){

        int len = 0;
        char *Ptr = pStr;

        __asm__  (
            "movl %1, %%edi\n\t"
            "xor %%al, %%al\n\t"
            "movl 0xffffffff, %%ecx\n\t"
            "repne scasb\n\t"
            "subl %%ecx,%%eax\n\t"
            "movl %%eax,%0"
            :"=r" (len)     /*Output*/
            :"r"(len)       /*Input*/
            :"%eax"         /*Clobbered register*/


    );

        return len;
    }

Ответ 1

Проблема использования GCC inline asm для изучения сборки заключается в том, что вы тратите половину своего времени на изучение того, как работает сборка gcc inline, а не только на сборку. Например, здесь, как я могу написать этот же код:

#include <stdio.h>

int getStringLength(const char *pStr){

    int len;

    __asm__  (
        "repne scasb\n\t"
        "not %%ecx\n\t"
        "dec %%ecx"
        :"=c" (len), "+D"(pStr)     /*Outputs*/
        :"c"(-1), "a"(0)            /*Inputs*/
        /* tell the compiler we read the memory pointed to by pStr,
           with a dummy input so we don't need a "memory" clobber */
        , "m" (*(const struct {char a; char x[];} *) pStr)

    );

    return len;
}

Смотрите компилятор asm output в проводнике компилятора Godbolt. Вход в фиктивную память - это сложная часть: см. Обсуждение в комментариях и в списке рассылки gcc для наиболее оптимального способа сделать это, что все еще безопасно.

Сравнивая это с вашим примером

  • Я не инициализирую len, так как asm объявляет его как выход (= c).
  • Нет необходимости копировать pStr, так как это локальная переменная. По спецификации нам уже разрешено изменять его (хотя, поскольку он const, мы не должны изменять данные, на которые он указывает).
  • Нет причин говорить встроенному asm о том, чтобы положить Ptr в eax, только чтобы ваш asm переместил его в edi. Я просто положил значение в edi в первую очередь. Обратите внимание: поскольку значение в edi изменяется, мы не можем просто объявить его как "вход" (по спецификации, inline asm не должен изменять значение входов). Изменение этого параметра на выход чтения/записи решает эту проблему.
  • Нет необходимости иметь asm zero eax, так как вы можете иметь ограничения для этого. В качестве побочного преимущества gcc "знает", что он имеет 0 в регистре eax, и (в оптимизированных сборках) он может повторно использовать его (подумайте: проверяя длину 2 строки).
  • Я также могу использовать ограничения для инициализации ecx. Как уже упоминалось, изменение значения входов не допускается. Но поскольку я определяю ecx как результат, gcc уже знает, что я его меняю.
  • Поскольку содержимое ecx, eax и edi явно указано, нет необходимости больше ничего скрывать.

Все это делает для (слегка) более короткий и более эффективный код.

Но это смешно. Как, черт возьми, я могу сказать "heck" на SO?), Вы должны знать все это?

Если целью является изучение asm, использование inline asm не является вашим лучшим подходом (на самом деле я бы сказал, что inline asm является bad idea в большинстве случаев). Я бы рекомендовал вам объявить getStringLength как extern и полностью записать его в asm, а затем связать его с вашим кодом C.

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

Ответ 2

Наконец, он работал:

    int getStringLength(const char *pStr){

        int len = 0;
        const char *Ptr = pStr;

        __asm__  (
            "mov %%eax, %%edi\n\t"
            "xor %%eax, %%eax\n\t"
            "mov $0xffffffff, %%ecx\n\t"
            "repne scasb\n\t"
            "sub %%ecx,%%eax\n\t"
            "sub $2, %%eax\n\t"
            :"=a" (len)        /*Output*/
            :"a"(Ptr)          /*Input*/
            :"%ecx", "%edi"    /*Clobbered register (ecx, edi)*/
        );

        return len;
    }