Можно ли вызвать функцию с чужим стеком?
Имеется C-код с ассемблерной вставкой. Функция my_printf хорошо отрабатывает, но плохо завершается (Bus error (core dumped)).
Подскажите пожалуйста правильный пролог и эпилог - ведь должны же они быть!
//FreeBSD 10.0, gcc-4.8
//gcc48 asm.cpp -Wall -g -masm=intel -o a
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;
}
int my_printf(const char* s, int i) {
...
/*
вызываем printf
*/
...
return 1;
}
int main() {
my_printf("Digit: %d\n", 100);
return 1;
}
Вот еще материал, может пригодится.
Вы предлагаете разобраться с чем-то попроще, но с чем-то попроще проблем нет никаких. Вот простой рабочий пример:
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 в свой метод с произвольным количеством переменных, используя при этом минимальное количество кода.
Если всё же кто-то найдёт решение даже спустя год, просьба выложить решение. Это дико интересно - я обыскал весь интернет - ничего подобного никто нигде не предлагал.
Вы предлагаете разобраться с чем-то попроще, но с чем-то попроще проблем нет никаких. Вот простой рабочий пример:
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 <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;
}
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;
}
//отключить оптимизации
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;
}
Не универсальный это метод и экономию может дать только при большом числе параметров, пересылаемых по стеку.
Думаю, компиляторы подобные оптимизации и без нас умеют сами и лучше.
//отключить оптимизации компилятора
//вариант с возвратом прямо в 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;
}
команды асма 'enter' и 'leave' вам в помощь .
Если собрались использовать это в методах, то учтите, что нестатические методы вызываются по соглашению thiscall. Кроме дополнительного неявного параметра там и со стеком нюансы. Насколько понимаю у компиляторов и даже, возможно, их версий, а также платформы, этот thiscall по-разному реализуется. Придется вникать в тему указателя this и указателя на нестатические функции-члены и в документацию по компилятору.
P.S. Плюньте на все эти ручные низкоуровневые оптимизации. Возможно вы просто недооцениваете возможностей современных компиляторов в оптимизации. Про непереносимость исходников с ассемблерными вставками между компиляторами я уж и не говорю.