Hallo,
hier bin ich wieder. Ich arbeite gerade an einer eigenen Implementation von C-Runtime Funktionen und wollte mal meine memcpy Funktion sharen.
Großzügig kommentiert und erklärt:
__declspec(naked) void _stdcall copyMemory(void *dest, const void *src, int length) { // naked funktion ohne prolog/epilog. Wir bearbeiten den stack selbst. // parameter werden auf den stack von rechts nach links übergeben // dest, src und length befinden sich bereits auf dem stack // visualisierung: /* | dest | esp - 12 | [esp + 4] | 19ff60 | | src | esp - 8 | [esp + 8] | 19ff64 | | length | esp - 4 | [esp + 12] | 19ff68 | | return address | esp | [esp] | 19ff6C | <- rücksprungadresse */ // esp zeigt immer auf das oberste element im stack // pusht man etwas auf den stack so verkleinert sich der zeiger von ESP, beim pop einfach vice versa. // zeigt zum beispiel ESP auf 19ff64 und ich pushe eine DWORD auf den stack, dann subtrahiert man sizeof(DWORD) vom esp pointer und der neue ESP pointer würde dann auf 19ff60 zeigen (das gepushte objekt). // um auf die entsprechenden parameter im stack zugreifen zu können muss man also logischerweise von der aktuellen position aus einfach X + ( N * 4 ), wobei X unser ESP darstellt und N dem index entspricht. // man stelle sich vor, jeder parameter ist im 4 byte alignment für x86 prozessoren vorhanden (DWORD). Das trifft jedoch nicht immer zu. // möchten wir also den wert von length in EAX haben, machen wir einfach mov eax, dword ptr [esp + 12]. Möchte man jedoch nur die Adresse von [esp + 12] bzw length, dann einfach mit der // LEA instruction: LEA eax, [esp + 12] // die stackverarbeitung ist so einfach, da alles was auf dem stack liegt relativ zur aktuellen ESP adresse ist. _asm { // mit der aufrufkonvention _stdcall sagen wir dem compiler das wir den stack als callee (der aufgerufene) selbst handlen, deswegen ist die sicherung von edi, esi und ecx notwending. // legen wir die werte auf den stack push edi push esi push ecx // stack verändert sich wie folgt: /* | ecx | esp - 20 | [esp + 4] | esi | esp - 18 | [esp + 8] | edi | esp - 16 | [esp + 12] | dest | esp - 12 | [esp + 16] | src | esp - 8 | [esp + 20] | length | esp - 4 | [esp + 24] | return address | esp | [esp] */ // length befindet sich jetzt bei [esp + 24], da 3 weitere register auf den stack gelegt wurden und der zeiger von ESP sich noch einmal um 12 verkleinert hat mov edi, dword ptr [esp + 16] // kopiere dest in den EDI register [esp + 16], siehe oben mov esi, dword ptr [esp + 20] // kopiere src in den ESI register [esp + 20], siehe oben mov ecx, dword ptr [esp + 24] // kopiere length in den ECX register [esp + 24], siehe oben rep movsb //rep movs byte ptr [edi], byte ptr [ESI] // hole die gespeicherten register einzeln vom stack in die register zurück // bei jedem pop wird vergrößert sich ESP mit 4 pop ecx pop esi pop edi // stack verändert sich wieder wie folgt: /* | dest | esp - 12 | [esp + 4] | src | esp - 8 | [esp + 8] | length | esp - 4 | [esp + 12] | return address | esp | [esp] */ // ret 12 cleaned alle ursprünglichen parameter vom stack und stellt die eigentliche return address wieder her. anschließend wird zurück gesprungen. // zurück bleibt nur noch ESP mit der rücksprungadresse. notwendig mit _stdcall. ret 12 } }
Usage genau wie eine normale memcpy Funktion.
anders als das könnte man auch mit vorgefertigtem prolog/epilog arbeiten und auf der basis vom ebp register die ganze aufgabe erledigen.
Greetz