Почему кодировка сборки objdump различается?

Я читал эту статью. о позиционном независимом коде, и я столкнулся с этим списком функций на ассемблере.

0000043c <ml_func>:
 43c:   55                      push   ebp
 43d:   89 e5                   mov    ebp,esp
 43f:   e8 16 00 00 00          call   45a <__i686.get_pc_thunk.cx>
 444:   81 c1 b0 1b 00 00       add    ecx,0x1bb0
 44a:   8b 81 f0 ff ff ff       mov    eax,DWORD PTR [ecx-0x10]
 450:   8b 00                   mov    eax,DWORD PTR [eax]
 452:   03 45 08                add    eax,DWORD PTR [ebp+0x8]
 455:   03 45 0c                add    eax,DWORD PTR [ebp+0xc]
 458:   5d                      pop    ebp
 459:   c3                      ret

0000045a <__i686.get_pc_thunk.cx>:
 45a:   8b 0c 24                mov    ecx,DWORD PTR [esp]
 45d:   c3                      ret

Однако на моей машине (gcc-7.3.0, Ubuntu 18.04 x86_64) я получил немного другой результат:

0000044d <ml_func>:
 44d:   55                      push   %ebp
 44e:   89 e5                   mov    %esp,%ebp
 450:   e8 29 00 00 00          call   47e <__x86.get_pc_thunk.ax>
 455:   05 ab 1b 00 00          add    $0x1bab,%eax
 45a:   8b 90 f0 ff ff ff       mov    -0x10(%eax),%edx
 460:   8b 0a                   mov    (%edx),%ecx
 462:   8b 55 08                mov    0x8(%ebp),%edx
 465:   01 d1                   add    %edx,%ecx
 467:   8b 90 f0 ff ff ff       mov    -0x10(%eax),%edx
 46d:   89 0a                   mov    %ecx,(%edx)
 46f:   8b 80 f0 ff ff ff       mov    -0x10(%eax),%eax
 475:   8b 10                   mov    (%eax),%edx
 477:   8b 45 0c                mov    0xc(%ebp),%eax
 47a:   01 d0                   add    %edx,%eax
 47c:   5d                      pop    %ebp
 47d:   c3                      ret 

Основное отличие, которое я обнаружил, заключалось в том, что семантика инструкции mov. В верхнем листинге mov ebp,esp фактически перемещает esp в ebp, а в нижнем листинге mov %esp,%ebp делает то же самое, но порядок операндов другой.

Это довольно запутанно, даже когда мне приходится кодировать рукописную сборку. Подводя итог, мои вопросы: (1) почему я получил разные представления сборки для одних и тех же инструкций и (2) какое из них я должен использовать при написании кода сборки (например, с __asm(:::);)


person shpark    schedule 16.03.2019    source источник
comment
Верхний соответствует синтаксису Intel, а нижний — синтаксису AT&T. Синтаксис AT&T отличается, и источник и пункт назначения меняются местами, поэтому это источник и пункт назначения. Если вам нужен синтаксис Intel с OBJDUMP, используйте параметр -Mintel   -  person Michael Petch    schedule 16.03.2019
comment
Что касается вашего второго вопроса, если вы компилируете с помощью GCC и хотите использовать синтаксис Intel во встроенной сборке, вы можете передать параметр -masm-intel в GCC. По умолчанию используется синтаксис AT&T.   -  person Michael Petch    schedule 16.03.2019
comment
быстрый способ сравнить Intel и AT&T — найти строки с немедленными значениями, такими как add ecx,0x1bb0 или добавить $0x1bab,%eax, которые устанавливают синтаксис, и затем вы можете переворачивать его или нет в уме, когда читаете его в зависимости от того, что вы считаете вменяемым. Какой порядок является разумным, зависит от порядка религии и политики, это очень личное.   -  person old_timer    schedule 16.03.2019
comment
другие подсказки относительно определенного возраста или синтаксиса, используемого в коде (язык ассемблера определяется ассемблером, инструментом, а не каким-либо стандартом), заключается в поиске знака процента в стиле mips в регистрах, стиль mips -0x10 (% eax) синтаксис или стиль Intel DWORD PTR [eax] с квадратными скобками, а не стиль Intel, как в Intel против AT&T, но стиль Intel в целом не зависит от AT&T или нет. Ваш первый пример - это классический язык ассемблера синтаксиса Intel в стиле Intel, последний - стиль ассемблера gnu, ассемблер gnu хорошо известен тем, что искажает синтаксис для всех целей, а не только для x86.   -  person old_timer    schedule 16.03.2019


