Как избавиться от звонка __x86.get_pc_thunk.ax - PullRequest
0 голосов
/ 30 апреля 2018

Я пытался скомпилировать и преобразовать очень простую C-программу в язык ассемблера.

Я использую Ubuntu, и тип ОС - 64-битный.

Это программа на Си.

void add();

int main() { 
add();
return 0;
}

если я использую gcc -S -m32 -fno-asynchronous-unwind-tables -o simple.S simple.c вот как должен выглядеть мой файл с исходным кодом сборки:

.file   "main1.c"
.text
.globl main
.type   main, @function
main:
pushl   %ebp
movl    %esp, %ebp
andl    $-16, %esp
call    add
movl    $0, %eax
movl    %ebp, %esp
popl    %ebp
ret
.size   main, .-main
.ident  "GCC: (Debian 4.4.5-8) 4.4.5" // this part should say Ubuntu instead of Debian
.section    .note.GNU-stack,"",@progbits

но вместо этого это выглядит так:

.file   "main0.c"
.text
.globl  main
.type   main, @function
main:
leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
pushl   %ebp
movl    %esp, %ebp
pushl   %ebx
pushl   %ecx
call    __x86.get_pc_thunk.ax
addl    $_GLOBAL_OFFSET_TABLE_, %eax
movl    %eax, %ebx
call    add@PLT
movl    $0, %eax
popl    %ecx
popl    %ebx
popl    %ebp
leal    -4(%ecx), %esp
ret
.size   main, .-main
.section        

.text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl  __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type   __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
movl    (%esp), %eax
ret
.ident  "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
.section    .note.GNU-stack,"",@progbits

В моем университете мне сказали использовать флаг -m32, если я использую 64-битную версию Linux. Может кто-нибудь сказать мне, что я делаю не так? Я даже использую правильный Флаг?

редактировать после -fno-pie

.file   "main0.c"
.text
.globl  main
.type   main, @function
main:
leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
pushl   %ebp
movl    %esp, %ebp
pushl   %ecx
subl    $4, %esp
call    add
movl    $0, %eax
addl    $4, %esp
popl    %ecx
popl    %ebp
leal    -4(%ecx), %esp
ret
.size   main, .-main
.ident  "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
.section    .note.GNU-stack,"",@progbits

выглядит лучше, но не совсем так. например, что означает Лил?

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Как правило, нельзя ожидать, что два разных компилятора сгенерируют один и тот же код сборки для одного и того же ввода, даже если они имеют одинаковый номер версии; они могут иметь любое количество дополнительных «патчей» для генерации кода. Пока наблюдаемое поведение одинаково, все идет.

Вы также должны знать, что GCC в режиме по умолчанию -O0 генерирует намеренно плохой код. Он настроен для простоты отладки и скорости компиляции, а не для ясности или эффективности сгенерированного кода. Часто код, генерируемый gcc -O1, легче понять, чем код, генерируемый gcc -O0.

Вы также должны знать, что функция main часто требует дополнительных настроек и демонтажа, которые не нужны другим функциям. Инструкция leal 4(%esp),%ecx является частью этой дополнительной настройки. Если вы хотите понять только машинный код, соответствующий коду , который вы написали, а не подробности ABI , назовите вашу тестовую функцию иначе, чем main.

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


Теперь, чтобы ответить на вопрос, который был буквально задан, причина появления

call __x86.get_pc_thunk.ax

потому что ваш компилятор по умолчанию генерирует «независимые от позиции» исполняемые файлы. Независимо от позиции означает, что операционная система может загрузить машинный код программы по любому адресу в (виртуальной) памяти, и она все еще будет работать. Это допускает такие вещи, как рандомизация макета адресного пространства , но чтобы это работало, вам нужно предпринять специальные шаги для установки «глобального указателя» в начале каждой функции, которая обращается к глобальным переменным или вызывает другую функцию ( с некоторыми исключениями). На самом деле проще объяснить сгенерированный код, если включить оптимизацию:

main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ebx
        pushl   %ecx

Это всего лишь настройка стека фрейма main и сохранение регистров, которые необходимо сохранить. Вы можете игнорировать это.

        call    __x86.get_pc_thunk.bx
        addl    $_GLOBAL_OFFSET_TABLE_, %ebx

Специальная функция __x86.get_pc_thunk.bx загружает свой обратный адрес, который является адресом следующей сразу инструкции addl, в регистр EBX. Затем мы добавляем к этому адресу значение магической константы _GLOBAL_OFFSET_TABLE_, которая в позиционно-независимом коде представляет собой разницу между адресом инструкции, которая использует _GLOBAL_OFFSET_TABLE_, и адресом глобальная таблица смещений . Таким образом, EBX теперь указывает на глобальную таблицу смещений.

        call    add@PLT

Теперь мы вызываем add@PLT, что означает вызов add, но прыгаем через "таблицу связей процедур", чтобы сделать это. PLT заботится о том, что add определяется в общей библиотеке, а не в основном исполняемом файле. Код в PLT использует глобальную таблицу смещений и предполагает, что вы уже установили EBX, чтобы указывать на него, перед вызовом символа @PLT. Вот почему main должен настроить EBX, хотя, кажется, ничто его не использует. Если бы вы вместо этого написали что-то вроде

 extern int number;
 int main(void) { return number; }

тогда вы увидите прямое использование GOT, что-то вроде

    call    __x86.get_pc_thunk.bx
    addl    $_GLOBAL_OFFSET_TABLE_, %ebx
    movl    number@GOT(%ebx), %eax
    movl    (%eax), %eax

Мы загружаем EBX с адресом GOT, затем мы можем загрузить адрес глобальной переменной number из GOT, и затем мы фактически разыменовываем адрес, чтобы получить значение number.

Если вместо этого вы скомпилируете 64-битный код, вы увидите что-то другое и гораздо более простое:

    movl    number(%rip), %eax

Вместо того, чтобы возиться с GOT, мы можем просто загрузить number с фиксированного смещения от счетчика программы. Добавлена ​​относительная к ПК адресация, а также 64-разрядные расширения архитектуры x86. Точно так же ваша исходная программа в 64-битном независимом от позиции режиме просто скажет

    call    add@PLT

без предварительной настройки EBX. Вызов все еще должен проходить через PLT, но PLT использует саму относительную к ПК адресацию и не нуждается в помощи своего вызывающего абонента.


Единственная разница между __x86.get_pc_thunk.bx и __x86.get_pc_thunk.ax заключается в том, в каком регистре они хранят свой обратный адрес: EBX для .bx, EAX для .ax. Я также видел, как GCC генерирует .cx и .dx вариантов. Вопрос только в том, какой регистр он хочет использовать для глобального указателя - это должен быть EBX, если будут вызовы через PLT, но если их нет, он может использовать любой регистр, поэтому он пытается выберите тот, который не нужен ни для чего другого.


Почему она вызывает функцию для получения обратного адреса? Старые компиляторы сделали бы это вместо этого:

    call 1f
1:  pop  %ebx

но это облажает предсказание обратного адреса , поэтому в настоящее время компилятору приходится сталкиваться с некоторыми дополнительными проблемами, чтобы убедиться, что каждый call связан с ret.

0 голосов
/ 30 апреля 2018

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

После переименования в xmain я получаю:

xmain:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        call    add
        movl    $0, %eax
        leave
        ret
...