Русские документы
Ежедневные компьютерные новости RSS rusdoc.ru  Найти :
Новости
Последние поступления
Книжный магазин
  Hardware:
Видеоустройства
Системные платы
Процессоры
Мобильные устройства
Аудиосистема
Охлаждение системы
Накопители информации
КПК и ноутбуки
Телефоны и связь
Периферия
Система
Сети
Разные устройства
 
  Programming:
Web-разработка
Языки программирования
Технологии и теория
Разработка игр
Программная инженерия
 
  Software:
Операционные системы
Windows 7
Базы данных
Обзоры программ
Графика и дизайн
   
  Life:
Компьютерная жизнь
Разные материалы
   
Партнеры
Публикация
Правовая информация
Реклама на сайте
Обратная связь
Экспорт в RSS Экспорт в RSS2.0
    Читать в Яндекс.Ленте



Вызов функции с «неизвестным» именем на C++. Часть 1 — cdecl

Раздел: Programming / С++ @ 21.12.2009 | Ключевые слова: функция cpp версия для печати

Автор: Antigluk
Источник: habrahabr

Постановка задачи


Что же я имел ввиду, когда написал «неизвестное» имя функции? А значит это то, что имя функции, её параметры и, в конце концов, соглашение вызова, становятся известными только во время выполнения программы. Займемся её вызовом! =)

Сейчас мы попробуем вызвать функцию по стандарту cdecl.
Выдержка из Википедии:
The cdecl calling convention is used by many C systems for the x86 architecture. In cdecl, function parameters are pushed on the stack in a right-to-left order. Function return values are returned in the EAX register (except for floating point values, which are returned in the x87 register ST0). Registers EAX, ECX, and EDX are available for use in the function.

В общем, параметры передаются через стек в обратном порядке, результирующее значение будет в EAX кроме чисел с плавающей точкой — они будут в псевдо-стеке x87.

Составим план работы:
1) Сгенерировать некий буфер в памяти, который можно будет без изменений, пословно(4 байта) поместить в стек.
2) Узнать адрес функции, которую будем вызывать
3) Поместить в стек буфер по словам
4) Вызвать функцию
5) Выдернуть результат

Поехали!



Что же у нас есть:
1) char* sName — тут находится имя функции
2) int N — количество параметров
3) enum CParamType {cptNone=0, cptPointer, cptInt, cptDouble} — возможные типы данных — ограничимся пока что этими
4) CParamType Params[] — список типов параметров
5) void* ParamList[] — собственно, указатели на переменные с параметрами
6) CParamType RetType — тип данных результата
7) void* Ret — указатель на память, куда нужно скинуть результат
8) enum CCallConvention {cccNone=0, cccCDecl, cccStdCall,cccFastCall} — типы соглашений вызова
9) CCallConvention conv — соглашение вызова. Для начала будем вызывать только cdecl функции

Это необходимый и достаточный список объявлений, которые нам нужны для вызова.
На C/C++ нету средств для осуществления этой операции, поэтому придется обратиться к ассемблеру.

1. Создаем буфер


Во-первых, посчитаем количество слов. Все просто — void*, int — 4 байта — 1 слово, double — 8 байт — 2 слова.
int WordCount=0;
for(int i=0,i {
switch(Params[i])
{
case cptPointer:
case cptInt:
WordCount++;
break;
case cptDouble:
WordCount+=2;
break;
}
}


Посчитали. Выделяем память:
void* Buffer = new char[4*WordCount];

Заполняем буфер: void*, int — помещаем без изменений, а в double меняем слова местами.
int offset=0;
double x;
for(int i=0,i {
switch(Params[i])
{
case cptPointer:
case cptInt:
*(int*)(buf+offset)=*((int*)(ParamList[i]));
offset+=4;
break;
case cptDouble:
x=*((double*)(((DTMain*)(v->T))->pData));
memcpy(buf+offset+4,&x,4);
memcpy(buf+offset,(char*)&x+4,4);
offset+=8;
break;
}
}


Думаю, тут комментировать нечего. offset — смещение по буферу.

2. Узнаем адрес функции


Тут все достаточно просто.
void* addr = dlsym(NULL,sName);
Где первый параметр — дескриптор библиотеки. NULL для поиска в текущем контексте.
Подключаем dlfcn.h и не забываем в параметры линковки дописать -ldl.

3. Помещаем в стек буфер по словам


Фуух. Самое интересное.
Для работы со стеком нам, естественно, понадобится ассемблер. Я пользуюсь gnu компилятором, поэтому ассемблер с синтаксисом AT&T — ногами не пинать, мне самому не очень нравится, но выбирать не приходится.

asm("\
movl $0, %%eax;\
movl %2,%%ebx; \
movl %3,%%ecx; \
l1: cmpl %%ecx, %%eax; \
je l2;\
pushl (%%ebx,%%eax,4); \
addl $1,%%eax;\
jmp l1;"
:"=r"(b)
: "r"(addr), "r"(Buffer), "g"(WordCount)
: "%eax"
);


Мы делаем цикл: пока ecx (WordCount) не станет 0, кладем в стек слово и уменьшаем ecx.

4. Вызываем функцию



Делаем
l2: call *%1;
после заполнения стека. %1 — указатель на функцию (addr).

5. Возвратить результат



Тут 2 варианта: целый результат или дробный. Согласно соглашению, по умолчанию результат будет в %eax, но если с плавающей точкой — то в всевдо-стеке x87.
1) Целый результат
movl %%eax, %0;
где %0 — переменная результата.

2) Вариант с плавающей точкой
По идее здесь нужно изъять из ST(0) ответ. Пока что у меня не получилось этого сделать. Хотелось бы увидеть в комментариях возможные решения. Заранее спасибо.

Ну вот и все! Задача была действительно не тривиальная. Надеюсь, этот пост кому-то понадобится.







версия для печатиРаспечатать статью


Вернуться в раздел: Programming / С++


Реклама:
Читать наc на:

Add to Google
Читать в Яндекс.Ленте






Rambler's Top100
© Copyright 1998-2012 Александр Томов. All rights reserved.