Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: 6457
Последний выпуск: 19.06.2015

Можно ли вызвать функцию с чужим стеком?

88K
26 января 2015 года
Ансельм Студиозус
4 / / 26.01.2015
Всем доброе время суток!
Имеется C-код с ассемблерной вставкой. Функция my_printf хорошо отрабатывает, но плохо завершается (Bus error (core dumped)).
Подскажите пожалуйста правильный пролог и эпилог - ведь должны же они быть!

//FreeBSD 10.0, gcc-4.8
//gcc48 asm.cpp -Wall -g -masm=intel -o a

Код:
#include <stdio.h>
int
my_printf(const char* s, ...) {
  int (*p)(const char*, ...)=printf;
  asm(
    "add ebp,8 n"
    "mov esp,ebp n"
    "mov edx,%0 n"
    "call edx n"
    :
    :"r"(p)
  );
  return 1;
}

int
main() {
  my_printf("Hello, %s!n", "World");
  return 1;
}
315
26 января 2015 года
sadovoya
757 / / 19.11.2005
И my_printf и printf обычные cdecl ф-ции (и main, кстати, тоже). Вам надо вызывать printf в соответствии с cdecl соглашениями вызовов, если решили ассемблерную вставку делать. Что уже есть у my_print при входе в нее и где? Что и где нужно иметь перед вызовом из нее printf? Что компилятор в пролог/эпилог сам заносит, надо ли что-то корректировать? Вначале попробуйте разобраться с чем-то попроще, с фиксированным числом параметров, типа:

Код:
#include <stdio.h>

int my_printf(const char* s, int i) {
...
  /*
    вызываем printf
 */

...
  return 1;
}

int main() {
  my_printf("Digit: %d\n", 100);
  return 1;
}
P.S. Зачем вам это вообще? Если для познания механизмов вызовов, то лучше пишите простенькие c-программы, собирайте и дизассемблируйте. Гораздо проще разобраться. Тем более ассемблерные вставки gcc - еще дополнительные заморочки.
Вот еще материал, может пригодится.
88K
27 января 2015 года
Ансельм Студиозус
4 / / 26.01.2015
Благодарю за отзыв.
Вы предлагаете разобраться с чем-то попроще, но с чем-то попроще проблем нет никаких. Вот простой рабочий пример:
Код:
#include <stdio.h>

int
my_printf(const char* s, ...) {
  int (*p)(const char*, ...)=printf;
  asm(
    "sub esp,8 \n"          // выделяем место под новый стек 8 байт
    "mov edx,[ebp+8] \n"    // вычитываем первый аргумент из старого стека ("Hello, %s!n")
    "mov [esp],edx \n"      // копируем первый аргемент в новый стек
    "mov edx,[ebp+12] \n"   // вычитываем второй аргумент из старого стека ("World")
    "mov [esp+4],edx \n"    // копируем второй аргемент в новый стек
    "mov edx,%0 \n"         // находим алрес процедуры printf
    "call edx \n"           // вызываем процедуру printf
    "add esp,8 \n"          // уничтожаем новый стек
    :
    :"r"(p)
  );
  return 1;
}

int
main() {
  my_printf("Hello, %s!n", "World");
  return 1;
}
Смысл подобных примеров в том, что мы создаём новый стек, куда копируем аргументы из стека старого. Минус такого подхода в том, что нам заранее надо знать количество аргументов - это раз; другой минус - мы зачем-то создаём новый стек - это два. Возникает закономерный вопрос - если уже есть готовый "старый стек", почему бы его не передать в новую функцию напрямик?
Вы спрашиваете, зачем мне это надо? Например для того, чтобы завернуть стандартную функцию printf в свой метод с произвольным количеством переменных, используя при этом минимальное количество кода.
Если всё же кто-то найдёт решение даже спустя год, просьба выложить решение. Это дико интересно - я обыскал весь интернет - ничего подобного никто нигде не предлагал.
253
27 января 2015 года
Ramon
1.1K / / 16.08.2003
Благодарю за отзыв.
Вы предлагаете разобраться с чем-то попроще, но с чем-то попроще проблем нет никаких. Вот простой рабочий пример:
Код:
#include <stdio.h>

int
my_printf(const char* s, ...) {
  int (*p)(const char*, ...)=printf;
  asm(
    "sub esp,8 n"          // выделяем место под новый стек 8 байт
    "mov edx,[ebp+8] n"    // вычитываем первый аргумент из старого стека ("Hello, %s!n")
    "mov [esp],edx n"      // копируем первый аргемент в новый стек
    "mov edx,[ebp+12] n"   // вычитываем второй аргумент из старого стека ("World")
    "mov [esp+4],edx n"    // копируем второй аргемент в новый стек
    "mov edx,%0 n"         // находим алрес процедуры printf
    "call edx n"           // вызываем процедуру printf
    "add esp,8 n"          // уничтожаем новый стек
    :
    :"r"(p)
  );
  return 1;
}

