Приветствую. Уважаемые, при генерации exe-файла в masm каждое обращение к API-функции превращается в вызове переходника. Например: call ExitProcess компилируется в call xxxxxxxx и где-то по адресу xxxxxxxx: call [ExitProcess] Причем это происходит независимо от того, как вызвать ExitProcess. Пробовал 3 варианта: - с описанием ExitProcess proto - с описанием extern ExitProcess - с явным указанием вызова call [ExitProcess] Объясните, пожалуйста, можно ли в masm напрямую вызывать импортируемые функции call [foo] И, если можно, то как. Спасибо
to max7C4 сорри, но для чего тогда придуманы таблицы импорта? я думаю, что этот переходник сидит в библиотеке импорта. В моем случае в kernel32.lib Тогда вопрос, как С++ обходит этот вопрос. Там функции импорта вызываются правильно.
SnowPawn Если имеешь в виду зачем в конечном коде используются переходники типа call xxxxx, jmp [yyyy]. Вместо того чтобы просто вызвать код нужной функции, то смотри если в твоем коде неск раз вызывается одна и та же функция, то загрузчику придется при каждой встрече команды вызова такой функции копировать ее адрес в место вызова (адрес нужной функции), в то время как при использовании переходников такая необходимость отпадает.
to Vic так мой вопрос в том и заключается, почему masm не поступает так же, как С++. зачем он использует первый call?
Тут , могу конечно напутать , но ИМХО вопрос к разработчикам... потому что, например, при компиляции файлов .с, с помощью gcc, вызов тоже через переходники выполняется...
спасибо, сам разобрался. сгенерировал asm-код простенькой С++ программы и понял. lib-библиотеки предоставляют нам по два вида каждой функции: foo и _imp__foo. используя первый - получим переходник. используя второй - прямой вызов call [foo].
Кто-то спрашивал такое не так давно. В общем, похоже, что это сделано компилятором для инкрементального линкера - чтоб быстрее линковать (в некоторых случаях добавки/удаления кода). Посмотри на код в релизе.
SnowPawn длина инструкции call _imp__foo - 6 байт, call foo - 5 байт, jmp _imp__foo - 6 байт составляем уравнение n*6 > n*5 + 6 --> отсюда мораль, если foo требуется вызвать 6 и менее раз программа с call _imp__foo будет короче, в противном случае (более 6 раз вызвать foo) используйте call foo и jmp _imp__foo. Правда, если в одном месте требуется несколько раз вызвать API-функцию с разными параметрами (например, SendMessage), а использовать цикл затруднительно, тогда можно использовать следующую конструкцию: lea esi,_imp__foo (6 байт), а затем несколько раз вызвать call dword ptr [esi] (2 байта). Вместо esi можно использовать edi, ebx, ebp по вкусу
На самом деле соглашение по вызову WinAPI-функции не всегда совпадает с соглашением, которое используется или хотябы поддерживается в конкретном языке программирования. Поэтому в общем случае заглушки нужны. Про эффективность использования того или иного способа здесь было сказано все верно. Добавлю только то, что call [procentry] к примеру в DLL, где может потребоваться релокация, будет приводить к лишней итерации загрузчика при каждом обращении к функции. Vic вроде бы про это говорил, но он упоминал адрес функции, а здесь нужно релоцировать адрес ячейки, в которой хранится адрес функции.
Mikl___ Мне кажется, дело не только в том, что программа будет короче. Вызов через переходники упрощают работу загрузчика + дают возможность лучше работать механизму COW, т.к. корректировать придется только страницы с IAT, т.е. гораздо меньшее количество страниц.
n0name Упрощается не алгоритмически, а количественно -- грубо говоря, вместо двадцати правок, надо сделать только одну.
В аттаче пример того как обойтись без IAT (MessageBox в 97 байт), мне могут возразить не универсально и работает только под конкретной сборкой, ну так и Windows не каждый день обновляют. А еще хотелось бы сослаться на работу классика Сверхбыстрый импорт API-функций Mika0x65 IMHO вы не правы -- и в том и другом случае загрузчик исправит только запись в секции импорта, а вот доступ к функции в случае с переходником будет медленнее 1) читаем адрес переходника 2) читаем адрес в импорте 3) переходим на этот адрес _imp__foo 1) читаем адрес в импорте 2) переходим на этот адрес
А, понял, я невнимательно прочитал. Вариант call [XXXXXXXX] быстрее при выполнении, но в этом случае вероятно будет нужна релокация (если это .dll).
Hello to everyone! I'm new to this forum. First, I'm Bulgarian and I understand almost everything written in Russian but It's hard to me to write in your language so I'll be using English. I use MASM32. Some time ago I asked myself the same question if I could remove the extra JMP when calling API functions with INVOKE or CALL. So I created these macroses: Код (Text): summon macro func_name:req, args:vararg local i, rev_params, imp_func_name rev_regs textequ <> i = 0 % for arg, <args> rev_regs catstr <arg> , <,> , rev_regs endm % for arg, <rev_regs> push arg i = i + 4 endm % imp_func_name catstr <_imp__> , <&func_name> , <@> , %i extern imp_func_name: ptr proc call imp_func_name endm partial macro i:req, func_name:req, args:vararg local rev_params, imp_func_name rev_regs textequ <> % for arg, <args> rev_regs catstr <arg> , <,> , rev_regs endm % for arg, <rev_regs> push arg endm % imp_func_name catstr <_imp__> , <&func_name> , <@> , %i*4 extern imp_func_name: ptr proc call imp_func_name endm Now instead of writing for example: invoke PostThreadMessageA, ThreadID,WM_PAUSE,eax,var1 You write: summon PostThreadMessageA, ThreadID,WM_PAUSE,eax,var1 If you need to push some parameters beforehand you can use the partial macro instead of summon: push var1 ... push eax ... partial 4, PostThreadMessageA, ThreadID,WM_PAUSE The first number indicates the number of parameters the function has. These macros has some advantages and some drawbacks compared to INVOKE: 1. You don't need to include and use kernel32.inc, user32.inc, etc. 2. You cannot use ADDR, you have to use offset instead. 2.1 Advantage: You can use forward references. 2.2. Disadvantage: If you refer to a local variable addr will automatically generate LEA EAX,... / PUSH EAX. You will have to write the LEA instruction manually with summon or partial. This can be good or bad depending on your preference or particular situation. Also you may have to use partial instead of summon and manually push some of the parameters into stack to get the same sequence of instructions as the one generated by invoke if addr is not used with the last parameter of invoke. 3. You will not get an error message during assembly/compiling if you've put a wrong name or number of parameters to an API function. You will get an error message during linkage instead. 4. Not all API functions can be called by the summon and partial macros but most of them can. Also you can always use call and invoke if/when needed. I hope this will be useful for some of you