Windows - Få størrelsen på en C ++-funktion

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg læste dette spørgsmål, fordi jeg forsøger at finde størrelsen på en funktion i et C ++-program, det er antydet, at der kan være en måde, der er platformspecifik. Min målrettede platform er Windows


Den metode jeg har i øjeblikket i mit hoved er følgende:

1. Hent en peger til funktionen

2. Øg pointeren (&tælleren), indtil jeg når maskinkodens værdi for ret

3. Tælleren vil være størrelsen af ​​funktionen?


Edit1: For at præcisere, hvad jeg mener med 'størrelse', mener jeg antallet af byte (maskinkode), der udgør funktionen.

Edit2: Der har været et par kommentarer, der spørger hvorfor eller hvad har jeg til hensigt at gøre med dette. Det ærlige svar er, at jeg ikke har nogen mening, og jeg kan ikke se fordelene ved at kende en funktion længde før kompileringstid. (Selvom jeg er sikker på, at der er nogle)


Dette virker som en gyldig metode for mig, vil det fungere?

Bedste reference


Nej, det virker ikke:



  1. Der er ingen garanti for, at din funktion kun indeholder en enkelt ret instruktion.

  2. Selvom det kun indeholder en enkelt ret, kan du ikke bare se på de enkelte byte - fordi den tilsvarende værdi kan vises som en værdi snarere end en instruktion.



Det første problem kan muligvis blive arbejdet rundt, hvis du begrænser din kodestil til f.eks. Kun et enkelt afkast i din funktion, men den anden kræver i princippet en demonterer, så du kan fortælle de enkelte instruktioner fra hinanden.

Andre referencer 1


Det er muligt at få alle blokke af en funktion, men det er et unaturligt spørgsmål at spørge, hvad der er 'størrelse' af en funktion. Optimeret kode vil omarrangere kodeblokke i rækkefølge af eksekvering og flytte sjældent brugte blokke (undtagelsesveje) til yderste dele af modulet. For flere detaljer, se Profilstyrede optimeringer, for eksempel hvordan Visual C ++ opnår dette i forbindelse med koblingstidskode generation. Så en funktion kan starte ved adresse 0x00001000, gren på 0x00001100 til et spring på 0x20001000 og en ret, og har en vis undtagelseshåndteringskode 0x20001000. Ved 0x00001110 starter en anden funktion. Hvad er 'størrelse' af din funktion? Den spænder fra 0x00001000 til + 0x20001000, men det 'ejer' kun få blokke i det span. Så dit spørgsmål skal være unasked. [28]


Der er andre gyldige spørgsmål i denne sammenhæng, som det samlede antal instruktioner en funktion har (kan bestemmes ud fra programmets symboldatabase og fra billedet), og endnu vigtigere, hvad er antallet af instruktioner i den hyppigt udførte kodevej inde funktionen. Alt dette er spørgsmål, der normalt stilles i forbindelse med præstationsmåling, og der er værktøjer, som instrumentkoden og kan give meget detaljerede svar.


Chasing pointers i hukommelsen og søger efter ret vil få dig hvor som helst jeg er bange. Moderne kode er langt mere komplekse end det.

Andre referencer 2


Dette vandt 't arbejde ... hvad hvis der er et spring, en dummy ret, og så målet for springet? Din kode vil blive narret.


Generelt er det umuligt at gøre dette med 100\% nøjagtighed, fordi du skal forudsige alle kodeveje, hvilket er som at løse stoppeproblemet. Du kan få 'temmelig god' nøjagtighed, hvis du implementerer din egen disassembler, men ingen løsning vil være næsten lige så let som du forestiller dig. [29]


Et 'trick' ville være at finde ud af, hvilken funktionskode der er efter den funktion, du leder efter, hvilket ville give ret gode resultater under forudsætning af visse (farlige) antagelser. Men så skal du vide, hvilken funktion der kommer efter din funktion, som efter optimeringer er ret svært at regne ud.





Rediger 1:


Hvad hvis funktionen ikke engang slutter med en ret instruktion overhovedet ? Det kunne meget godt bare jmp tilbage til sin opkalder (selvom det ikke er sandsynligt).





Rediger 2:


Glem ikke, at x86 i det mindste har instruktioner med variabel længde ...





Opdatering:



For dem der siger, at flowanalysen ikke er den samme som løsningen af ​​stopproblemet:


Overvej hvad der sker, når du har kode som:


foo:
    ....
    jmp foo


Du vil nødt til at følge springet hver gang for at finde ud af slutningen af ​​funktionen, og du kan ikke ignorere den forbi den første gang, fordi du ikke ved om eller ej du 'beskæftiger sig med selvmodificerende kode. (Du kan have inline-samling i din C ++-kode, som f.eks. Ændrer sig.) Det kan meget vel udvide til et andet hukommelsessted, så din analysator vil (eller skulle) ende i en uendelig sløjfe, medmindre du tolererer falske negativer.


Er det ikke som stoppet problem?

Andre referencer 3


Wow, jeg bruger funktionstørrelse tæller hele tiden, og den har mange og mange anvendelser. Er det pålideligt? Ingen måde. Er det standard c ++? Ingen måde. Men det er derfor, du skal tjekke det i disassembleren for at sikre det fungerede, hver gang du slipper en ny version. Compiler-flag kan ødelægge ordren.


static void funcIwantToCount()
{
   // do stuff
}
static void funcToDelimitMyOtherFunc()
{
   \_\_asm \_emit 0xCC
   \_\_asm \_emit 0xCC
   \_\_asm \_emit 0xCC
   \_\_asm \_emit 0xCC
}

int getlength( void *funcaddress )
{
   int length = 0;
   for(length = 0; *((UINT32 *)(&((unsigned char *)funcaddress)[length])) != 0xCCCCCCCC; ++length);
   return length;
}


Det ser ud til at fungere bedre med statiske funktioner. Globale optimeringer kan dræbe det.


P. S. Jeg hader folk, spørger hvorfor du vil gøre det, og det er umuligt osv. Stop med at stille disse spørgsmål, tak. Lader dig dumme. Programmererne bliver ofte bedt om at gøre ikke-standard ting, fordi nye produkter næsten altid trykker grænserne af hvad der er til rådighed. Hvis de ikke gør det, er dit produkt sandsynligvis et rehash af hvad der allerede er gjort. Kedelig!!!

Andre referencer 4


Den rigtige løsning på dette er at grave i din kompilators dokumentation. Den ARM compiler, vi bruger, kan laves til at producere en samlingstump (code.dis), hvorfra det er ret trivielt at trække forskydningerne mellem en given manglede funktion etiket og den næste manglede funktionsetiket.


Jeg er ikke sikker på, hvilke værktøjer du skal bruge til dette med et Windows-mål. Det ser ud til, at værktøjerne i svaret på dette spørgsmål måske er det, du søger.


Bemærk også, at jeg (arbejder i det indlejrede rum) antog, at du talte om post-compile-analyse. Det kan stadig være muligt at undersøge disse mellemliggende filer programmatisk som en del af en bygning, forudsat at:



  • Målfunktionen er i en anden genstand

  • Byggesystemet er blevet undervist afhængighederne

  • Du ved helt sikkert, at kompilatoren vil bygge disse objektfiler



Bemærk, at jeg ikke er helt sikker på, hvorfor du vil vide disse oplysninger. Jeg har tidligere brug for det for at være sikker på, at jeg kan passe et bestemt stykke kode på et helt bestemt sted i hukommelsen. Jeg må indrømme, at jeg er nysgerrig over, hvad det ville have for et mere generelt desktop-OS-mål.

Andre referencer 5


Denne kan arbejde i meget begrænsede scenarier. Jeg bruger det i en del af en kodeinjektionsværktøj, jeg skrev. Jeg kan ikke huske hvor jeg fandt oplysningerne, men jeg har følgende (C ++ i VS2005):


#pragma runtime\_checks("", off)

static DWORD WINAPI InjectionProc(LPVOID lpvParameter)
{
    // do something
    return 0;
}

