Комментарии 92
Всё это ооочень странно, скажите пожалуйста, где это Вам понадобилось?
Постановка задачи
Что же я имел ввиду, когда написал «неизвестное» имя функции? А значит это то, что имя функции, её параметры и, в конце концов, соглашение вызова, становятся известными только во время выполнения программы. Займемся её вызовом! =)
Что же у нас есть:Хм…
1) char* sName — тут находится имя функции
2) int N — количество параметров
Сейчас я занимаюсь разработкой интерпретатора Си. Вызов функций, которые объявлены программистом, проблем не вызывает. Но нужно вызывать и другие функции из разных библиотек. Для этого и делал.
Аааа, ну извиняйте :-)
Мне теперь тоже стало интересно как это можно сделать, но на более высоком уровне языка.
Мне теперь тоже стало интересно как это можно сделать, но на более высоком уровне языка.
Если найдете решение на высшем уровне, скажите, я занимаюсь этим исследованием уже около месяца. Будет интересно посмотреть.
Можно предположить, что все параметры преобразуются в машинному слову [или в два машинных слова, если не влазит] и обычно функции не имеют больше N аргументов [имхо, меньше восьми].
Исходя из этого наклепать 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) Перед выходом — сама очищает стек
Т.е. логично сделать проеверку по вершине стека, если после выхода из функции она сместилась — вот тут и кидаем кесепшен или еще чего :)
1) Мы в стек заносим параметры
2) Функция делает сове черное дело
3) Перед выходом — сама очищает стек
Т.е. логично сделать проеверку по вершине стека, если после выхода из функции она сместилась — вот тут и кидаем кесепшен или еще чего :)
До чистки стека можно не добраться вообще.
Что если количество заявленных параметров не совпадает с фактически помещенными в стек, если фактических меньше?
Функция при выполнении начнет считывать параметры [esp+4],[esp+8] и т.д.
Начнем читать мусор в параметры, а если стек не наполнен, а параметров много, то можем и за границу сегмента вылезти, словим Access Violation или другого зверя.
Что если количество заявленных параметров не совпадает с фактически помещенными в стек, если фактических меньше?
Функция при выполнении начнет считывать параметры [esp+4],[esp+8] и т.д.
Начнем читать мусор в параметры, а если стек не наполнен, а параметров много, то можем и за границу сегмента вылезти, словим Access Violation или другого зверя.
Базовый контроль можно проевести через исключения например :)
Вы бы лучше прикрутили к своей программе Lua или какой-то другой интерпретируемый язык, и всё бы было хорошо.
А сейчас вы решаете метапроблему («создание С интерпретатора, чтобы можно было вызывать любую функцию с неизвестными параметрами») вместо решения проблемы «обеспечить вынос части логики программы в скрипт и обеспечить двустороннее взаимодействие между С кодом и скриптом».
Такие дела.
А сейчас вы решаете метапроблему («создание С интерпретатора, чтобы можно было вызывать любую функцию с неизвестными параметрами») вместо решения проблемы «обеспечить вынос части логики программы в скрипт и обеспечить двустороннее взаимодействие между С кодом и скриптом».
Такие дела.
Особенно если вспомнить, что есть такой зверь как IDispatch
подцеплять плагины,
имя которых нам неизвестно,
его можно взять из имени файла, который лежит в определенной директории
или типа того!!!
спасибо за статью, оч полезно!
имя которых нам неизвестно,
его можно взять из имени файла, который лежит в определенной директории
или типа того!!!
спасибо за статью, оч полезно!
www.sco.com/developers/devspecs/abi386-4.pdf — вот тут нужно читать про соглашение вызова, а не в Википедии.
По второй проблеме — почему бы не написать программу с использованием FP и потом посмотреть в дизассемблере как забрать результат? Или я снова ничего не понял?
Может так:
?
MOVQ [EAX], MM0
EMMS
?
по поводу Q сомневаюсь… double — вроде ж как 2 слова.
да и смотрел я листинги…
там сразу после вызова функции:
и каждый раз 16(%esp) разное.
и непонятно как в конкретную переменную положить.
я ассемблер знаю плохо, так что не взыщите)
да и смотрел я листинги…
там сразу после вызова функции:
call _Z4funcd
fstpl 16(%esp)
и каждый раз 16(%esp) разное.
и непонятно как в конкретную переменную положить.
я ассемблер знаю плохо, так что не взыщите)
sourceware.org/libffi/ — это не оно?
По идее здесь нужно изъять из ST(0) ответ. Пока что у меня не получилось этого сделать.
fstpl
подскажите пожалуйста, как его использовать?
тут я описал проблему...
тут я описал проблему...
А что, неужели нельзя просто использовать function pointer и делать приведение типов? Зачем с ассемблером связываться? Неужели аргументы вызываемых функций тоже будут неизвестны?
почитайте комментарии, аргументы неизвестны тоже.
Извините, не разглядел, что Вы делаете свой интерпретатор Си… Тогда здесь действительно сложно что-либо возразить :). Ваша статья будет безусловно очень полезна людям, которые будут заниматься тем же самым… А также будет наверняка использоваться не по назначению, поскольку Вы описали всё достаточно подробно :)).
«Если бы все было так просто...»
Чё за параша, где STL ???
А почему нет кода для раскрутки стека? В конвенциях cdecl раскручивать стек должен вызывающий код.
Переизобретение лямбда-функций, а потом и остальных столпов функционального программирования на С++?
Что-то мне подсказывает, что портированием будет ужас :)
По делу сказать нечего, посоветую только посмотреть на существующий cint
Иногда читая такие топики мне становится ужасно представить себе, что я провожу часы в долгой отладке в поисках потерянного байта! )
Я знал! Я знал, что Си это высшее шоманство…
>The cdecl calling convention is used by many C systems for the x86 architecture
Значит уже даже на amd64 способ не годится, а что насчет arm, mips?
Хм… вижу юзание ассемблера. Значит код не портируемый. На вид это эдакий маленький красивенький велосипедик, но с одним колесом, поэтому удерживать равновесие тяжело и далеко на нём не уедешь.
Одним словом таких вещей, как вызов неизвестной функции с неизвестными параметрами в рамках Си/Си++ хотеться не должно. А если очень хочется, то выбирайте более подходящие языки.
Значит уже даже на amd64 способ не годится, а что насчет arm, mips?
Хм… вижу юзание ассемблера. Значит код не портируемый. На вид это эдакий маленький красивенький велосипедик, но с одним колесом, поэтому удерживать равновесие тяжело и далеко на нём не уедешь.
Одним словом таких вещей, как вызов неизвестной функции с неизвестными параметрами в рамках Си/Си++ хотеться не должно. А если очень хочется, то выбирайте более подходящие языки.
хм, ну почему-же. не писать же весь проект на другом языке, лишь из-за этого.
Ну мне лично тяжело представить ситуации, когда без такого не обойтись. А наличие таких вот шаманств в продакшн коде может когда-нибудь и подвести, ибо это грабли. Хотя для общего развития и понимания работы процессора такие штуки оч полезны. И всё же прошу, при, написании подобных статей, указывать, что это решение представляет из себя грабли, которые могут в один прекрасный день по лбу дать.
>А значит это то, что имя функции, её параметры и, в конце концов, соглашение вызова, становятся известными только во время выполнения программы
Ну хорошо, задачку я понял. Это вполне хорошая тренировка для ума. Но где в реальном то коде такое может понадобится?
Ну хорошо, задачку я понял. Это вполне хорошая тренировка для ума. Но где в реальном то коде такое может понадобится?
прочитайте-же комментарий, на который я дал вам ссылку. в нем я написал, что пишу интерпретатор, в котором нужно вызывать функции из разных библиотек
тем что он компилятор :) исполняет он только скомпилированный им-же код.
>От других распространённых компиляторов TCC отличается прежде всего тем, что может исполнять скомпилированную им программу, то есть выполнять функцию интерпретатора. Данное свойство позволяет использовать язык Си в качестве скриптового языка
Разве это не то?
Разве это не то?
пробовал и под windows, оно работает:)
так что код портируемый. другое дело под 64bit…
так что код портируемый. другое дело под 64bit…
портируемость между разными аппаратными платформами, а не ОСями :)
Вообще, учитывая что Вы это делаете в рамках разработки интерпретатора непортируемость решения не есть недостаток. Сдается мне что процент портируемого кода в том же gcc весьма мал, все равно для каждой аппаратной платформы надо писать свой бэкенд.
Поправьте если где ошибся.
Вообще, учитывая что Вы это делаете в рамках разработки интерпретатора непортируемость решения не есть недостаток. Сдается мне что процент портируемого кода в том же gcc весьма мал, все равно для каждой аппаратной платформы надо писать свой бэкенд.
Поправьте если где ошибся.
На x86-64 код для win и linux отличается
о, а можно ссылочку?
svn.apache.org/viewvc/harmony/enhanced/drlvm/trunk/vm/interpreter/src/
Смотреть на файлы invoke*
Есть тонкости с выравниванием фрэйма. Может еще что, уже не помню, написал это 3+ года назад…
Смотреть на файлы invoke*
Есть тонкости с выравниванием фрэйма. Может еще что, уже не помню, написал это 3+ года назад…
>пробовал и под windows, оно работает:)
Я бы крайне удивился, если бы не работало. Ибо вроде как у Виндовса еще не появилось своего набора команд. В Линуксе и Виндовсе разное ABI, но вот код и там и там в итоге обычный x86
>другое дело под 64bit…
И не только. Вполне вероятно, что потребуется код портировать на arm архитектуру, которая вполне может соперничать с ia-32 и amd64 по распространённости. Вот тут и получите граблями по лбу :)
Я бы крайне удивился, если бы не работало. Ибо вроде как у Виндовса еще не появилось своего набора команд. В Линуксе и Виндовсе разное ABI, но вот код и там и там в итоге обычный x86
>другое дело под 64bit…
И не только. Вполне вероятно, что потребуется код портировать на arm архитектуру, которая вполне может соперничать с ia-32 и amd64 по распространённости. Вот тут и получите граблями по лбу :)
блин, человек какие-то элементы интерпретатора реализует, какой граблями по лбу? Что-то я не видел 100% переносимых компиляторов/интерпретаторов, наверное потому что пока их существование невозможно :)
А зачем простому человеку, кроме как в целях обучения и just for fun, этим заниматься? :) Поэтому и предупреждаю, что такие штуки в реальном коде нежелательны.
Писал такой код для i386, x86-64, ia64 (linux, win), если интересно могу кинуть ссылку. Ключевое слово apache harmony interpreter mode. Сейчас пишу с телефона, локаничность повышена.
См выше: svn.apache.org/viewvc/harmony/enhanced/drlvm/trunk/vm/interpreter/src/
invokeJNI*
invokeJNI*
Пишете свой rundll.exe? :-)
Хотя на самом деле у меня компилятор моего скриптового языка определял по началу описания функций, позже стал использовать .h файлы для проверок.
А гадать на кофейной гуще не дело на самом деле, данный подход не является безопасным и дает дорогу куче различных эксплоитов.
Хотя на самом деле у меня компилятор моего скриптового языка определял по началу описания функций, позже стал использовать .h файлы для проверок.
А гадать на кофейной гуще не дело на самом деле, данный подход не является безопасным и дает дорогу куче различных эксплоитов.
Совсем недавно реализовал примерно похожий функционал. Нужно было вызывать между DLL функции с переменным числом аргументов. Причём подключение тоже было динамическое, run-time.
Причём в моём случае, также заранее не был известен набор аргументов вызываемых функций. То есть сама вызываемая функция не знала, с какими аргументами её вызовут. Поэтому, все аргументы я передавал так: число аргументов, после чего список пар тип аргумента / значение.
Также хочу напомнить, что псевдостек x87 используется GCC. В случае использования компилятора M$, а также использования DLL, созданных на нём, числа с плавающей точкой надо передавать вместе со всеми остальными аргументами.
Причём в моём случае, также заранее не был известен набор аргументов вызываемых функций. То есть сама вызываемая функция не знала, с какими аргументами её вызовут. Поэтому, все аргументы я передавал так: число аргументов, после чего список пар тип аргумента / значение.
Также хочу напомнить, что псевдостек x87 используется GCC. В случае использования компилятора M$, а также использования DLL, созданных на нём, числа с плавающей точкой надо передавать вместе со всеми остальными аргументами.
У меня одного cdecl ассоциируется с «Сишный Децл»? :)
спасибо, забавная статья, но достаточно нежизнеспособный подход, я бы лично избегал использование таких конструкций " во избежании ".
Описанное соглашение — соглашение __stdcall. В __cdecl функция стек не очищает, его очищает _вызывающая_ функция. Отталкиваясь от той же статьи в англоязычной википедии, можно увидеть следующий код:
В статье нигде об этом не сказано. Исходя из вышеизложенного, автор все-таки пользуется соглашением __stdcall :-)
push c
push b
push a
call function_name
add esp, 12 ;Stack clearing
mov x, eax
В статье нигде об этом не сказано. Исходя из вышеизложенного, автор все-таки пользуется соглашением __stdcall :-)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Вызов функции с «неизвестным» именем на C++. Часть 1 — cdecl