stdcall и ... в Си

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by slesh, 26 Sep 2010.

  1. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    Столкнулся с одной довольно интересной задачей. А именно: передача неопределенного кол-ва аргументов для stdcall функции.

    т.е. вот какая сетуация:
    1) если есть тип
    typedef UINT (__stdcall * PROC_CALL)(void* Param1);
    то всё нормально вызывается, но тока 1 параметр можно передать.
    2) если есть тип
    typedef UINT (__stdcall * PROC_CALL)(void* Param1, ...);
    то странным образом вызов данной функции ведет себя как __cdecl, т.е. после вызова функции, вызывающая сторона очищает стек, при этом она это делает всегда, несмотря даже на наличие __stdcall в прототипе.
    Толи это бага MS VC 2008, толи фишка языка.

    А юзать разный прототип для выполнения функций с разным кол-вом параметров, как-то неохото. Потому что может быть параметров от 1 до 8

    Кто что предложит?
    Единственно что в голову пришло, это юзать промежуточную функцию, которая через ASM будет генерить нормальный __stdcall вызов, но както не оч хочется такое юзать
     
    #1 slesh, 26 Sep 2010
    Last edited: 26 Sep 2010
  2. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    stdcall не может быть в функциях неопределенного количества аргументов, т.к. стек отчищает сама вызываемая функция, сто пудов это не баг, MSVC сама меняет эту дерективу, хотя странно...лучще бы ошибку выдавала, т.к. деректива stdcall и ... в парметрах как антонимы. Да и "функцию-обертку" сделать не выйдет, да даже если и выйдет(что то сверхестественное), то какой от нее профит?
     
  3. alexey-m

    alexey-m Elder - Старейшина

    Joined:
    15 Jul 2009
    Messages:
    518
    Likes Received:
    100
    Reputations:
    37
    stdcall игнорируется при объявлении функции с переменным числом параметров, так как этот формат вызова жестко указывает размер стека, и для таких функций он неприменим.
     
  4. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    2 Jingo Bo профит есть. Допустим у меня есть адреса десяти функций в массиве (не важно каких), все они объявлены как __stdcall и имеют разное кол-во параметров.
    И мне из програмки в зависимости от ситуации надо вызывать эти функции. Хранить прототипы для каждой функции - не рационально, потому что на перед не знаешь какие функции будут юзайться.
     
  5. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Портотип можно объявить как :
    typedef UINT (__stdcall * PROC_CALL)(uint8 narg);

    в ручную вызывать функцию, то есть через asm до вызова кидать в стек аргументы, и в аргумент функции количество параметров, потом call, а в функции в начале вытаскивать из стека все аргументы так же через asm, думаю это единственный вариант.
     
  6. sn0w

    sn0w Статус пользователя:

    Joined:
    26 Jul 2005
    Messages:
    1,021
    Likes Received:
    1,200
    Reputations:
    327
    забавно, недавно с похожей задачей разбирался. если функция накед, неважно определен ли стдколл явно или нет, то компилер генерит цдекл код, поскольку только вызывающая процедура может определить кол-во аргументов в стеке.
     
  7. fluffylion

    fluffylion Member

    Joined:
    22 Feb 2010
    Messages:
    55
    Likes Received:
    10
    Reputations:
    0
    Можно объявить функцию например так -
    typedef UINT (__stdcall * PROC_CALL)(void* Param1, void* Param2, void* Param3, void* Param4, void* Param5, void* Param6, void* Param7, void* Param8);
    при вызове передавать в функцию требуемые параметры, в качестве остальных 0

    Либо еще вариант собрать параметры в структуру и передавать ее в функцию. Например:
    Code:
    struct stParams
    {
       void* Param1;
       void* Param2;
       ...
       void Param8;
    };
    
    typedef UINT (__stdcall * PROC_CALL)(stParams* pParams);
    
    Правда не знаю на сколько это удобно в данном случае.
     
  8. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    2 Jingo Bo всё делается не для усложнения а для облегчения. потому что если уже вообще захочется универсальности то достаточно сразу писать
    Code:
    push ...
    push ...
    push ...
    push ...
    call [адрес переменной хранящей адрес функции]
    
    Былобы оч хорошо еслибы вообще замутить в виде макроса такое
     
  9. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Да это понятно.

    Code:
    push ...
    push ...
    push ...
    push ...
    call [адрес переменной хранящей адрес функции]
    Ну собственно да, но ток это не очень хорошо, т.к если вылезет где нить эксцепшн, то в стеке может что то остаться(по мере взятия аргументов), но это все от реализации уже зависит.
    Как нех. stdarg.h через va_list
     
  10. sn0w

    sn0w Статус пользователя:

    Joined:
    26 Jul 2005
    Messages:
    1,021
    Likes Received:
    1,200
    Reputations:
    327
    вот еще такая тема

    Code:
    //////////////////////////////////////////////////////////////////////////
    __declspec(naked) NTSTATUS __cdecl ZwService(ULONG n, ...)
    {
        __asm
        {
            mov eax, [esp+4]
            lea edx, [esp+8]
            int 0x2e
            ret
        }
    }
    
    //////////////////////////////////////////////////////////////////////////
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {    
        DWORD startaddr, retlen;
        char aaa[128];
    
        ZwService(0xEC, /* win7 x86 ZwQueryInformationThread syscall # */
            GetCurrentThread(), 
            ThreadQuerySetWin32StartAddress, 
            (PVOID)&startaddr, 
            4, 
            &retlen);
        
        wsprintf(aaa, "%08X", startaddr);
        MessageBoxA(0,aaa,0,0);
    
        ExitProcess(0);
        
    }
     
  11. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    2 sn0w ну так это тока системные вызовы так вызывать и то, на Win 7 этот код вроде как уже не будет пахать потому что из юзаермода наглухо убили int2e (хотя могу и ошибаться) Конечно можно просто подсосывать sysenter а не int2e
    А если допустим мне надо вызнать какуюнить функцию у которой большинство обраотки идет в юзермоде, то тут такой код будет бесполезным
     
  12. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Ну, я предложил, только лишь в таком случае через va_list макросы, по другому только в ручную.
     
  13. greki_hoy

    greki_hoy Member

    Joined:
    4 Mar 2010
    Messages:
    326
    Likes Received:
    57
    Reputations:
    41
    2slesh
    попробуй на первый взгляд работает
    Code:
    #include <stdio.h>
    #include <string.h>
    #include <windows.h>
    
    
    DWORD TlsApiCall;
    
    __declspec(naked)
    int __cdecl coll(void*a,...){
    	__asm{
    		push dword ptr[TlsApiCall];
    		call dword ptr[TlsGetValue];
    		mov dword ptr[eax],esp;
    		push dword ptr[esp];
    		pop dword ptr[eax+4];
    		lea esp, [esp+8];
    		call dword ptr[esp-4];
    		push eax;
    		push dword ptr[TlsApiCall];
    		call dword ptr[TlsGetValue];
    		pop dword ptr[eax+8];
    		mov esp, dword ptr[eax];
    		push dword ptr[eax+4];
    		pop dword ptr[esp];
    		mov eax, dword ptr[eax+8];
    		ret;
    	}
    }
    
    
    int main(){
          char dup[12];
          // гденить начале
          TlsApiCall = TlsAlloc();
          // для каждого потока 12 байт
          TlsSetValue(TlsApiCall, &dup);
          // имеем требуемое поведение 
          coll(addr, bla, bla, bla, ...);
    }
    
    да и просто
    typedef UINT (__stdcall * PROC_CALL)(void* Param1, ...);
    не так а так
    typedef UINT (__stdcall * PROC_CALL)();
    сгенерирует variadic __stdcall вызов и стек чистить не будет
    это для Си а в Си++ так не будет работать в нем можно например
    что то типа coll юзать или на шаблонах можно сделать
    переходник
    это все применительно к MSVC/C++
     
    #13 greki_hoy, 5 Jan 2011
    Last edited: 5 Jan 2011
    1 person likes this.