Как стать автором
Обновить

Комментарии 92

Всё это ооочень странно, скажите пожалуйста, где это Вам понадобилось?
Постановка задачи

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

Что же у нас есть:
1) char* sName — тут находится имя функции
2) int N — количество параметров
Хм…
а что странного? «становятся известными только во время выполнения программы» — это и значит что в переменной sName значение появляется только в runtime — например, при чтении с клавиатуры.
Сейчас я занимаюсь разработкой интерпретатора Си. Вызов функций, которые объявлены программистом, проблем не вызывает. Но нужно вызывать и другие функции из разных библиотек. Для этого и делал.
Аааа, ну извиняйте :-)
Мне теперь тоже стало интересно как это можно сделать, но на более высоком уровне языка.
Если найдете решение на высшем уровне, скажите, я занимаюсь этим исследованием уже около месяца. Будет интересно посмотреть.
Можно предположить, что все параметры преобразуются в машинному слову [или в два машинных слова, если не влазит] и обычно функции не имеют больше N аргументов [имхо, меньше восьми].

Исходя из этого наклепать N функциональных типов и вызывать функций через приведение типа.
Что-то в духе:
   size_t ExecuteFunc__сdecl(void* func_ptr, int argc, size_t argv[] )
   {
      typedef size_t (__cdecl *func0)(void);
      typedef size_t (__cdecl *func1)(size_t);
      ....
      typedef size_t (__cdecl *funcN)(size_t, .... , size_t);
   
      switch( argc ) {
         case 0:  return ((func0)func_ptr)();
         case 1:  return ((func1)func_ptr)(argv[0]);
         ....
         case N:  return ((func0)func_ptr)(argv[0], argv[1], .... , argv[N]);
      }
      /* как-то разбираемся с нештатной ситуацией */
      return 0;
   }

Возвращаемое значение «использовать по вкусу» — исходя из того должна ли функция вернуть что-либо или нет.
Получается китайский код, но избавляемся от ассемблера ^_^ Не уверен, что такой вариант лучше, но все же ;)
Соглашения о вызове — штука низкоуровневая. Выше никак.
минусанул топик не разобравшись, возвращаю в карму — всё по чесноку.
НЛО прилетело и опубликовало эту надпись здесь
А если я скажу, что я не собираюсь контролировать? ;)
Ну во-первых, учитывая интерпретаторность ( :) ) при объявлении импортированой функции мы явно указываем список параметров. Да, я не могу узнать сколько параметров ДОЛЖНА принимать эта функция. Я передаю ровно столько, сколько задано в скрипте.
Да, это огромная дыра в безопасности, но пока что я о ней не сильно заботился.
Пока что я не вижу решений этой проблемы. Могу ответить одно — пускай проактивная защита этим занимается ;)
Возможное решение проблемы — изменить стиль вызова функции. Я точно не помню как он зовется (вроде pascal), но его суть такая:
1) Мы в стек заносим параметры
2) Функция делает сове черное дело
3) Перед выходом — сама очищает стек

Т.е. логично сделать проеверку по вершине стека, если после выхода из функции она сместилась — вот тут и кидаем кесепшен или еще чего :)
До чистки стека можно не добраться вообще.
Что если количество заявленных параметров не совпадает с фактически помещенными в стек, если фактических меньше?
Функция при выполнении начнет считывать параметры [esp+4],[esp+8] и т.д.
Начнем читать мусор в параметры, а если стек не наполнен, а параметров много, то можем и за границу сегмента вылезти, словим Access Violation или другого зверя.
Базовый контроль можно проевести через исключения например :)
НЛО прилетело и опубликовало эту надпись здесь
дак это всегда можно сделать если речь идет об асме :-D
НЛО прилетело и опубликовало эту надпись здесь
Вы бы лучше прикрутили к своей программе Lua или какой-то другой интерпретируемый язык, и всё бы было хорошо.

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

Такие дела.
«создание С интерпретатора, чтобы можно было вызывать любую функцию с неизвестными параметрами»… вообще-то я решаю другую проблему:
«Добавление в интерпретатор С возможность вызова любой функции с неизвестными параметрами».
тогда ок =)
тогда ок =)
Особенно если вспомнить, что есть такой зверь как IDispatch
подцеплять плагины,
имя которых нам неизвестно,
его можно взять из имени файла, который лежит в определенной директории
или типа того!!!