static DWORD WINAPI InjectionProcEnd()
{
    return 0;
}

#pragma runtime\_checks("", on)


Og så har jeg i en anden funktion:


size\_t cbInjectionProc = (size\_t)InjectionProcEnd - (size\_t)InjectionProc;


Du skal slukke nogle optimeringer og erklære funktionerne som statiske for at få dette til at fungere; Jeg husker ikke specifikationerne. Jeg ved ikke, om dette er et nøjagtigt byteantal, men det er tæt nok. Størrelsen er kun den umiddelbare funktion; det omfatter ikke andre funktioner, der kan kaldes af den funktion. Bortset fra ekstreme kantlister som dette er 'størrelsen af ​​en funktion' meningsløs og ubrugelig.

Andre referencer 6


I C ++ er der ingen idé om funktionsstørrelse. Ud over alt andet nævnt, præprocessor makroer også gøre for en ubestemt størrelse. Hvis du vil tælle antallet af instruktionsord, kan du ikke gøre det i C ++, fordi det ikke eksisterer, før det er kompileret.

Andre referencer 7


Hvad mener du 'størrelse af en funktion'?


Hvis du mener en funktionspeger, end det er altid kun 4 bytes for 32bits systemer.


Hvis du mener størrelsen på koden, end du bare skal demontere genereret kode og finde indgangspunktet og det nærmeste ret opkald. En måde at gøre det på er at læse instruktionspegeregisteret i begyndelsen og i slutningen af ​​din funktion.


Hvis du vil finde ud af antallet af instruktioner, der kaldes i det gennemsnitlige tilfælde for din funktion, kan du bruge profilere og opdele antallet af pensionerede instruktioner på antallet af opkald.

Andre referencer 8


Jeg tror, ​​at det vil fungere på Windows-programmer lavet med Msvc, som for filialer synes 'ret' altid at komme til slutningen (selvom der er grene, der vender tilbage tidligt gør det en jne til at slutte).
Men du skal bruge en slags disassembler bibliotek til at finde den nuværende opcode længde, da de er variabel længde for x86. Hvis du ikke gør dette, vil du løbe ind i falske positiver.


Jeg ville ikke blive overrasket, hvis der er tilfælde, der ikke fanger.

Andre referencer 9


Der er ingen faciliteter i Standard C ++ for at opnå størrelsen eller længden af ​​en funktion.

Se mit svar her: Er det muligt at indlæse en funktion i nogle tildelte hukommelser og køre den derfra?


Generelt ved at kende størrelsen af ​​en funktion bruges i indlejrede systemer, når kopiering af eksekverbar kode fra en skrivebeskyttet kilde (eller en langsom hukommelsesenhed, såsom en seriel flash), til RAM. Desktop og andre operativsystemer indlæser funktioner i hukommelsen ved hjælp af andre teknikker, såsom dynamiske eller delte biblioteker.

Andre referencer 10


Indstil bare PAGE\_EXECUTE\_READWRITE på adressen, hvor du fik din funktion. Læs derefter hver byte. Når du får byte '0xCC' betyder det, at funktionens afslutning er actual\_reading\_address - 1.

Andre referencer 11


Brug af GCC, ikke så hårdt overhovedet.


void do\_something(void) { 
   printf("\%s!", "Hello your name is Cemetech"); 
   do\_something\_END: 
} 

... 

   printf("size of function do\_something: \%i", (int)(&&do\_something\_END - (int)do\_something));

Andre referencer 12


under kode får den nøjagtige funktionsblokstørrelse, det virker fint med min test
runtime\_checks deaktiver \_RTC\_CheckEsp i fejlsøgningstilstand


    #pragma runtime\_checks("", off)
DWORD \_\_stdcall loadDll(char* pDllFullPath)
{  
    OutputDebugStringA(pDllFullPath);
    //OutputDebugStringA("loadDll...................
");
    return 0;
    //return test(pDllFullPath);
}
#pragma runtime\_checks("", restore)

