gcc x86-32 выравнивание стека и вызов printf - PullRequest
0 голосов
/ 12 сентября 2018

Насколько мне известно, x86-64 требует, чтобы стек был выровнен по 16 байтов перед вызовом, в то время как gcc с -m32 не требует этого для main .

У меня есть следующий код тестирования:

.data
intfmt:         .string "int: %d\n"
testint:        .int    20

.text
.globl main

main:
    mov     %esp, %ebp
    push    testint
    push    $intfmt
    call    printf
    mov     %ebp, %esp
    ret

Сборка с as --32 test.S -o test.o && gcc -m32 test.o -o test.Я знаю, что syscall write существует, но, насколько мне известно, он не может печатать целые числа и обрабатывает данные так, как это делает printf.

После ввода main 4-байтовый адрес возврата находится в стеке.Затем, наивно интерпретируя этот код, каждый из двух push-вызовов помещает в стек по 4 байта, поэтому для вызова требуется еще одно 4-байтовое значение, переданное для выравнивания.

Вот objdump двоичного файла, сгенерированного gas и gcc:

0000053d <main>:
 53d:   89 e5                   mov    %esp,%ebp
 53f:   ff 35 1d 20 00 00       pushl  0x201d
 545:   68 14 20 00 00          push   $0x2014
 54a:   e8 fc ff ff ff          call   54b <main+0xe>
 54f:   89 ec                   mov    %ebp,%esp
 551:   c3                      ret    
 552:   66 90                   xchg   %ax,%ax
 554:   66 90                   xchg   %ax,%ax
 556:   66 90                   xchg   %ax,%ax
 558:   66 90                   xchg   %ax,%ax
 55a:   66 90                   xchg   %ax,%ax
 55c:   66 90                   xchg   %ax,%ax
 55e:   66 90                   xchg   %ax,%ax

Я очень озадачен сгенерированными инструкциями push.

  1. Если передаются два 4-байтовых значения, как достигается выравнивание?
  2. Почему вместо 0x14 выдвигается 0x2014?Что такое 0x201d?
  3. Чего вообще достигает call 54b?Вывод hd соответствует objdump.Почему это отличается в GDB?Это динамический компоновщик?

B+>│0x5655553d <main>                       mov    %esp,%ebp                      │
   │0x5655553f <main+2>                     pushl  0x5655701d                     │
   │0x56555545 <main+8>                     push   $0x56557014                    │
   │0x5655554a <main+13>                    call   0xf7e222d0 <printf>            │
   │0x5655554f <main+18>                    mov    %ebp,%esp                      │
   │0x56555551 <main+20>                    ret  

Ресурсы о том, что происходит, когда исполняется двоичный файл, приветствуются, так как я не знаю, что на самом деле происходит, иучебники, которые я прочитал, не охватывают это.Я нахожусь в процессе чтения Как запускаются программы: ELF binaries .

1 Ответ

0 голосов
/ 12 сентября 2018

i386 System V ABI дает гарантию / требует выравнивания стека 16 байт перед call, как я сказал в начале моего ответа, который вы связали.(Если только вы не вызываете частную вспомогательную функцию, в этом случае вы можете создать свои собственные правила для выравнивания, передачи аргументов и того, какие регистры перекрываются для этой функции.)

Функции допускается сбой или неправильное поведение, если вы нарушаете это требование ABI, но не обязаны это делать. например, scanf в x86-64 Ubuntu glibc (составленный недавним gcc) только недавно начал делать это: scanf Ошибки сегментации при вызове из функции, которая не изменяет RSP

Функции могут зависеть от выравнивания стека для производительности (для выравнивания double или массива double s, чтобы избежать кеширования-линии разделяется при обращении к ним).

Обычно единственный случай, когда функция зависит от выравнивания стека для правильности , это когда она скомпилирована для использования SSE / SSE2, поэтому она может использовать 16-байтовое выравниваниетребуется загрузить / сохранить для копирования структуры или массива (movaps или movdqa) или для автоматической векторизации цикла над локальным массивом.

Я думаю, что Ubuntu не компилируетсяих 32-битные библиотеки с SSE (за исключением функций типа memcpy, которые используют диспетчеризацию во время выполнения), поэтому они все еще могут работать на древних процессорах, таких как Pentium II.Многоархатные библиотеки в системе x86-64 должны принимать SSE2, но с 4-байтовыми указателями менее вероятно, что 32-битные функции будут иметь 16-байтовые структуры для копирования.

В любом случае, независимо от причины, очевидно, printfв вашей 32-битной сборке glibc на самом деле не зависит от правильности выравнивания стека 16 байт, поэтому он не ошибается даже при неправильном выравнивании стека.


Почему 0x2014толкнул вместо 0х14?Что такое 0x201d?

0x14 (десятичное число 20) - это значение в памяти в этом месте.Он будет загружен во время выполнения, потому что вы использовали push r/m32, а не push $20 (или постоянную времени сборки, такую ​​как .equ testint, 20 или testint = 20).

Для создания gcc -m32PIE (Position Independent Executable), который перемещается во время выполнения , потому что это значение по умолчанию в gcc Ubuntu.

0x2014 - это смещение относительно начала файла.Если вы разберетесь во время выполнения после запуска программы, вы увидите реальный адрес.

То же самое для call 54b.Предположительно, это вызов PLT (который находится рядом с началом сегмента файла / текста, следовательно, с низким адресом).

Если вы разберетесь с objdump -drwC, вы увидите информацию о перемещении символов.(Мне также нравится -Mintel, но будьте осторожны, это MASM-подобный, а не NASM).

Вы можете связать с gcc -m32 -no-pie, чтобы сделать классические исполняемые файлы зависимыми .Я бы определенно рекомендовал это, особенно для 32-битного кода, и особенно, если вы компилируете C, используйте gcc -m32 -no-pie -fno-pie, чтобы получить не-PIE code-gen, а также ссылки на исполняемый файл без PIE.(см. 32-разрядные абсолютные адреса, более не разрешенные в Linux x86-64? для получения дополнительной информации о PIE.)

...