спасибо за статью, оч полезно!
Согласен, просто в википедии наиболее кратко для цитаты.
По второй проблеме — почему бы не написать программу с использованием FP и потом посмотреть в дизассемблере как забрать результат? Или я снова ничего не понял?
Может так:
MOVQ [EAX], MM0
EMMS

?
по поводу Q сомневаюсь… double — вроде ж как 2 слова.
да и смотрел я листинги…
там сразу после вызова функции:
call _Z4funcd
fstpl 16(%esp)

и каждый раз 16(%esp) разное.
и непонятно как в конкретную переменную положить.
я ассемблер знаю плохо, так что не взыщите)
я тоже не ас.
MM0 — 64 разрядный регистр, так что Q в самый раз.
Ааа, всё, понял что Вы имели ввиду :-)
Да, тут нужно подумать.
Думаю Вам нужно ещё узнать тип результата. Ведь результат в интерпретаторе должен куда-то присваиваться…
тип данных результата я знаю:
CParamType RetType
Не встречал, посмотрю.
По идее здесь нужно изъять из ST(0) ответ. Пока что у меня не получилось этого сделать.


fstpl
А что, неужели нельзя просто использовать function pointer и делать приведение типов? Зачем с ассемблером связываться? Неужели аргументы вызываемых функций тоже будут неизвестны?
почитайте комментарии, аргументы неизвестны тоже.
Извините, не разглядел, что Вы делаете свой интерпретатор Си… Тогда здесь действительно сложно что-либо возразить :). Ваша статья будет безусловно очень полезна людям, которые будут заниматься тем же самым… А также будет наверняка использоваться не по назначению, поскольку Вы описали всё достаточно подробно :)).
«Если бы все было так просто...»
Чё за параша, где STL ???
при чем тут STL?
А при чём тут C++?
STL тут явно не при чём.
А почему нет кода для раскрутки стека? В конвенциях cdecl раскручивать стек должен вызывающий код.
Переизобретение лямбда-функций, а потом и остальных столпов функционального программирования на С++?
Ну подскажите более красивое решение. Я буду вам только благодарен
Писать на функциональных языках. :-)
А по проблеме, если я не путаю, нечто подобное есть (ну или будет) в стандарте C++0x и уже более-менее реализовано в GCC 4.4.x
вы говорите о лямбда-выражениях в 0x?
Да.
НЛО прилетело и опубликовало эту надпись здесь
Что-то мне подсказывает, что портированием будет ужас :)
извиняюсь за задержку, подсветил.
По делу сказать нечего, посоветую только посмотреть на существующий cint

Иногда читая такие топики мне становится ужасно представить себе, что я провожу часы в долгой отладке в поисках потерянного байта! )
Я знал! Я знал, что Си это высшее шоманство…
>The cdecl calling convention is used by many C systems for the x86 architecture

Значит уже даже на amd64 способ не годится, а что насчет arm, mips?
Хм… вижу юзание ассемблера. Значит код не портируемый. На вид это эдакий маленький красивенький велосипедик, но с одним колесом, поэтому удерживать равновесие тяжело и далеко на нём не уедешь.
Одним словом таких вещей, как вызов неизвестной функции с неизвестными параметрами в рамках Си/Си++ хотеться не должно. А если очень хочется, то выбирайте более подходящие языки.
хм, ну почему-же. не писать же весь проект на другом языке, лишь из-за этого.
Ну мне лично тяжело представить ситуации, когда без такого не обойтись. А наличие таких вот шаманств в продакшн коде может когда-нибудь и подвести, ибо это грабли. Хотя для общего развития и понимания работы процессора такие штуки оч полезны. И всё же прошу, при, написании подобных статей, указывать, что это решение представляет из себя грабли, которые могут в один прекрасный день по лбу дать.
может и грабли, но иногда без этого не обойтись.
>А значит это то, что имя функции, её параметры и, в конце концов, соглашение вызова, становятся известными только во время выполнения программы

