Указатели функции и неизвестное количество аргументов в C++

Я столкнулся со следующим странным блоком кода. Предположите, что у Вас есть следующее определение типа:

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). Так, путем выполнения вызова функции в последней строке, не мы открывающий дверь в возможное повреждение стека? Я знаю, что это компилирует, но какая ошибка времени выполнения движение должно произойти в случае, что мы передаем неправильные аргументы функции, на которую мы указываем?

7
задан Emil D 4 March 2010 в 15:14
поделиться

8 ответов

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

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

В двух словах: Неопределенное поведение

4
ответ дан 6 December 2019 в 15:21
поделиться

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

1
ответ дан 6 December 2019 в 15:21
поделиться

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

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

4
ответ дан 6 December 2019 в 15:21
поделиться

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

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

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

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

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

3
ответ дан 6 December 2019 в 15:21
поделиться

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

0
ответ дан 6 December 2019 в 15:21
поделиться

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

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

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

1
ответ дан 6 December 2019 в 15:21
поделиться

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

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

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

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

1
ответ дан 6 December 2019 в 15:21
поделиться

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

0
ответ дан 6 December 2019 в 15:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: