Указатели на функции и неизвестное количество аргументов в C ++ - PullRequest
7 голосов
/ 04 марта 2010

Я наткнулся на следующий странный кусок кода. Представьте, что у вас есть следующий typedef:

typedef int (*MyFunctionPointer)(int param_1, int param_2);

И затем в функции мы пытаемся запустить функцию из DLL следующим образом:

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

Проблема в том, что нет никакого способа узнать, принимает ли функция, адрес которой мы получили с LoadLibrary, два целочисленных аргумента. Имя dll предоставляется пользователем во время выполнения, затем перечисляются имена экспортируемых функций и пользователь выбирает тестируемый (опять же, во время выполнения: S: S). Итак, выполняя вызов функции в последней строке, не открываем ли мы дверь для возможного повреждения стека? Я знаю, что это компилируется, но какая ошибка во время выполнения произойдет в случае, если мы передаем неправильные аргументы функции, на которую мы указываем?

Ответы [ 8 ]

4 голосов
/ 04 марта 2010

Боюсь, что нет способа узнать - программист должен знать прототип заранее, когда получает указатель на функцию и использует его.

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

4 голосов
/ 04 марта 2010

Есть три ошибки, о которых я могу думать, если ожидаемое и используемое число или тип параметров и соглашение о вызовах отличаются:

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

В двух словах: Undefined behavior

3 голосов
/ 04 марта 2010

Если это функция __stdcall и , то они оставили название без изменений (оба большие if, но, конечно, возможно, тем не менее), имя будет иметь @nn в конце, где nn это число. Это число - число байтов, которые функция ожидает в качестве аргументов и удалит их из стека, прежде чем она вернется.

Итак, если это серьезная проблема, вы можете взглянуть на необработанное имя функции и проверить, что объем данных, которые вы помещаете в стек, соответствует количеству данных, которые он собирается очистить из стека.

Обратите внимание, что это все еще только защита от Мерфи, а не Макиавелли. Когда вы создаете DLL, вы можете использовать файл экспорта для изменения имен функций. Это часто используется для удаления искажения имени - но я уверен, что это также позволит вам переименовать функцию с xxx @ 12 на xxx @ 16 (или что-то еще), чтобы ввести читателя в заблуждение относительно ожидаемых параметров.

Редактировать: (в основном в ответ на комментарий msalters): это правда, что вы не можете применить __stdcall к чему-то вроде функции-члена, но вы, безусловно, можете использовать его для таких вещей, как глобальные функции, независимо от того, написаны они на C или C ++.

Для таких вещей, как функции-члены, экспортированное имя функции будет искажено. В этом случае вы можете использовать UndecorateSymbolName, чтобы получить полную подпись. Использовать это несколько нетривиально, но не слишком сложно.

1 голос
/ 04 марта 2010

Нет общего ответа. Стандарт обязывает выдавать определенные исключения при определенных обстоятельствах, но помимо этого описывает, как будет выполняться соответствующая программа, и иногда говорится, что определенные нарушения должны приводить к диагностике. (Здесь или там может быть что-то более конкретное, но я точно не помню этого.)

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

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

К сожалению, очень вероятно, что ответ будет что-то напортачить, не будучи сразу очевидным.

1 голос
/ 04 марта 2010

Это может привести к сбою в коде DLL (так как он получил поврежденные данные), или: Я думаю, что Visual C ++ добавляет код в отладочные сборки, чтобы обнаружить проблему такого типа. Он скажет что-то вроде: «Значение ESP не было сохранено при вызове функции» и будет указывать на код рядом с вызовом. Это помогает, но не полностью надежно - я не думаю, что это остановит вас, передавая неправильный аргумент, но размер одинакового размера (например, int вместо параметра char * в x86). Как говорят другие ответы, вам просто нужно знать, на самом деле.

1 голос
/ 04 марта 2010

Я так не думаю, это хороший вопрос, единственным условием является то, что вы ДОЛЖНЫ знать, каковы параметры для работы указателя функции, если вы не и вслепую заполняете параметры и вызываете их, это сбой или прыжок в лес, чтобы его больше никогда не было видно ... Программист должен передать сообщение о том, что ожидает функция и тип параметров, к счастью, вы можете разобрать ее и узнать, посмотрев на указатель стека и ожидаемый адрес с помощью «указателя стека» (sp), чтобы узнать тип параметров.

Например, используя PE Explorer, вы можете узнать, какие функции используются, и изучить дамп разборки ...

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

0 голосов
/ 04 марта 2010

Большинство компиляторов C / C ++ имеют вызывающую программу, которая устанавливает стек перед вызовом, а затем корректирует указатель стека. Если вызываемая функция не использует указатель или ссылочные аргументы, не будет никакого повреждения памяти, хотя результаты будут бесполезными. И, как говорит повторный анализ, ошибки указателя / ссылки почти всегда обнаруживаются при небольшом тестировании.

0 голосов
/ 04 марта 2010

Обычно, если вы вызываете LoadLibrary и GetProcByAddrees, у вас есть документация, которая сообщает вам прототип. Еще чаще, как и во всех Windows.dll, вам предоставляется файл заголовка. Несмотря на то, что это приведет к ошибке, в противном случае ее обычно очень легко заметить, а не к той ошибке, которая может проникнуть в производство.

...