Skip to content

Latest commit

 

History

History

05-calling-conventions-2

[АК]ОС 5. Соглашение о вызовах. c/c++ код из ассемблера, asm код из c/c++. Указатели на функции.

Видео

[yt]

[vk]

Файлики с кодом

  1. Пустой main и код возврата
  2. Запускаем c/c++ код из ассемблера: c, c++, asm
  3. Запускаем ассемблерный код из c/c++: c, c++, asm
  4. Указатели на функции: my_foreach.S, function_pointer.cpp

Код возврата

При завершении процесса судить о том, завершился он корректно или нет, можно по коду возврата. В код возврата выставляется в то значение, которое возвращается из main.

То есть значение rax, которое вернет main.

0 - процесс завершился корректно
любое другое число - была ошибка

Код возврата можно проверить следующим образом: Запустить бинарь и сразу после этого сделать echo $?. Запускать эту команду надо сразу после выполнения кода, так как туда сохраняется код возврата последнего запущенного процесса.

Также можно проверять следующим образом:

./a.out || echo "Failed"

В таком случае при ошибке будет выведен Failed.

Чтобы сделать return 0; из ассемблера надо обнулить регистр rax.

mov rax, 0 - работает, но лучше так не делать
xor rax, rax - лучше
xor eax, eax - еще лучше

⚠️ Warning! Если не обеспечить код возврата 0, то задача в контесте будет падать с RE.

Запуск c/c++ кода из ассемблера и наоборот

⚠️ Warning! Важно соблюдать соглашение о вызовах. Компилятор, который будет компилировать c/c++ код , соглашение соблюдает и ожидает этого же от кода на asm. Несоблюдение соглашения о вызовах почти всегда будет приводить к segfault.

extern "C"

Так как в C++, в отличие от C, разрешены перегруженные функции (различающиеся только аргументами), их имена изменяются с помощью name mangling — процесса преобразования имен функций. Подробнее можно прочитать здесь: https://www.emmtrix.com/wiki/Demystifying_C%2B%2B_-_Name_Mangling.

В нашем случае, функция multiply_add(int, int) в скомпилированном виде будет иметь имя _Z12multiply_addii. Линковщик для C++-файла попытается найти именно эту функцию, которой в .S файле нет, из-за чего произойдет ошибка линковки.

Использование extern "C" заставляет компилятор генерировать имена функций по правилам C (где имена должны быть уникальными, даже если аргументы разные). В результате линковщик будет искать функцию multiply_add, которая уже присутствует в .S файле, и линковка пройдет успешно.

c из asm

c, asm

Компиляция:

gcc main.S multiply_add.c

c++ из asm

c++, asm

Компиляция:

g++ main.S multiply_add.c

Функция в cpp файлике должна быть объявлена как extern "C"

extern "C" int multiply_add(int a, int b)

asm из c

c, asm Функция в .S файлике должна быть отмечена .global

.global multiply_add

В .c файлике должно быть объявление функции:

int multiply_add(int, int);

Компиляция:

gcc main.c multiply_add.S

asm из c++

c++, asm

Функция в .S файлике должна быть отмечена .global

.global multiply_add

В .cpp файлике должно быть объявление функции:

extern "C" int multiply_add(int, int);

Указатели на функции:

Текст взят из материалов прошлых лекций

Декларатор вида

int (*pfunc)(int a, int b);

читается следующим образом: pfunc - это указатель на функцию, принимающую два параметра типа int и возвращающую значение типа int. То есть pfunc - это переменная указатель на функцию. С помощью typedef переменная может быть объявлена следующим образом:

typedef int (*func_t)(int a, int b); // func_t - тип указателя на функцию
func_t pfunc;

Любая переменная-указатель на функцию может принимать значение NULL (или 0, или nullptr в Си++). Если переменная-указатель на функцию не равна NULL, она должна указывать на функцию, совместимую по передаваемым параметрам и возвращаемому значению.

Операция взятия адреса &, примененная к имени функции, дает значение типа указателя на функцию с соответствующим количеством и типом параметров и типом возвращаемого значения, например,

int handler(int value, int mask);

    &handler   // даст значение типа int (*)(int, int)

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

pfunc = handler;

это то же самое, что

pfunc = &handler;

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