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