int
main() {
  my_printf("Hello, %s!n", "World");
  return 1;
}
Смысл подобных примеров в том, что мы создаём новый стек, куда копируем аргументы из стека старого. Минус такого подхода в том, что нам заранее надо знать количество аргументов - это раз; другой минус - мы зачем-то создаём новый стек - это два. Возникает закономерный вопрос - если уже есть готовый "старый стек", почему бы его не передать в новую функцию напрямик?
Вы спрашиваете, зачем мне это надо? Например для того, чтобы завернуть стандартную функцию printf в свой метод с произвольным количеством переменных, используя при этом минимальное количество кода.
Если всё же кто-то найдёт решение даже спустя год, просьба выложить решение. Это дико интересно - я обыскал весь интернет - ничего подобного никто нигде не предлагал.



Вы не создаёте стек, вы занимаетесь х*рнёй.
Для справки: переменное количество аргуметов внезапно не обязано лежать на стеке.

Код:
#include <stdarg.h>
#include <stdio.h>

int my_printf(const char* s, ...)
{
    va_list ap;
    int r;

    va_start(ap, s);
    r = vprintf(s, ap);
    va_end(ap);

    return r;
}
88K
27 января 2015 года
Ансельм Студиозус
4 / / 26.01.2015
Я намеренно упростил вопрос, для примера используя простую функцию printf, но вовсе не с целью получить тривиальный ответ. Нетривиальное решение не может быть реализовано без использования ассемблерной вставки. На всякий случай уточню вопрос: речь идёт о корректном использовании функцией чужого стека - как аккуратно туда зайти - и главное аккуратно выйти, ничего не нарушив. Повторю исходный код с комментариями:

Код:
#include <stdio.h>
void
my_printf(const char* s, ...) {
  int (*p)(const char*, ...)=printf;
  asm(
    "add ebp,8 n"         // смещаем кадр стека в позицию первого аргумента
    "mov esp,ebp n"       // совмещаем вершину стека с началом кадра
    "mov edx,%0 n"        // получаем адрес нашей функции printf
    "call edx n"          // вызываем printf
    :
    :"r"(p)
  );
}

int
main() {
  my_printf("Hello, %s!n", "World");
  return 1;
}
Это почти работает!
315
31 января 2015 года
sadovoya
757 / / 19.11.2005
Вызывающая сторона при вызове my_printf кладет в стек адрес возврата, сама my_printf должна обеспечить сохранность регистра ebp. Скорее всего она его сохранит на стеке (но не факт) до других действий со стеком (тоже не факт), а в конце - восстановит. Если это так, как представляется, то к моменту ассемблерной вставки на вершине стека уже лежит начальное содержимое ebp и далее переданный ф-ции адресс возврата (потом уже параметры). Значит, мы должны два первых от вершины значения снять до вызова printf (они ей лишние), а после вызова printf вернуть. Вот сама схема действий, без оптимизаций:

Код:
#include <stdio.h>
//отключить оптимизации

int my_printf(const char* s,...) {
    static volatile int ebp=0, ret=0;
    asm volatile (
        "popl %2\n" //ebp
        "popl %1\n" //ret
        "call %0\n"
        "pushl %1\n"
        "pushl %2\n"
        :
        :"r"(printf),"m"(ret),"m"(ebp)
        :"memory"
    );
    return 1;
}

int main() {
    my_printf("Hello, %s %d %f\n", "World!", -100, .89);
    return 1;
}
У меня работает. Но одних volatile (с целью необходимого здесь подавления оптимизаций компилятором) не хватило. То работала, то нет при пересборках. Пришлось еще ключами отключить оптимизации компилятору вообще.
Не универсальный это метод и экономию может дать только при большом числе параметров, пересылаемых по стеку.
Думаю, компиляторы подобные оптимизации и без нас умеют сами и лучше.
315
01 февраля 2015 года
sadovoya
757 / / 19.11.2005
Вариант с возвратом прямо в main после джампа на printf:

Код:
#include <stdio.h>
//отключить оптимизации компилятора

//вариант с возвратом прямо в main после джампа на printf

void my_printf(const char* s,...) {
    asm volatile (
        "popl %%ebp\n"
        "jmpl %0\n"
        :
        :"r"(printf)
    );
}

int main() {
    my_printf("Hello, %s %d %f\n", "World!", -100, .89);
    return 1;
}
243
02 февраля 2015 года
koderAlex
1.4K / / 07.09.2005
ковырять руками стек очень не рекомендуется . )
команды асма 'enter' и 'leave' вам в помощь .
315
04 февраля 2015 года
sadovoya
757 / / 19.11.2005
Цитата:
Вы спрашиваете, зачем мне это надо? Например для того, чтобы завернуть стандартную функцию printf в свой метод


Если собрались использовать это в методах, то учтите, что нестатические методы вызываются по соглашению thiscall. Кроме дополнительного неявного параметра там и со стеком нюансы. Насколько понимаю у компиляторов и даже, возможно, их версий, а также платформы, этот thiscall по-разному реализуется. Придется вникать в тему указателя this и указателя на нестатические функции-члены и в документацию по компилятору.

P.S. Плюньте на все эти ручные низкоуровневые оптимизации. Возможно вы просто недооцениваете возможностей современных компиляторов в оптимизации. Про непереносимость исходников с ассемблерными вставками между компиляторами я уж и не говорю.

Знаете кого-то, кто может ответить? Поделитесь с ним ссылкой.

Ваш ответ

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог