Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Проверка прототипа функции из DLL


Автор: null56 2.6.2010, 20:03
Всем привет
Задача: пройти по файлам dll, найти функцию по имени, успешно вызвать
Решение:
Код

typedef void (__cdecl *my_func)(int, int);

int main()
{
    HINSTANCE module = LoadLibrary(__TEXT("C://my_file.dll"));
    if (module)
    {
        LPCSTR func_name = "testf";
        my_func func_from_dll = (my_func)GetProcAddress(module, func_name);
        if (func_from_dll)
        {
            int a; int b;
            try // не спасет всё равно
            {
                func_from_dll(a, b);
            }
            catch (...)
            {
                printf("Catch");
            }
        }
        else
        {
            //...
        }
        FreeLibrary(module);
    }
    else
    {
        //...
    }
    return 1;
}

Результат: всё по идее хорошо, но если прототип совпадает, а если нет? я так понимаю (и получаю) падение программы. у функции нет контроля над тем, что ты пытаешься загрузить, есть имя в def и адрес, к прототипу любого вида можно привезти
Вопрос: можно ли как - нибудь избежать падения, при динамическом импорте функций из библиотек, при несовпадании прототипов? какие - нибудь дополнительные проверки или другие функции для этих целей.

Надеюсь вопрос понятен, заранее благодарен за помощь

Автор: 12usver12 2.6.2010, 21:04
в отдельном потоке вызывать и ставить хук на завершении функции и смотреть адрес возврата

Автор: borisbn 2.6.2010, 22:43
Если библиотеки твои (или сделаны по твоим правилам), то можно в них заменить __cdecl на __stdcall, а также убрать extern "C". В этом случае прототип будет выглядеть как-то так
func_name@4i4i

Автор: Abyx 2.6.2010, 22:44
проверять esp

Автор: null56 2.6.2010, 22:56
borisbn, да, библы мои, но никогда не слышал об этом способе

Abyx, а можно по подробнее и что я должен там обнаружить?

Автор: xvr 2.6.2010, 23:01
Полной информации о прототипе функции в dll нет. Есть масса способов извлечь некоторую информацию о вызываемой функции. Большую часть их уже озвучили, могу добавить еще:
  •  Если функция С++ (и С++ манглинг не отключен), то прототип однозначно восстанавливается из С++ имени функции
  •  Если осталась отладочная информация, то прототип функции (и не только его) можно извлеч оттуда
  •  Если модель вызова функции была stdcall то размер переметров в стеке будет закодированна в числе после @ в имени функции (func@4 - 4 байта параметров)
  •  Исследовать код функции на предмет пролога и эпилога (требует весьма недюжих познаний во многих областях)

Автор: jonie 2.6.2010, 23:40
все игры с стеком справдливы только для win32 (!) это важно....

Автор: Dem_max 3.6.2010, 06:38
Цитата

Задача: пройти по файлам dll, найти функцию по имени, успешно вызвать


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

Автор: Abyx 3.6.2010, 10:07
borisbn, xvr, при чем тут манглинг?
Если известно имя функции, то очевидно известен ее прототип, в том числе число параметров.
Если функция неправильная, то никакой манглинг тут не поможет.
Импортируется функция вместе с манглингом, GetProcAddress(hlib, "foo@8"), расшифровка никакой новой информации не даст.

Проверка esp позволит узнать правильное ли было количество параметров у функции, после ее вызова, не более того.

Автор: GremlinProg 3.6.2010, 10:29
Цитата(Abyx @  3.6.2010,  12:07 Найти цитируемый пост)
borisbn, xvr, при чем тут манглинг?

при декорировании например C++ прототип и кодируется именем функции,
stdcall - тоже, но в меньшей степени: тут можно определить только размер фрейма

так что манглинг тут многое может прояснить,
конечно, все можно получить из отладочной секции, но доступ к имени гораздо проще,

рекомендую декорирование C++ взять на контроль, если надо обязательно знать прототип,
в vs есть утилитка undname.exe и есть соответствующая API

недостаток у них только есть небольшой: слишком длинные имена не распознают, это особенно актуально при экспорте шаблонных имплементаций

Автор: Abyx 3.6.2010, 10:37
Как я понимаю, у ТСа проблема в том, что он не уверен, что функция в длл будет иметь ожидаемый прототип.
Например - длл это плагин, написанный сторонним разработчиком на другом языке.
В этом случае, ТС может специфицировать что функция имеет прототип
extern "C" void __stdcall foo(int a, int b);
соответственно в таблице экспорта длл должно быть имя "foo@8"

но человек пишущий длл, может писать ее на другом языке, и напишет
Код

proc foo x, y, z
....
export foo as "foo@8"


Добавлено @ 10:43
какбэ от того что ТС напишет
Код

typedef void (__stdcall *my_func)(int, int);
.............
        LPCSTR func_name = "testf@8";
        my_func func_from_dll = (my_func)GetProcAddress(module, func_name);

ничего не поменяется


кроме того что это усложнит жизнь тому кто пишет длл

Автор: null56 3.6.2010, 10:48
Abyx, да про это и речь, что там вообще может быть всё что угодно, те же 8 байт параметров и та же функция... а в функции происходит работа вообще с другими типами... Мне нужна длл с моими четкими параметрами (TCHAR *, std :: vector<std::basic_string<TCHAR> > &) = те же 8 байтов (для 32 бит)
Но GremlinProg,  xvr, про декорирование имен говорят, сейчас читаю и пытаюсь понять, зашит ли там при сборке длл прототип, если да, то это как раз то что нужно....
http://en.wikipedia.org/wiki/Name_mangling

Автор: Abyx 3.6.2010, 10:50
Цитата(null56 @  3.6.2010,  10:48 Найти цитируемый пост)
зашит ли там при сборке длл прототип

только в С или в С++ у конкретного компилятора.

если используете msvc - пишите в длл у функции __declspec(dllexport), и импортируйте получившееся имя

Автор: GremlinProg 3.6.2010, 11:00
Цитата(Abyx @  3.6.2010,  12:37 Найти цитируемый пост)

но человек пишущий длл, может писать ее на другом языке, и напишет
Код

proc foo x, y, z
....
export foo as "foo@8"


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

гораздо проще приложение может упасть в самой библиотеке :)

try-except не защитит только от ошибки со стеком, т.к. она фатальна в любой ситуации,
все остальное поймает

Автор: Abyx 3.6.2010, 11:02
восстановление ebp решает все ошибки со стеком %)
т.е. try сохраняет ebp, и при отлове эксепшена ebp восстанавливается, а в эпилоге функции восстанавливается и esp
собственно если в функции есть ebp-фрейм, на ошибки с esp можно не обращать внимания, лишь бы esp не поднимался выше нижней границы фрейма

Автор: GremlinProg 3.6.2010, 11:07
Цитата(Abyx @  3.6.2010,  13:02 Найти цитируемый пост)
восстановление ebp решает все ошибки со стеком %)

а ebp ты где сохранишь перед восстановлением :))

Автор: jonie 3.6.2010, 11:11
Abyx, да ну?)) а если я запишу в адрес возрата своё что-нибудь)?

Даешь каждому плагину по отдельному процессу и пайпы (например) для обмена информацией !))

В любом случае вы не сможете защититься от хитрых (тупых) программистов которые косячат в плагинах и используют хаки - процесс-то один... плагин может тупо вызвать ExitProcess(0) идите - ловите..

Считаю что проверка стека и т.д. - избыточна, это микроскопом гвозди зибивать -- если плагин кривой, то он кривой, и тут уже ничего не сделать.

Автор: Abyx 3.6.2010, 11:13
Цитата(GremlinProg @  3.6.2010,  11:07 Найти цитируемый пост)
а ebp ты где сохранишь перед восстановлением smile) 

хотя действительно, если вызываемая функция решит что у нее больше параметров чем должно быть, 
и если она начнет менять значения "параметров", то она может убить SEH фрейм.

Тогда надо создавать новый стек.

Добавлено через 33 секунды
jonie, при чем тут адрес возврата?

Автор: jonie 3.6.2010, 11:15
Abyx, функция может убить стек вообще (например если у функции 2-а параметра (указателя) куда она активно пишет, а вызывающий думает что 1)...

Автор: Abyx 3.6.2010, 11:16
Цитата(jonie @  3.6.2010,  11:11 Найти цитируемый пост)
Считаю что проверка стека и т.д. - избыточна, это микроскопом гвозди зибивать -- если плагин кривой, то он кривой, и тут уже ничего не сделать.

Во-первых прога не должна падать из за кривых плагинов. Это надо предусмотреть, иначе юзеры будут недовольны, когда прога будет молча падать на старте.
Во вторых если разработчик плагина написал не тот прототип, неплохо чтобы прога ему это сообщила. Хороший пример - это COM. Передал неверное число параметров - получил соответствующее исключение, знаешь где ошибка.

Добавлено через 31 секунду
jonie, читайте пост выше

Автор: GremlinProg 3.6.2010, 11:20
Цитата(Abyx @  3.6.2010,  13:13 Найти цитируемый пост)
Тогда надо создавать новый стек.

это и решает новый поток, хотя его нарушение по-моему ситуацию не меняет, т.к. этот стек в той же памяти процесса
скорее всего поддержу jonie:
Цитата(jonie @  3.6.2010,  13:11 Найти цитируемый пост)
Даешь каждому плагину по отдельному процессу и пайпы (например) для обмена информацией !))


Автор: Abyx 3.6.2010, 11:24
впринципе да, можно ставить VEH и создавать новый поток, ловить исключение и убивать поток.
что случится со стеком потока - не важно.

Автор: GremlinProg 3.6.2010, 11:24
Цитата(Abyx @  3.6.2010,  13:16 Найти цитируемый пост)
Передал неверное число параметров - получил соответствующее исключение, знаешь где ошибка.

ну и тут то же самое: COM-объект имеет свой динамический прототип, который можно переврать точно так же как и название функции при экспорте

Автор: Abyx 3.6.2010, 11:28
GremlinProg, однако COM безопасен, и выдает информативные сообщения об ошибках

Автор: GremlinProg 3.6.2010, 11:35
он безопасен на том же уровне, что и правильно написанный плагин
неправильно написанный COM небезопасен точно так же как и неправильно написанный плагин
и ни какие методы самоконтроля COM тут не помогут: упадет точно так же

единственная разница в том, что прототип COM-объекта модифицировать сложнее чем поменять имя экспортируемой функции, отсюда и видимая безопасность

Автор: xvr 3.6.2010, 14:22
Цитата(null56 @  3.6.2010,  10:48 Найти цитируемый пост)
Мне нужна длл с моими четкими параметрами (TCHAR *, std :: vector<std::basic_string<TCHAR> > &) = те же 8 байтов (для 32 бит)
Если это твои параметры - то все, сливай воду, туши свет  smile Классы (даже по ссылке), включая stl контейнеры, крайне не рекомендуется передавать в plugin'ы. Т.к. их реализация сильно зависит от версии компилятора и библиотек.

Цитата(Abyx @  3.6.2010,  11:28 Найти цитируемый пост)
GremlinProg, однако COM безопасен, и выдает информативные сообщения об ошибках
Не путайте COM с ActiveX (IDispatch). Второй - безопасен, первый - нет


Автор: Abyx 3.6.2010, 14:27
TCHAR - это вообще #define

Автор: null56 3.6.2010, 14:31
xvr, хорошо, что сказали... сделаем так:  std :: vector<std::basic_string<TCHAR> > => (unsigned int, TCHAR **), иными словами без стл сделаем

Добавлено через 1 минуту и 54 секунды
Цитата(Abyx @ 3.6.2010,  14:27)
TCHAR - это вообще #define

пускай будет wchar_t

Автор: xvr 3.6.2010, 14:40
Кстати, plugin вполне может упасть не только при вызове неправильно оформленной функции, но и в процессе ее работы. Ошибки, они везде бывают  smile И защититься от этого можно только полной изоляцией dll с plugin'ом (в другом процессе). Но это тормоз, нет - ТОРМОЗ, или даже ТОРМОЗИЩЕ  smile 

Автор: borisbn 3.6.2010, 15:06
Цитата(null56 @  3.6.2010,  10:48 Найти цитируемый пост)
Мне нужна длл с моими четкими параметрами (TCHAR *, std :: vector<std::basic_string<TCHAR> > &) 

очень не советую передавать между exe и dll stl-классы, т.к. это будет нормально работать только если и то и то собрано одним и тем же компилятором, причём одной и той же версии SDK.

Автор: jonie 3.6.2010, 15:15
borisbn, и с одними и теми же параметрами компилятора..

Цитата

Ошибки, они везде бывают  smile И защититься от этого можно только полной изоляцией dll с plugin'ом (в другом процессе). Но это тормоз, нет - ТОРМОЗ, или даже ТОРМОЗИЩЕ  smile 

xvr, смотря что делает плагин, может ему надо всего-то послать сообщение "начни работать"

Автор: borisbn 3.6.2010, 15:57
Цитата(jonie @  3.6.2010,  15:15 Найти цитируемый пост)
borisbn, и с одними и теми же параметрами компилятора..

Да, конечно. Просто в своих классах/структурах/интерфейсах, передающихся между dll и exe всегда ставлю явное выравнивание ( pragma pack... ), явный тип вызова ( __stdcall или __cdecl ) и явный размер в enum'ах ( enumName_FORCE_DWORD = 0x7FFFFFFF ),
поэтому и забыл о параметрах компилятора smile

Автор: jonie 3.6.2010, 20:02
borisbn, указанные опции не спасут тебя, если в коде stl стоит нечто вроде "#ifdef FAST_FLOAT ..." (т.е. внутренни шаблонные классы могут скопилиться по-разному даже если задать то что ты описал )

Автор: borisbn 3.6.2010, 20:08
jonie, полностью согласен, что stl нельзя передавать в/из dll. Я говорил о своих классах и структурах

Автор: null56 4.6.2010, 10:37
Мне посоветовали наиболее быстрое решение предотвращения ошибки
Код

    __try
    {
// вызываем функцию с произвольным набором параметров
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
// обрабатываем исключение
    }

Надежный ли это вариант ловли вызова с неверными параметрами? у меня на тестовом примере исключение отрабатывает

Автор: Abyx 4.6.2010, 11:05
от неверного числа параметров это не поможет

Автор: Dem_max 4.6.2010, 11:37
Цитата

от неверного числа параметров это не поможет


Ага, ошибка может и не выскочит, но не известно когда приложение свалится

Автор: null56 4.6.2010, 12:09
просто я передавал и даже не передавал вообще параметры, вроде ловила она этот момент... но видимо это частный случай.... 

Автор: rogi08 4.6.2010, 12:33
А прокатит ли вариант с блоком 
Код

__try { }
__except(EXCEPTION_EXECUTE_HANDLER) { }
Если функция в dll перед работой будет предварительно тестить параметры, тем самым будем знать, где dll точно валится.

Автор: GremlinProg 4.6.2010, 12:44
Цитата(rogi08 @  4.6.2010,  14:33 Найти цитируемый пост)
Если функция в dll перед работой будет предварительно тестить параметры, тем самым будем знать, где dll точно валится.

проблема будет не обязательно в невалидных параметрах,

проблема будет при возврате из функции, когда эта функция как-либо потрет(изменит) стек, где по ее мнению расположен фрейм,
а на самом деле там будут переменные или фреймы основной программы,
состояние которых после вызова и будут определять, где конкретно свалится программа

чем больше будет потерто лишнего, тем больше переменных и фреймов основной программы будет влиять на то, где оно свалится,
так что в общем случае не спасет даже забор вложенных try-except

Автор: rogi08 4.6.2010, 13:07
подойдёт ли вариант с функцией SetUnhandledExceptionFilter?

Автор: Abyx 4.6.2010, 13:11
rogi08, вы ее когданить использовать пробовали?
вернее так:
пример кода покажите который решает задачу ТСа

Автор: rogi08 4.6.2010, 13:50
Abyx, 
идею использования SetUnhandledExceptionFilter предложил punxer: http://wasm.ru/forum/viewtopic.php?pid=382029#p382029 
Судя на коду и комментариям, идея не подходит.

Автор: Abyx 4.6.2010, 13:59
rogi08, я тоже читаю васм, не надо сюда кидать ссылки %)
алсо какой смысл повторять чужие неверные идеи %)

Автор: rogi08 6.6.2010, 16:28
Abyx
Цитата(Abyx @  4.6.2010,  13:59 Найти цитируемый пост)
я тоже читаю васм, не надо сюда кидать ссылки

ссылки выкладывал не лично для Вас))

Цитата(Abyx @  4.6.2010,  13:59 Найти цитируемый пост)
какой смысл повторять чужие неверные идеи

Это было лишь предположением, сверьте даты сообщений здесь и на васм.

Выскажите свою правильную и красивую идею.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)