Ответы (1)


obdjump по умолчанию используется -Matt синтаксис AT&T (как и ваш 2-й блок кода). См. att и intel-syntax. Вики тегов содержат некоторую информацию о различиях в синтаксисе: https://stackoverflow.com/tags/att/info vs. https://stackoverflow.com/tags/intel-syntax/info

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


Используйте objdump -d -Mintel для синтаксиса Intel. Я использую alias disas='objdump -drwC -Mintel' в своем .bashrc, поэтому я могу disas foo.o получить нужный мне формат с напечатанными релокациями (важно для понимания несвязанного .o), без переноса строк для длинных инструкций и с расшифрованными именами символов C++.


Во встроенном ассемблере вы можете использовать любой синтаксис, если он соответствует ожиданиям компилятора. По умолчанию используется AT&T, и я рекомендую использовать его для совместимости с clang. Возможно, есть способ, но clang не работает так же, как GCC с -masm=intel.

Кроме того, AT&T в основном является стандартным для встроенного ассемблера GNU C на x86, и это означает, что вам не нужны специальные параметры сборки для работы вашего кода.

Но вы можете использовать gcc -masm=intel для компиляции исходных файлов, которые используют синтаксис Intel в своих операторах asm. Это хорошо для вашего собственного использования, если вы не заботитесь о clang.


Если вы пишете код для заголовка, вы можете сделать его переносимым между синтаксисом AT&T и Intel, используя альтернативные диалекты, по крайней мере, для GCC:

static inline
void atomic_inc(volatile int *p) {
    // use __asm__ instead of asm in headers, so it works even with -std=c11 instead of gnu11
    __asm__("lock {addl $1, %0 | add %0, 1}": "+m"(*p));
// TODO: flag output for return value?
   // maybe doesn't need to be asm volatile; compilers know that modifying pointed-to memory is a visible side-effect unless it's a local that fully optimizes away.
   // If you want this to work as a memory barrier, use a `"memory"` clobber to stop compile-time memory reordering.  The lock prefix provides a runtime full barrier
}

source+asm outputs for gcc/clang в обозревателе компиляторов Godbolt.

С g++ -O3 (по умолчанию или -masm=att) мы получаем

atomic_inc(int volatile*):
    lock addl $1, (%rdi)              # operand-size is from my explicit addl suffix
    ret

С g++ -O3 -masm=intel мы получаем

atomic_inc(int volatile*):
    lock  add DWORD PTR [rdi], 1      # operand-size came from the %0 expansion
    ret

clang работает с версией AT&T, но не работает с -masm=intel (или -mllvm --x86-asm-syntax=intel, что подразумевается), потому что это, по-видимому, относится только к коду, созданному LLVM, а не к тому, как внешний интерфейс заполняет шаблон asm. .

Сообщение об ошибке clang:

<source>:4:13: error: unknown use of instruction mnemonic without a size suffix
    __asm__("lock {addl $1, %0 | add %0, 1}": "+m"(*p));
            ^
<inline asm>:1:2: note: instantiated into assembly here
        lock  add (%rdi), 1
        ^
1 error generated.

Он выбрал альтернативный синтаксис «Intel», но все же заполнил шаблон операндом памяти AT&T.

person Peter Cordes    schedule 16.03.2019
comment
Ух ты. Спасибо за прекрасное объяснение с примерами! - person shpark; 17.03.2019