DWORD \_\_stdcall getFuncSize\_loadDll()
{
    DWORD maxSize=(PBYTE)getFuncSize\_loadDll-(PBYTE)loadDll;
    PBYTE pTail=(PBYTE)getFuncSize\_loadDll-1;
    while(*pTail != 0xC2 && *pTail != 0xC3) --pTail;
    if (*pTail==0xC2)
    {   //0xC3          : ret
        //0xC2 04 00    : ret 4
        pTail +=3;
    }

    return pTail-(PBYTE)loadDll;
};

Andre referencer 13


Jeg bogfører dette for at sige to ting:


1) De fleste af de svar, der gives her er virkelig dårlige og vil bryde let . Hvis du bruger C-funktionspegeren (ved hjælp af funktionsnavnet) i en debug bygning af din eksekverbare og muligvis under andre omstændigheder, kan det pege på en JMP shim det vil ikke have funktionen kroppen selv. Her er et eksempel. Hvis jeg gør følgende for den funktion, jeg definerede nedenfor:


FARPROC pfn = (FARPROC)some\_function\_with\_possibility\_to\_get\_its\_size\_at\_runtime;


den pfn jeg får (for eksempel: 0x7FF724241893) vil pege på dette, som blot er en JMP instruktion:


Indtast billedbeskrivelse her [32]


Derudover kan en compiler nest flere af disse shims eller forgrene din funktionskode, så den vil have flere epilogs eller ret instruktioner. Heck, det kan ikke engang bruge en ret instruktion. Derefter er der ingen garanti for, at funktionerne selv vil blive udarbejdet og knyttet i den rækkefølge, du definerer dem i kildekoden.


Du kan gøre alle de ting i samling sprog, men ikke i C eller C ++.


2) Så det var de dårlige nyheder. Den gode nyhed er, at svaret på det oprindelige spørgsmål er ja, der er så (eller en hack ) for at få den nøjagtige funktionsstørrelse, men den følger med følgende begrænsninger:



  • Det fungerer kun i 64-bit eksekverbare filer på Windows.

  • Det er naturligvis Microsoft specifikt og kan ikke bæres.

  • Du skal gøre dette på run-time.



Konceptet er enkelt - udnytte SEHs implementering i x64 Windows-binære filer. Kompilator tilføjer detaljer om hver funktion i PE32 + -overskriften (i IMAGE\_DIRECTORY\_ENTRY\_EXCEPTION -kataloget over den valgfrie overskrift, som du kan bruge til at opnå den nøjagtige funktionsstørrelse. (Hvis du undrer dig over, er disse oplysninger brugt til at fange, håndtere og afvikling af undtagelser i blokkerne \_\_try/\_\_except/\_\_finally. [33]


Her er et hurtigt eksempel:


//You will have to call this when your app initializes and then
//cache the size somewhere in the global variable because it will not
//change after the executable image is built.

size\_t fn\_size; //Will receive function size in bytes, or 0 if error
some\_function\_with\_possibility\_to\_get\_its\_size\_at\_runtime(&fn\_size);


og så:


#include <Windows.h>

//The function itself has to be defined for two types of a call:
// 1) when you call it just to get its size, and
// 2) for its normal operation
bool some\_function\_with\_possibility\_to\_get\_its\_size\_at\_runtime(size\_t* p\_getSizeOnly = NULL)
{
    //This input parameter will define what we want to do:
    if(!p\_getSizeOnly)
    {
        //Do this function's normal work
        //...

        return true;
    }
    else
    {
        //Get this function size
        //INFO: Works only in 64-bit builds on Windows!
        size\_t nFnSz = 0;

        //One of the reasons why we have to do this at run-time is
        //so that we can get the address of a byte inside 
        //the function body... we'll get it as this thread context:
        CONTEXT context = {0};
        RtlCaptureContext(&context);

        DWORD64 ImgBase = 0;
        RUNTIME\_FUNCTION* pRTFn = RtlLookupFunctionEntry(context.Rip, &ImgBase, NULL);
        if(pRTFn)
        {
            nFnSz = pRTFn->EndAddress - pRTFn->BeginAddress;
        }

        *p\_getSizeOnly = nFnSz;
        return false;
    }
}