Ну хорошо, задачку я понял. Это вполне хорошая тренировка для ума. Но где в реальном то коде такое может понадобится?
прочитайте-же комментарий, на который я дал вам ссылку. в нем я написал, что пишу интерпретатор, в котором нужно вызывать функции из разных библиотек
тем что он компилятор :) исполняет он только скомпилированный им-же код.
>От других распространённых компиляторов TCC отличается прежде всего тем, что может исполнять скомпилированную им программу, то есть выполнять функцию интерпретатора. Данное свойство позволяет использовать язык Си в качестве скриптового языка

Разве это не то?
нет. он перед выполнением кода компилирует его.
пробовал и под windows, оно работает:)
так что код портируемый. другое дело под 64bit…
портируемость между разными аппаратными платформами, а не ОСями :)
Вообще, учитывая что Вы это делаете в рамках разработки интерпретатора непортируемость решения не есть недостаток. Сдается мне что процент портируемого кода в том же gcc весьма мал, все равно для каждой аппаратной платформы надо писать свой бэкенд.

Поправьте если где ошибся.
На x86-64 код для win и linux отличается
о, а можно ссылочку?
спасибо.
>пробовал и под windows, оно работает:)
Я бы крайне удивился, если бы не работало. Ибо вроде как у Виндовса еще не появилось своего набора команд. В Линуксе и Виндовсе разное ABI, но вот код и там и там в итоге обычный x86
>другое дело под 64bit…
И не только. Вполне вероятно, что потребуется код портировать на arm архитектуру, которая вполне может соперничать с ia-32 и amd64 по распространённости. Вот тут и получите граблями по лбу :)
блин, человек какие-то элементы интерпретатора реализует, какой граблями по лбу? Что-то я не видел 100% переносимых компиляторов/интерпретаторов, наверное потому что пока их существование невозможно :)
А зачем простому человеку, кроме как в целях обучения и just for fun, этим заниматься? :) Поэтому и предупреждаю, что такие штуки в реальном коде нежелательны.
в моем случае это так, но вы считаете, что интерпретаторы пишут только just for fun?
А в реальном коде реального интерпретатора как Вы без этого обойдетесь? О_о
Хорошо, беру свои слова обратно, в интерпретаторах без такого тяжеловато.
Писал такой код для i386, x86-64, ia64 (linux, win), если интересно могу кинуть ссылку. Ключевое слово apache harmony interpreter mode. Сейчас пишу с телефона, локаничность повышена.
А запостите, мне тоже очень интересно :)
интересно, очень!
см чуть ниже, никак не могу попасть ;)
Пишете свой rundll.exe? :-)

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

А гадать на кофейной гуще не дело на самом деле, данный подход не является безопасным и дает дорогу куче различных эксплоитов.
дописал в конец статьи, так как много вопросов очень по поводу зачем все это :)
ну в какой-то мере да. интерпретатор я пишу, а это его часть.
а небезопасно — да. нужно обдумать это…
Совсем недавно реализовал примерно похожий функционал. Нужно было вызывать между DLL функции с переменным числом аргументов. Причём подключение тоже было динамическое, run-time.

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

Также хочу напомнить, что псевдостек x87 используется GCC. В случае использования компилятора M$, а также использования DLL, созданных на нём, числа с плавающей точкой надо передавать вместе со всеми остальными аргументами.
У меня одного cdecl ассоциируется с «Сишный Децл»? :)
у одного :D я читаю как «сидекл»
спасибо, забавная статья, но достаточно нежизнеспособный подход, я бы лично избегал использование таких конструкций " во избежании ".
к сожалению, избежать я этого не могу в своем проекте
Описанное соглашение — соглашение __stdcall. В __cdecl функция стек не очищает, его очищает _вызывающая_ функция. Отталкиваясь от той же статьи в англоязычной википедии, можно увидеть следующий код:
push c
push b
push a
call function_name
add esp, 12 ;Stack clearing
mov x, eax

В статье нигде об этом не сказано. Исходя из вышеизложенного, автор все-таки пользуется соглашением __stdcall :-)
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории