Я новичок в сборке. Когда я изучал программирование, я создал программу, которая реализует таблицы умножения до 1000 * 1000. Таблицы отформатированы так, чтобы каждый ответ был в строке factor1 << 10 | factor2
(Я знаю, я знаю, это не красиво). Эти таблицы затем загружаются в массив: int* tables
, Пустые строки заполнены 0. Вот ссылка на файл для таблиц (7,3 МБ). Я знаю, что использование ассемблера не сильно ускорит это, но я просто хотел сделать это для развлечения (и немного практики).
Я пытаюсь преобразовать этот код во встроенную сборку (tables
является глобальным):
int answer;
// ...
answer = tables [factor1 << 10 | factor2];
Вот что я придумал:
asm volatile ( "shll $10, %1;""orl %1, %2;""movl _tables(,%2,4), %0;" : "=r" (answer) : "r" (factor1), "r" (factor2) );
Мой код C ++ работает нормально, но моя сборка не удалась. Что не так с моей сборкой (особенно movl _tables(,%2,4), %0;
часть), по сравнению с моим C ++
Я использовал несколько случайных чисел: 89 796 как factor1
а также factor2
, я знать что есть элемент в 89 << 10 | 786
(который 91922
) — проверил это с помощью C ++. Когда я запускаю его с gdb
Я получаю SIGSEGV:
Программа получила сигнал SIGSEGV, Ошибка сегментации.
на этой линии:
"movl _tables(,%2,4), %0;" : "=r" (answer) : "r" (factor1), "r" (factor2) );
Я добавил два метода вокруг моего asm
откуда я знаю где asm
Блок находится в разборке.
asm
блок:Разборка из objdump -M att -d
выглядит хорошо (хотя я не уверен, я новичок в сборке, как я уже сказал):
402096: 8b 45 08 mov 0x8(%ebp),%eax
402099: 8b 55 0c mov 0xc(%ebp),%edx
40209c: c1 e0 0a shl $0xa,%eax
40209f: 09 c2 or %eax,%edx
4020a1: 8b 04 95 18 e0 47 00 mov 0x47e018(,%edx,4),%eax
4020a8: 89 45 ec mov %eax,-0x14(%ebp)
Разборка из objdump -M intel -d
:
402096: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
402099: 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
40209c: c1 e0 0a shl eax,0xa
40209f: 09 c2 or edx,eax
4020a1: 8b 04 95 18 e0 47 00 mov eax,DWORD PTR [edx*4+0x47e018]
4020a8: 89 45 ec mov DWORD PTR [ebp-0x14],eax
Из того, что я понимаю, движется первый параметр моего void calc ( int factor1, int factor2 )
функция в eax
, Затем он перемещает второй параметр в edx
, Тогда это сдвиги eax
влево на 10 и or
с этим edx
, 32-разрядное целое число составляет 4 байта, поэтому [edx*4+base_address]
. Переместить результат в eax
а затем положить eax
в int answer
(который, я думаю, включен -0x14
стека). Я не вижу особой проблемы.
.exe
:Когда я заменяю asm
блок с простым C ++ (answer = tables [factor1 << 10 | factor2];
) и разобрать это вот что я получаю в синтаксисе Intel:
402096: a1 18 e0 47 00 mov eax,ds:0x47e018
40209b: 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
40209e: c1 e2 0a shl edx,0xa
4020a1: 0b 55 0c or edx,DWORD PTR [ebp+0xc]
4020a4: c1 e2 02 shl edx,0x2
4020a7: 01 d0 add eax,edx
4020a9: 8b 00 mov eax,DWORD PTR [eax]
4020ab: 89 45 ec mov DWORD PTR [ebp-0x14],eax
В&Синтаксис T:
402096: a1 18 e0 47 00 mov 0x47e018,%eax
40209b: 8b 55 08 mov 0x8(%ebp),%edx
40209e: c1 e2 0a shl $0xa,%edx
4020a1: 0b 55 0c or 0xc(%ebp),%edx
4020a4: c1 e2 02 shl $0x2,%edx
4020a7: 01 d0 add %edx,%eax
4020a9: 8b 00 mov (%eax),%eax
4020ab: 89 45 ec mov %eax,-0x14(%ebp)
Я не очень знаком с синтаксисом Intel, поэтому я просто собираюсь попытаться понять AT&Синтаксис T:
Сначала перемещается базовый адрес tables
массив в %eax
, Затем это перемещает первый параметр в %edx
, Это сдвиги %edx
влево на 10, то or
S со вторым параметром. Затем, сдвигая %edx
слева на два, это на самом деле умножается %edx
на 4. Затем это добавляет %eax
(базовый адрес массива). Итак, в основном это просто сделано это: [edx*4+0x47e018]
(Синтаксис Intel) или 0x47e018(,%edx,4)
В&Т. Он перемещает значение элемента, в который он попал %eax
и помещает это в int answer
, Этот метод более «расширен», но он делает то же самое, что и моя рукописная сборка! Так почему мой дает SIGSEGV
в то время как компилятор работает нормально?
Бьюсь об заклад (от разборки), что tables
это указатель на массив, а не сам массив.
Итак, вам нужно:
asm volatile ( "shll $10, %1;"movl _tables,%%eax
"orl %1, %2;""movl (%%eax,%2,4)",
: "=r" (answer) : "r" (factor1), "r" (factor2) : "eax" )
(Не забывайте лишний клоббер в последней строке).
Конечно, есть варианты, это может быть более эффективным, если код находится в цикле:
asm volatile ( "shll $10, %1;""orl %1, %2;""movl (%3,%2,4)",
: "=r" (answer) : "r" (factor1), "r" (factor2), "r"(tables) )
Это должно быть дополнением к ответу Матса Петерссона — я написал его просто потому, что мне не сразу было понятно, почему анализ разборки в OP (что его сборка и компиляция, сгенерированные компилятором были эквивалентны) был неверным.
Как объясняет Матс Петерссон, проблема в том, что tables
на самом деле это указатель на массив, поэтому для доступа к элементу вам нужно дважды разыменовать. Теперь для меня это было не сразу понятно где это происходит в сгенерированном компилятором коде. Виновником является эта невинно выглядящая линия:
a1 18 e0 47 00 mov 0x47e018,%eax
Для неподготовленного глаза (включая моего) это может выглядеть как значение 0x47e018
перемещен в eax
, но на самом деле это не так. Синтаксическое представление Intel тех же кодов операций дает нам подсказку:
a1 18 e0 47 00 mov eax,ds:0x47e018
Ах — ds:
— так что это на самом деле не значение, а адрес!
Для тех, кто интересуется сейчас, следующие будут коды операций и синтаксическая сборка ATT для перемещения значение 0x47e018
в eax
:
b8 18 e0 47 00 mov $0x47e018,%eax