Безопасный вызов функций WinAPI (сохранение значений регистров)

Тема в разделе "WASM.BEGINNERS", создана пользователем andy_tallinn, 11 мар 2007.

  1. andy_tallinn

    andy_tallinn Andy Tallinn

    Публикаций:
    0
    Регистрация:
    11 мар 2007
    Сообщения:
    13
    Адрес:
    World
    Здравствуйте.

    Как известно, функции «всеми любимого» WinAPI изменяют содержимое регистров.
    При этом меняется, увы, не только [EAX].

    Очень хочется узнать, какими способами уважаемые программисты данного сообщества
    решают вопрос «безопасного» вызова WinAPI-функций? «Безопасный» в данном контексте
    означает соблюдение условия, по которому регистры общего назначения (кроме [EAX])
    восстанавливают/сохраняют свое значение, существовавшее до вызова WinAPI-функции.

    Вариант с pushad/popad конечно прост и ясен, но чувствую, что не единственный.

    С уважением,
    Andy.
     
  2. W4FhLF

    W4FhLF New Member

    Публикаций:
    0
    Регистрация:
    3 дек 2006
    Сообщения:
    1.050
    А зачем их сохранять я что-то не пойму? Главное, чтобы функция стек в порядок приводила, а это обеспечивается соглашением stdcall, в остальном я не вижу смысла. Ну если уж так надо тебе сохранять регистры до вызова, то кроме pushad можешь класть их в заранее объявленные переменны. Что тут ещё придумаешь?
     
  3. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    andy_tallinn
    API документированно сохраняют esi, edi, ebx - если этого тебе мало - остальные кидай во временные переменные - только смысл сомнительный - "долгоиграющие" переменные лучше хранить в отведённых для них местах с именами, а регистры активно использовать в блоках кода не связанных с вызовом чужих функций, особенно таких тормозных как api.
     
  4. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    регистры используются обычно для временного хранения промежуточных значений, так что смысла в сохранении их я не вижу)

    алгоритм: загрузили регистр из памяти - поработали - сохранили в память, дальше вызов апи, дальше последовательность повторяется.

    ну можно, конечно, сделать PUSH / POP на те регистры, которые тебе "дороги" =)
     
  5. KiNDeR

    KiNDeR New Member

    Публикаций:
    0
    Регистрация:
    13 июн 2003
    Сообщения:
    258
    Адрес:
    Russia
    удалено... :)
     
  6. andy_tallinn

    andy_tallinn Andy Tallinn

    Публикаций:
    0
    Регистрация:
    11 мар 2007
    Сообщения:
    13
    Адрес:
    World
    W4FhLF, Y_Mur, Great, спасибо за комментарии!

    KiNDeR :)

    В своей практике я стараюсь использовать переменные (работа с памятью) по минимуму,
    заключая логику программы в небольшие функции, оперирующие регистрами.

    К тому же, не забываю сохранять значения регистров (кроме [EAX]) при входе в функцию
    и восстанавлить при выходе из нее. К сожалению, похоже, что M$ не разделяет моей
    точки зрения в аспекте написания логики программного кода.
     
  7. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    andy_tallinn
    А push/pop - это не работа с памятью? :)

    Вообще ЛОЛ :)))
     
  8. andy_tallinn

    andy_tallinn Andy Tallinn

    Публикаций:
    0
    Регистрация:
    11 мар 2007
    Сообщения:
    13
    Адрес:
    World
    Quantum

    Если нет, то стал бы я задавать вопрос? Вдумайтесь.

    Рад что поднял Вам настроение данной неприхотливой фразой :)
     
  9. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    andy_tallinn
    А Вам предлагаю подумать почему же на этот вопрос никто серьёзно не ответил. Возможно, вопрос изначально был лишён смысла?

    Коллеги тоже благодарны за позитифф! :) Ну, чуть выше прозвучала фраза посерьёзнее:

    При работе с чужим кодом (а АПИ - это тоже чужой код) неизбежно сталкиваешься с ограничениями, обычно именуемыми соглашениями о вызове (calling conventions). Стандартные соглашения (stdcall, cdecl, pascal, fastcall и т.д.) придуманы не дураками, как хочется верить, и лучше уж с ними основательно ознакомиться перед тем как начинать очередную дискуссию о том, что в МС не позаботились сохранить значение того или иного регистра. В последствии вопросы сами отпадают.
     
  10. nitrotoluol

    nitrotoluol New Member

    Публикаций:
    0
    Регистрация:
    5 сен 2006
    Сообщения:
    848
    pushad / popad

    Код (Text):
    1. pushad
    2. push 0
    3. push offset [str1]
    4. push offset [str2]
    5. push 0
    6. call MessageBoxA
    7. popad
     
  11. AsmGuru62

    AsmGuru62 Member

    Публикаций:
    0
    Регистрация:
    12 сен 2002
    Сообщения:
    689
    Адрес:
    Toronto
    Если посмотреть код сгенерированный компилятором (в RELEASE состоянии) - будет видно, что сохранения/восстановления регистров в таком коде нет. И это неспроста - не станет компилятор генерировать неоптимальный код. А вот интересный пример по теме:
    Код (Text):
    1. procedure1:
    2.     push    edx ; Saving registers on entry
    3.     push    ecx
    4.  
    5.     ; Using/Destroying ECX and EDX
    6.  
    7.     pop ecx ; Restoring registers on exit
    8.     pop edx
    9.     ret
    10.  
    11. procedure2:
    12.     ; Using/Destroying ECX and EDX
    13.     ret
    14.  
    15. ; --- CODE #1: Using protected function
    16.  
    17.     mov ecx, value1 ; Load values
    18.     mov edx, value2
    19.     call    procedure1  ; ECX and EDX are protected
    20.  
    21.     ; Can use ECX and EDX here...
    22.  
    23. ; --- CODE #2: Using un-protected function
    24.  
    25.     mov ecx, value1 ; Load values
    26.     mov edx, value2
    27.     call    procedure2  ; ECX and EDX are destroyed
    28.  
    29.     ; Cannot use ECX and EDX here... so we need to reload it
    30.  
    31.     mov ecx, value1 ; Load values again for some action
    32.     mov edx, value2
    Если сравнить два куска кода, то видно, что в коде #1 можно сразу использовать ECX и EDX, поскольку procedure1 сохраняет и восстанавливает эти регистры. В коде #2 есть две дополнительные команды MOV для перезагрузки ECX и EDX. Но зато в коде #1 есть четыре операции с памятью: две PUSH и две POP. Должно быть ясно, что код #2 (без сохранения регистров) быстрее кода #1.

    EBX, EDI и ESI необходимо сохранять только в теле CALLBACK функции. Не обязательно это делать в каждой функции написанной для Win32.

    Также необходимо держать флаг направления (DF) в нулевом состоянии - Win32 требует этого. Если была применена инструкция STD - перед выходом надо возвратить флаг в ноль используя CLD.
     
  12. andy_tallinn

    andy_tallinn Andy Tallinn

    Публикаций:
    0
    Регистрация:
    11 мар 2007
    Сообщения:
    13
    Адрес:
    World
    Quantum

    Увы, я не причислен к тем, кто всегда и везде видит отсутствие смысла.

    Спасибо. В моем мозге уже сохранены некоторые сведения о соглашениях,
    но постараюсь разобраться в этом вопросе более досконально :)
    В частности, меня интересует, почему M$ решила не заботиться
    именно о сохранении РОН [ECX], [EDX].


    nitrotoluol

    В принципе, Вы привели способ pushad/popad, о котором я упомянул в теме.


    AsmGuru62

    Благодарю за оказанное внимание. Код ясен, но увы,
    не всегда бывает проще загружать регистры из памяти:

    Код (Text):
    1. ; --- CODE #3: Using un-protected function with changed registry value
    2.  
    3.     mov ecx, value1 ; Load values
    4.     mov edx, value2
    5.     inc ecx     ; Changes...
    6.     dec edx
    7.     call    procedure2  ; ECX and EDX are destroyed
    8.  
    9.     ; Cannot use previous ECX and EDX here... so we need to reload it and to change again
    Резюме
    Прошу понять меня правильно. Я прекрасно понимаю, что заданный мной вопрос
    несколько странный, ведь в любом случае, работа функций WinAPI существено
    «тормозит» процесс выполнения программы, таким образом делая задержку
    при работе с памятью менее существенной. Цель вопроса — расширение
    кругозора за счет поиска новых путей решения вопроса.

    В частности, ранее я знал, что соглашения о вызове (stdcall, fastcall, cdecl, pascal)
    влияют на порядок помещения аргументов в стек, но никак не догадывался,
    что помимо этого они также регламентируют работу с регистрами.

    Особая благодарность: Quantum и AsmGuru62
     
  13. Sickle

    Sickle New Member

    Публикаций:
    0
    Регистрация:
    11 июл 2003
    Сообщения:
    181
    andy_tallinn
    уважаемый, Вы видите потерю производительности в том, что приходится сохранять регистры в стеке перед вызовом АПИ? и гневаетесь на M$ за то что она не потрудилась сделать это за Вас в своих функциях, ведь так?
    1. почитайте соглашение stdcall - надеюсь, просветление наступит
    2. а если даже M$ сделала бы сохранение значений регистров, то небыло бы это такой же потерей производительности, но только внутри функции? где логика?
     
  14. andy_tallinn

    andy_tallinn Andy Tallinn

    Публикаций:
    0
    Регистрация:
    11 мар 2007
    Сообщения:
    13
    Адрес:
    World
    Sickle

    Пожалуйста, перечитайте мой предыдущий комментарий.
     
  15. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    andy_tallinn
    Это не соглашения вызова, я сказал бы, что это соглашения API. Не воспринимай Мелкософт как АБСОЛЮТНЕ ЗЛО, в Линухе тоже есть соглашения API. А соглашения вызова, насколько я знаю, не оговаривают сохранение/восстановление регистров (кроме программного указателя и стека, разумеется). А так - для себя я даже имел наглость сочинять DLL-ки не только с cdecl-соглашением (это, конечно недопустимо для callback), но и с мерзким плюсплюсным манглом :)
    кстати о fastcall - его разные компиляторы трактуют по-разному, это следует иметь ввиду во избежание.
     
  16. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    Ustus
    Тут Вы ошибаетесь. Сохранение/несохранение регистров и флагов оговаривается во всех соглашениях вызова, включая *никсовый cdecl.

    andy_tallinn
    Нет других путей. Можно было бы заюзать соглашение fastcall для маленьких апишных функций, а для больших сделать cdecl (cdecl даёт выйгрыш в размере по сравнению со stdcall), но, как Вы сами заметили, это слишком несущественно и, очевидно, МС руководствовалось другими принципами при разработке АПИ. Единственное радикальное решение - это перейти на Линукс, где АПИ тормозит ещё больше :)
     
  17. Ustus

    Ustus New Member

    Публикаций:
    0
    Регистрация:
    8 авг 2005
    Сообщения:
    834
    Адрес:
    Харьков
    Quantum
    Возможно. По крайней мере по stdcall и cdecl, только что порывшись, вроде так и есть. Про pascal ничего не нашел, но было бы логично, чтобы регистровые требования совпадали с cdecl.

    Это инсинуация :) или чем-то обусловлено? (стыдно признаться, но *nix пока имел только потребительски)
     
  18. Cock

    Cock New Member

    Публикаций:
    0
    Регистрация:
    9 фев 2007
    Сообщения:
    148
    Позвольте спросить - почему логично? из каких соображений?

    andy_tallinn
    Вы похожи на красноглазого с лора.
    http://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BC%D0%BF%D1%8C%D1%8E%D1%82%D0%B5%D1%80%D0%BD%D1%8B%D0%B9_%D0%B6%D0%B0%D1%80%D0%B3%D0%BE%D0%BD
     
  19. Quantum

    Quantum Паладин дзена

    Публикаций:
    0
    Регистрация:
    6 янв 2003
    Сообщения:
    3.143
    Адрес:
    Ukraine
    Ustus
    В Линуксе роль АПИ играет сишная либа (LIBC), в которой оптимизацией даже не пахнет (сказывается убогость оптимизации в GCC, но и сам сишный код данной либы далёк от идеала рационализации).
     
  20. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Quantum
    Что вы имеете ввиду говоря о маленьких/больших апишных функциях? Каким образом их можно оценить по размеру?