Понимание и анализ кода сборки - PullRequest
0 голосов
/ 31 декабря 2018

Может кто-нибудь помочь мне понять этот ассемблерный код?Я совершенно новичок в языке ассемблера, и я просто не могу понять это ... Следующий код ассемблера должен произвести эту функцию:

func (int a) {return a * 34}

Комментарии // мои мысли, что это должно означать, пожалуйста, поправьте меня, если я ошибаюсь

//esp = stack-pointer, ebp = callee saved, eax = return value

pushl %ebp                   // a is pushed on stack
movl %esp,%ebp               // a = stackpointer
movl 8(%ebp),%eax            // eax = M(8 + a).But what is in M(8 + a)?
sall $4,%eax                 // eax << 4
addl 8(%ebp),%eax            // eax = M(8 + a)
addl %eax,%eax               // eax = eax + eax
movl %ebp,%esp               // eax = t
popl %ebp                    // pop a from stack
ret

Может кто-нибудь объяснить мне, как это понять?Большое спасибо!

Ответы [ 2 ]

0 голосов
/ 31 декабря 2018

Это дурацкий неоптимизированный код, потому что вы скомпилировали с -O0 (скомпилируйте быстро, пропустите большинство этапов оптимизации).Устаревшая установка / очистка стекового фрейма - это просто шум.Аргумент находится в стеке прямо над адресом возврата, то есть на 4(%esp) при входе в функцию(См. Также Как удалить «шум» из выходных данных сборки GCC / clang? )

Удивительно, что компилятор использует 3 инструкции для умножения путем сдвига и добавления вместоimull $34, 4(%esp), %eax / ret, если не настраивать старые процессоры.2 инструкции являются отсечкой для современных gcc и clang с настройками по умолчанию.См., Например, Как умножить регистр на 37, используя только 2 последовательные инструкции leal в x86?

Но это можно сделать с двумя инструкциями, используя LEA (не считаяmov для копирования реестра);код раздут, потому что вы скомпилировали без оптимизации.(Или вы настроились на старый процессор, где, возможно, есть какая-то причина избегать LEA.)

Я думаю, вы, должно быть, использовали для этого gcc;отключение оптимизации с другими компиляторами всегда просто использует imul для умножения на не-степень-2.Но я не могу найти версию gcc + опции в проводнике компилятора Godbolt, который выдает именно ваш код.Я не пробовал все возможные комбинации.MSVC 19.10 -O2 использует тот же алгоритм, что и ваш код, включая двойную загрузку a.

Компиляция с gcc5.5 (который является новейшим gcc, который не просто использует imul, даже при -O0), мы получаем что-то вроде вашего кода, но не совсем.(Те же операции в другом порядке, без двойной загрузки a из памяти).

# gcc5.5 -m32 -xc -O0 -fverbose-asm -Wall
func:
    pushl   %ebp  #
    movl    %esp, %ebp      #,            # make a stack frame

    movl    8(%ebp), %eax   # a, tmp89    # load a from the stack, first arg is at EBP+8

    addl    %eax, %eax      # tmp91          # a*2
    movl    %eax, %edx      # tmp90, tmp92
    sall    $4, %edx        #, tmp92         # a*2 << 4 = a*32
    addl    %edx, %eax      # tmp92, D.1807  # a*2 + a*32

    popl    %ebp    #                     # clean up the stack frame
    ret

Компиляция с оптимизацией с той же более старой версией GCC на проводник компилятора Godbolt : gcc5.5 -m32 -O3 -fverbose-asm, мы получаем:

# gcc5.5 -m32 -O3.   Also clang7.0 -m32 -O3 emits the same code
func:
    movl    4(%esp), %eax   # a, a          # load a from the stack
    movl    %eax, %edx      # a, tmp93      # copy it to edx
    sall    $5, %edx        #, tmp93        # edx = a<<5 = a*32
    leal    (%edx,%eax,2), %eax             # eax = edx + eax*2 = a*32 + a*2 = a*34
    ret              # with a*34 in EAX, the return-value reg in this calling convention

С gcc 6.x или новее мы получаем этот эффективный asm : imul-после того, как источник памяти декодирует только один микроплавкий уоп на современных процессорах Intel, а целочисленное умножение имеет только 3 цикла задержки для Intel со времен Core2 и AMD со времен Ryzen.(https://agner.org/optimize/).

# gcc6/7/8 -m32 -O3     default tuning
func:
    imull   $34, 4(%esp), %eax    #, a, tmp89
    ret

Но с -mtune=pentium3 мы, как ни странно, не получаем LEA . Это похоже на пропущенную оптимизацию. LEA имеет задержку в 1 цикл на Pentium 3/ Pentium-M.

# gcc8.2 -O3 -mtune=pentium3 -m32 -xc -fverbose-asm -Wall
func:
    movl    4(%esp), %edx   # a, a
    movl    %edx, %eax      # a, tmp91
    sall    $4, %eax        #, tmp91     # a*16
    addl    %edx, %eax      # a, tmp92   # a*16 + a = a*17
    addl    %eax, %eax      # tmp93      # a*16 * 2 = a*34
    ret

Это то же самое, что и ваш код, но использует reg-reg mov вместо перезагрузки из стека, чтобы добавить a к результату сдвига.

0 голосов
/ 31 декабря 2018
pushl %ebp                   // a is pushed on stack
movl %esp,%ebp               // a = stackpointer

Как отмечается в комментарии, ebp не имеет ничего общего с a.ebp - это базовый указатель стека - этот код сохраняет старое значение ebp в стек, а затем сохраняет указатель стека в ebp.

movl 8(%ebp),%eax            // eax = M(8 + a).But what is in M(8 + a)?

Правильно.В стеке находится входное значение eax.

sall $4,%eax                 // eax << 4

Правильно.(И результат присваивается обратно eax.)

addl 8(%ebp),%eax            // eax = M(8 + a)

Нет, вы неправильно это поняли.Это добавляет значение в стеке 8(ebp), которое является исходным значением a, к eax.Дополнение применяется к значениям, а не к адресам памяти.

addl %eax,%eax               // eax = eax + eax

Правильно.Значение eax здесь не изменяется, поэтому это возвращаемое значение функции.

movl %ebp,%esp               // eax = t
popl %ebp                    // pop a from stack
ret

Этот код отменяет действие первых двух инструкций.Это стандартная последовательность очистки, которая не имеет ничего общего с a.

. Важные части этой функции можно обозначить следующим образом:

a1 = a << 4;   // = a * 16
a2 = a1 + a;   // = a * 17
a3 = a2 + a2;  // = a * 34
return a3;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...