можно ли избавиться от переходников call __foo; __foo: call [foo]

Тема в разделе "WASM.ASSEMBLER", создана пользователем SnowPawn, 24 фев 2009.

  1. SnowPawn

    SnowPawn New Member

    Публикаций:
    0
    Регистрация:
    24 фев 2009
    Сообщения:
    4
    Приветствую.
    Уважаемые,
    при генерации exe-файла в masm каждое обращение к API-функции превращается в вызове переходника.
    Например:
    call ExitProcess
    компилируется в
    call xxxxxxxx
    и где-то по адресу xxxxxxxx:
    call [ExitProcess]
    Причем это происходит независимо от того, как вызвать ExitProcess.
    Пробовал 3 варианта:
    - с описанием ExitProcess proto
    - с описанием extern ExitProcess
    - с явным указанием вызова call [ExitProcess]
    Объясните, пожалуйста, можно ли в masm напрямую вызывать импортируемые функции
    call [foo]
    И, если можно, то как.
    Спасибо
     
  2. max7C4

    max7C4 New Member

    Публикаций:
    0
    Регистрация:
    17 мар 2008
    Сообщения:
    1.203
    call GetProcAddress, hKernel32, sExitProcess
    mov ExitProcess, eax
    ...
    call [ExitProcess], 0
     
  3. SnowPawn

    SnowPawn New Member

    Публикаций:
    0
    Регистрация:
    24 фев 2009
    Сообщения:
    4
    to max7C4
    сорри, но для чего тогда придуманы таблицы импорта?

    я думаю, что этот переходник сидит в библиотеке импорта.
    В моем случае в kernel32.lib

    Тогда вопрос, как С++ обходит этот вопрос. Там функции импорта вызываются правильно.
     
  4. Vic

    Vic New Member

    Публикаций:
    0
    Регистрация:
    12 апр 2008
    Сообщения:
    75
    SnowPawn
    Если имеешь в виду зачем в конечном коде используются переходники типа call xxxxx, jmp [yyyy]. Вместо того чтобы просто вызвать код нужной функции, то смотри если в твоем коде неск раз вызывается одна и та же функция, то загрузчику придется при каждой встрече команды вызова такой функции копировать ее адрес в место вызова (адрес нужной функции), в то время как при использовании переходников такая необходимость отпадает.
     
  5. Vic

    Vic New Member

    Публикаций:
    0
    Регистрация:
    12 апр 2008
    Сообщения:
    75
     
  6. SnowPawn

    SnowPawn New Member

    Публикаций:
    0
    Регистрация:
    24 фев 2009
    Сообщения:
    4
    to Vic
    так мой вопрос в том и заключается, почему masm не поступает так же, как С++.
    зачем он использует первый call?
     
  7. Vic

    Vic New Member

    Публикаций:
    0
    Регистрация:
    12 апр 2008
    Сообщения:
    75
    Тут , могу конечно напутать , но ИМХО вопрос к разработчикам... потому что, например, при компиляции файлов .с, с помощью gcc, вызов тоже через переходники выполняется...
     
  8. SnowPawn

    SnowPawn New Member

    Публикаций:
    0
    Регистрация:
    24 фев 2009
    Сообщения:
    4
    спасибо, сам разобрался.
    сгенерировал asm-код простенькой С++ программы и понял.
    lib-библиотеки предоставляют нам по два вида каждой функции:
    foo и _imp__foo.
    используя первый - получим переходник.
    используя второй - прямой вызов call [foo].
     
  9. AsmGuru62

    AsmGuru62 Member

    Публикаций:
    0
    Регистрация:
    12 сен 2002
    Сообщения:
    689
    Адрес:
    Toronto
    Кто-то спрашивал такое не так давно. В общем, похоже, что это сделано компилятором для инкрементального линкера - чтоб быстрее линковать (в некоторых случаях добавки/удаления кода). Посмотри на код в релизе.
     
  10. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.787
    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 по вкусу :)
     
  11. Phantom_84

    Phantom_84 New Member

    Публикаций:
    0
    Регистрация:
    6 июн 2007
    Сообщения:
    820
    На самом деле соглашение по вызову WinAPI-функции не всегда совпадает с соглашением, которое используется или хотябы поддерживается в конкретном языке программирования. Поэтому в общем случае заглушки нужны. Про эффективность использования того или иного способа здесь было сказано все верно. Добавлю только то, что call [procentry] к примеру в DLL, где может потребоваться релокация, будет приводить к лишней итерации загрузчика при каждом обращении к функции. Vic вроде бы про это говорил, но он упоминал адрес функции, а здесь нужно релоцировать адрес ячейки, в которой хранится адрес функции.
     
  12. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Mikl___
    Мне кажется, дело не только в том, что программа будет короче. Вызов через переходники упрощают работу загрузчика + дают возможность лучше работать механизму COW, т.к. корректировать придется только страницы с IAT, т.е. гораздо меньшее количество страниц.
     
  13. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    Каким образом упрощается работа загрузчика?
     
  14. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    n0name
    Упрощается не алгоритмически, а количественно -- грубо говоря, вместо двадцати правок, надо сделать только одну.
     
  15. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.787
    В аттаче пример того как обойтись без IAT (MessageBox в 97 байт), мне могут возразить не универсально и работает только под конкретной сборкой, ну так и Windows не каждый день обновляют. А еще хотелось бы сослаться на работу классика Сверхбыстрый импорт API-функций
    Mika0x65
    IMHO вы не правы -- и в том и другом случае загрузчик исправит только запись в секции импорта, а вот доступ к функции в случае с переходником будет медленнее 1) читаем адрес переходника 2) читаем адрес в импорте 3) переходим на этот адрес
    _imp__foo 1) читаем адрес в импорте 2) переходим на этот адрес
     
  16. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    Mikl___ разъяснил все верно. будет та же одна правка в IAT.
     
  17. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    А, понял, я невнимательно прочитал. Вариант call [XXXXXXXX] быстрее при выполнении, но в этом случае вероятно будет нужна релокация (если это .dll).
     
  18. n0name

    n0name New Member

    Публикаций:
    0
    Регистрация:
    5 июн 2004
    Сообщения:
    4.336
    Адрес:
    Russia
    так же как и в другом случае (call xxx, xxx: jmp [real_api]).
     
  19. Viruslav

    Viruslav New Member

    Публикаций:
    0
    Регистрация:
    5 мар 2009
    Сообщения:
    1
    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):
    1. summon  macro   func_name:req, args:vararg
    2.  
    3.     local   i, rev_params, imp_func_name
    4.  
    5.     rev_regs textequ <>
    6.     i = 0
    7.  
    8. %   for arg, <args>
    9.         rev_regs    catstr  <arg> , <,> , rev_regs
    10.     endm
    11.  
    12. %   for arg, <rev_regs>
    13.         push    arg
    14.         i = i + 4
    15.     endm
    16.  
    17. %   imp_func_name   catstr  <_imp__> , <&func_name> , <@> , %i
    18.     extern  imp_func_name: ptr proc
    19.     call    imp_func_name
    20. endm
    21.  
    22.  
    23. partial macro   i:req, func_name:req, args:vararg
    24.  
    25.     local   rev_params, imp_func_name
    26.  
    27.     rev_regs textequ <>
    28.  
    29. %   for arg, <args>
    30.         rev_regs    catstr  <arg> , <,> , rev_regs
    31.     endm
    32.  
    33. %   for arg, <rev_regs>
    34.         push    arg
    35.     endm
    36.  
    37. %   imp_func_name   catstr  <_imp__> , <&func_name> , <@> , %i*4
    38.     extern  imp_func_name: ptr proc
    39.     call    imp_func_name
    40. 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 :)