Получить длину строки во встроенном 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;
}
2 ответа
Проблема с использованием встроенного ассемблера GCC для изучения ассемблера состоит в том, что вы тратите половину своего времени на изучение того, как работает встроенная ассемблер gcc, а не на фактическое изучение ассемблера. Например, вот как я мог бы написать этот код:
#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 компилятора в проводнике компилятора Godbolt. Фиктивный ввод в память - сложная часть: посмотрите обсуждение в комментариях и в списке рассылки gcc, чтобы найти наиболее оптимальный способ сделать это, который все еще безопасен.
Сравнивая это с вашим примером
- Я не инициализирую
len
, поскольку asm объявляет его как выход (=c). - Там нет необходимости копировать
pStr
так как это локальная переменная. По спецификации нам уже разрешено его менять (хотя так какconst
мы не должны изменять данные, на которые он указывает). - Нет смысла указывать встроенный ассм
Ptr
вeax
только чтобы ваш асм переместить его вedi
, Я просто положил значение вedi
на первом месте. Обратите внимание, что, так как значение вedi
меняется, мы не можем просто объявить его как "вход" (по спецификации, встроенный asm не должен изменять значение входов). Изменение его на выход для чтения / записи решает эту проблему. - Там нет необходимости иметь ноль asm
eax
, так как вы можете иметь ограничения сделать это за вас. В качестве дополнительного преимущества, gcc будет "знать", что у него 0 вeax
зарегистрироваться, и (в оптимизированных сборках) он может использовать его повторно (подумайте: проверка длины 2 строк). - Я могу использовать ограничения для инициализации
ecx
тоже. Как уже упоминалось, изменение значения входов не допускается. Но так как я определяюecx
в качестве вывода, gcc уже знает, что я его изменяю. - Так как содержимое ecx, eax и edi явно указано, больше не нужно ничего загонять.
Все это делает (немного) более короткий и эффективный код.
Но это смешно. Как, черт возьми (я могу сказать "черт" на SO?) Ты должен знать все это?
Если цель состоит в том, чтобы выучить asm, использование inline asm не является вашим лучшим подходом (на самом деле я бы сказал, что inline asm - плохая идея в большинстве случаев). Я бы порекомендовал вам объявить getStringLength как extern и написать его полностью в asm, а затем связать его с вашим C-кодом.
Таким образом, вы узнаете о передаче параметров, возвращаемых значениях, сохранении регистров (наряду с изучением того, какие регистры должны быть сохранены и которые вы можете безопасно использовать в качестве нуля), стековых фреймов, как связать asm с C и т. Д., И т. Д., И т. Д. Все что более полезно знать, чем этот гоблин для inline asm.
Наконец-то все заработало:
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;
}