Voilà encore une utilisation des objets légers (voirmes autres tutoriaux) : appeler du code ASM depuis VB…
Pour faire simple, nous allons nous servir des codes surles pointeurs de fonctions et plus précisément du code sur les fonctionsstdcall.
= = Règles de codage ==
Il faudra le compilé pour avoir le code machine desinstructions de la fonction. Je rappelle que pour des besoins d’alignement(ajout de données dans le code), on doit ajouter des NOP entre deuxinstructions. Une instruction n’est pas sécable, elle doit rester continue.
Comment obtenir les instructions binaires d’un code (ou Csans appels externes) ?
SousVisual C++ 6
D’abord, il nous faut un projet : menu File|New puis sélectionner Win32 Application, donner un nom dans le champ ProjectName puis valider par OK. Vérifier que An Empty Project estsélectionné. Puis cliquer sur Finish.
Ensuite, il faut se mettre en Release pour avoir du codesans ajout (surtout si on veut compiler du C) : menu Build|Set ActiveProject Configuration. ChoisisserWin32 Release.
Ensuite, il faut ajouter un fichier main.cpp : menu File|Newet choisisser C++ Source File et taper main.cpp dans le champ FileName.
Dans le fichier copier ce code :
//ne sert à rien
int WinMain(longarg1,long arg2,longarg3,long arg4)
{
_asm {
//votre code sera ici
}
return 0;
}
//à remplacer par votre prototypepour mémoire, si vous souhaitez
_declspec(naked)void _stdcallMaFct(void)
{
_asm {
//ou ici suivant vos préférences
}
}
<!--[if !supportEmptyParas * --> <!--[endif]-->
Enfin, il faut ajouter la génération d’un fichier contenantle code ASM et le code machine en plus de la source : menu Project|Settingspuis dans l’onglet C/C++, choisir Listing Files dans la listedéroulante Category, puis dans la liste Listing File Type,choisir Assembly, Machine Code, and Source. Valider par OK.
Taper votre code dans l’un des deux emplacements prévus àcet effet.
Compiler votre code avec le menu Build|Compile main.cpp.
Dans le répertoire Release de votre répertoire deprojet, vous devez avoir un fichier main.cod
Repérer le début de votre code, chaque ligne de code doitavoir la syntaxe suivante :
Offset_codecode_machine code_asm
Offset_code est l’offset du code machinedepuis le début de la fonction. C’est un entier hexadécimal à 5 chiffres.
Code_machine sont des octets (donc par groupede deux chiffres hexadécimaux) qui doivent être dans cette ordre en mémoire etqui représentent l’instruction ASM code_asm. Il peut y avoir 6 ou7 octets.
Code_ASM est votre code ASM en texte.
<!--[if !supportEmptyParas * --> <!--[endif]-->
SousVisual C++ .Net
<!--[if !supportEmptyParas * --> <!--[endif]-->
D’abord, il nous faut un projet : cliquer sur Nouveauprojet puis dérouler Projets C++, cliquer sur Win32et choisir Projet Win32 et donner lui un nom et valider. Dans Paramètresde l’application, cocher Projet vide et cliquer surTerminer.
Ensuite, il nous faut un fichier main.cpp. Menu Projet|Ajouterun nouvel élément. Cliquer Visual C++ et choisir FichierC++ (.cpp). Donner lui le nom main.cpp et cliquer sur Ouvrir.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Dans le fichier copier ce code :
//ne sert à rien
int WinMain(longarg1,long arg2,longarg3,long arg4)
{
_asm {
//votre code sera ici
}
<!--[if !supportEmptyParas * --> <!--[endif]-->
return 0;
}
<!--[if !supportEmptyParas * --> <!--[endif]-->
//à remplacer par votre prototypepour mémoire, si vous souhaitez
_declspec(naked)void _stdcallMaFct(void)
{
_asm {
//ou ici suivant vos préférences
}
}
<!--[if !supportEmptyParas * --> <!--[endif]-->
Ensuite, il faut se régler en Release. Menu Générer|Gestionnairede configurations…Dans Configuration de la solutionactive, choisir Release.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Enfin, il nous faut paramétrer la génération du listing.Menu Projet|Propriétés de …
Dérouler C/C++. Cliquer sur Fichiers desortie. Dans la liste Sortie de l’assembleur,choisir Assembleur, code machineet source (/FAcs) puis cliquer sur Appliquer puis OK.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Taper votre code dans l’un des deux emplacements prévus àcet effet.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Vérifier que vous êtes bien dans main.cpp et qu’il estenregistrer. Compiler votre code avec le menu Générer|Compiler.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Dans le répertoire Release de votre répertoire deprojet, vous devez avoir un fichier main.cod
Repérer le début de votre code, chaque ligne de code doitavoir la syntaxe suivante :
Offset_codecode_machine code_asm
<!--[if !supportEmptyParas * --> <!--[endif]-->
Offset_code est l’offset du code machine depuisle début de la fonction. C’est un entier hexadécimal à 5 chiffres.
Code_machine sont des octets (donc par groupede deux chiffres hexadécimaux) qui doivent être dans cette ordre en mémoire etqui représentent l’instruction ASM code_asm. Il peut y avoir 6 ou7 octets.
Code_ASM est votre code ASM en texte.
<!--[if !supportEmptyParas * --> <!--[endif]-->
SousDev-C++
<!--[if !supportEmptyParas * --> <!--[endif]-->
Oubliez parce que ce n’est pas de l’asm normal, c’est del’asm GNU…C’est un genre spécial…
<!--[if !supportEmptyParas * --> <!--[endif]-->
Et après, comment fais-je pour placer ce code dans mavariable ?
<!--[if !supportEmptyParas * --> <!--[endif]-->
« J’ai mon fichier main.cod . J’en faisquoi ? ».
Vous allez regrouper par groupe de 4 octets les codesmachines et ajouter autant de CC (ou 90) qu’il faut pour compléter le dernierpaquet.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Par exemple (exemple fictif) :
12 34 56 78 9A BC DE F5 10 00
<!--[if !supportEmptyParas * --> <!--[endif]-->
Par groupe de 4 :
12 34 56 78 ; 9A BC DE F5 ; 10 00 CC CC
<!--[if !supportEmptyParas * --> <!--[endif]-->
Maintenant passons au code lui-même :
Private TypeasmCode
Code(0 To <taille nécessaire - 1>) As Long
End Type
Private m_Code AsasmCode
Private m_stdfct AstypStdCallFunctionCallerStack
<!--[if !supportEmptyParas * --> <!--[endif]-->
Public FunctionInit() As <interface>
'on placele code
With m_CodeESP
.Code(0) = &HXXXXXXXX
‘….
.Code(n) = &H XXXXXXXX
End With
'ondemande un objet pour appeler le code ASM
Set Init = InitStdCallFunctionCallerStack(m_stdfct,VarPtr(m_Code.Code(0)))
End Function
<!--[if !supportEmptyParas * --> <!--[endif]-->
<taille nécessaire-1> est le nombre de groupede 4 octets.
&HXXXXXXXX sera remplacé par chaque groupe de 4octets en remplissant en ordre inverse (convention little-endian).
Exemple : 12 34 56 78 donnera &H78563412
<interface> sera une interface définie dans unetypelib qui dérive de IUnknown et qui a une seule méthode dont leprototype correspond à celui de la fonction ASM.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Il est important de noter les points suivants :
<!--[if !supportEmptyParas * --> <!--[endif]-->
Deux exemples :
<!--[if !supportEmptyParas * --> <!--[endif]-->
L’allocationde mémoire dans la pile : une façon simple de se passer de Redim (et pasforcement de Redim Preserve)
<!--[if !supportEmptyParas * --> <!--[endif]-->
Voilà un moyen d’optimisation des allocations dynamiques demémoire (autres que Redim Preserve). En effet, allouer de la mémoire dans lapile est nettement plus rapide pour créer un buffer que de faire un Redim ou unDim tableau qui alloue dans le tas. Le code suivant allouera dans le tas :Dim b(512) as Byte ‘ou Redim b(512)
<!--[if !supportEmptyParas * --> <!--[endif]-->
Je rappelle que la pile croît vers le bas. Donc pourallouer, il faut soustraire la taille demandée de ESP.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Le problème de l’allocation dans le pile est la pagination de la mémoire. Une page fait 4096 octets. Si l’on change de page ou que l’onalloue plus d’une page, il faut vérifier avec l’instruction TEST que la pageest en mémoire. Dans le cas contraire et en l’absence de TEST, on assiste à unplantage sans message de Windows.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Il faudra donc que l’on vérifie que chaque page del’allocation est bien présente. La fonction prendra un argument qui est lataille que l’on veut allouer. Il est impératif que la taille soit un multiplede 4, sinon la pile sera désalignée et plantage.
<!--[if !supportEmptyParas * --> <!--[endif]-->
Le code d’allocation sera le suivant :
MOV ECX,[ESP + 4 * //on charge lataille d'allocation
LEA EAX,[ESP + 8 * //on chargel'adresse de la fin de la zone allouée
Probe:
CMP ECX,0x1000 //a-t-on demandé plus d'une page (4096 octets) ?
JB FinProbe //il faut tester leurs existances en mémoire
SUB ECX,0x1000 //on descend d'une page
SUB EAX,0x1000 //on retire une page la taille demandée
<!--[if !supportEmptyParas * --> <!--[endif]-->
TEST [EAX * ,ECX //on teste : si la page n'est pas chargée, le
//gestionnaire de mémoire la charge
//si on ne faitpas ce test et qu'une page n'est
//pas chargée
//le code plantede façon transparente (!!!)
<!--[if !supportEmptyParas * --> <!--[endif]-->
JMP Probe //on recommence
FinProbe:
SUB EAX,ECX //on met le reste < une page
<!--[if !supportEmptyParas * --> <!--[endif]-->
TEST [EAX * ,ECX //on teste, si jamais on a changer de page
<!--[if !supportEmptyParas * --> <!--[endif]-->
POP ECX //on récupère l'adresse de retour
MOV ESP,EAX //on place l'adresse nouvelle de la pile dans ESP
PUSH ECX //on remet l'adresse de retour
<!--[if !supportEmptyParas * --> <!--[endif]-->
RET //on retourne à l'appelant
<!--[if !supportEmptyParas * --> <!--[endif]-->
Avec le code machine :
0003b 8b 4c 24 04 mov ecx, DWORD PTR [esp+4 *
0003f 8d 44 24 08 lea eax, DWORD PTR [esp+8 *
$Probe$8877:
00043 81 f9 00 10 00
00 cmp ecx, 4096 ; 00001000H
00049 72 0f jb SHORT $FinProbe$8878
0004b 81 e9 00 10 00
00 sub ecx, 4096 ; 00001000H
00051 2d 00 10 00 00 sub eax, 4096 ;00001000H
00056 85 08 test DWORD PTR [eax * , ecx
00058 eb e9 jmp SHORT $Probe$8877
$FinProbe$8878:
0005a 2b c1 sub eax, ecx
0005c 85 08 test DWORD PTR [eax * , ecx
0005e 59 pop ecx
0005f 8b e0 mov esp, eax
00061 51 push ecx
00062 c3 ret 0
<!--[if !supportEmptyParas * --> <!--[endif]-->
Attention certaines instructions sont en deux lignes.
<!--[if !supportEmptyParas * --> <!--[endif]-->
La désallocation est plus simple, il suffit d’ajouter lataille à désallouer à ESP. Le code sera le suivant :
POP ECX //on retirel'adresse de retour
POP EAX //on retire lenombre d'octets à désallouer
ADD ESP,EAX //on désallouel'espace dans la pile
PUSH ECX //on remetl'adresse de retour
RET
<!--[if !supportEmptyParas * --> <!--[endif]-->
Avec le code machine :
00063 59 pop ecx
00064 58 pop eax
00065 03 e0 add esp, eax
00067 51 push ecx
00068 c3 ret 0
<!--[if !supportEmptyParas * --> <!--[endif]-->
Enfin, voici un code permettant d’obtenir l’adresse de lapile (ESP) à tout instant. Cela peut servir à vérifier qu’un appel de fonctionpar objet léger laisse la pile dans son état d’origine.
MOV EAX,ESP //on copie ESP dans ECX
ADD EAX,4 //on ajoute 4pour ne pas tenir compte
//de l'adresse de retour
RET //on retourne à l'appelant
<!--[if !supportEmptyParas * --> <!--[endif]-->
Avec code machine :
00035 8b c4 mov eax, esp
00037 83 c0 04 add eax, 4
0003a c3 ret 0
<!--[if !supportEmptyParas * --> <!--[endif]-->
<!--[if !supportEmptyParas * --> <!--[endif]-->
Cela donnera donc le code suivant :
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private Type asmCode
Code(0 To 1) As Long
End Type
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private TypeasmCode2
Code(0 To9) As Long
End Type
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private m_CodeESP AsasmCode
Private m_stdfctESP As typStdCallFunctionCallerStack
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private m_CodeAlloc As asmCode2
Private m_stdfctAlloc As typStdCallFunctionCallerStack
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private m_CodeFree AsasmCode
Private m_stdfctFree As typStdCallFunctionCallerStack
<!--[if !supportEmptyParas * --> <!--[endif]-->
'crée un objet qui permet derécupérer ESP
Public FunctionInitIGetESPStack() As ICallLongVoid
'on placele code
With m_CodeESP
.Code(0) = &HC083C48B
.Code(1) = &HCCCCC304
End With
'ondemande un objet pour appeler le code ASM
Set InitIGetESPStack =InitStdCallFunctionCallerStack(m_stdfctESP, VarPtr(m_CodeESP.Code(0)))
End Function
<!--[if !supportEmptyParas * --> <!--[endif]-->
'crée un objet qui permetd'allouer de la mémoire dans la pile
Public FunctionInitIAllocStack() As ICallLong1Long
'on placele code
With m_CodeAlloc
.Code(0) = &H4244C8B
.Code(1) = &H824448D
.Code(2) = &H1000F981
.Code(3) = &HF720000
.Code(4) = &H1000E981
.Code(5) = &H2D0000
.Code(6) = &H85000010
.Code(7) = &H2BE9EB08
.Code(8) = &H590885C1
.Code(9) = &HC351E08B
EndWith
'on demande un objet pour appeler le code ASM
Set InitIAllocStack = InitStdCallFunctionCallerStack(m_stdfctAlloc,VarPtr(m_CodeAlloc.Code(0)))
End Function
<!--[if !supportEmptyParas * --> <!--[endif]-->
'crée un objet qui permet dedésallouer de la mémoire dans la pile
Public FunctionInitIFreeStack() As ICallLong1Long
'on placele code
With m_CodeFree
.Code(0) = &HE0035859
.Code(1) = &HCCCCC351
End With
'ondemande un objet pour appeler le code ASM
Set InitIFreeStack =InitStdCallFunctionCallerStack(m_stdfctFree, VarPtr(m_CodeFree.Code(0)))
End Function
<!--[if !supportEmptyParas * --> <!--[endif]-->
<!--[if !supportEmptyParas * --> <!--[endif]-->
L’appelde l’instruction CPUID
<!--[if !supportEmptyParas * --> <!--[endif]-->
L’instruction CPUID permet d’obtenir certaines infos sur leCPU de votre PC. Lorsque EAX vaut 0 avant CPUID, il renvoie une chaîneauthentifiant la marque du processeur. Lorsque EAX vaut 1, CPUID renvoie laversion du processeur.
<!--[if !supportEmptyParas * --> <!--[endif]-->
On peut stocker toutes ces infos dans une structure que l’onpassera en paramètre.
<!--[if !supportEmptyParas * --> <!--[endif]-->
On aura le code suivant :
/*
typedefstruct {
char VendorID[12 * ;
unsigned long ProcessorSignature;
unsigned long MiscInfo;
}CPUINFO,*LPCPUINFO;
//_declspec(naked) void _stdcall GetCpuInfo(LPCPUINFOlpInfo);
_asm {
push ebx //on doittoujours sauvegarder EBX
<!--[if !supportEmptyParas * --> <!--[endif]-->
xor eax,eax //CPUID 0
cpuid
<!--[if !supportEmptyParas * --> <!--[endif]-->
mov eax,[esp + 8 * //on récupèrel'adresse de la structure
mov [eax * ,ebx //on les 4 DWORDde l'ID vendeur
mov [eax + 4 * ,edx
mov [eax + 8 * ,ecx
<!--[if !supportEmptyParas * --> <!--[endif]-->
mov eax,1 //CPUID 1
cpuid
<!--[if !supportEmptyParas * --> <!--[endif]-->
mov ecx,[esp + 8 * //on récupèrel'adresse de la structure
mov [ecx + 12 * ,eax //on récupère les infos
mov [ecx + 16 * ,ebx
<!--[if !supportEmptyParas * --> <!--[endif]-->
pop ebx //on restaureEBX
<!--[if !supportEmptyParas * --> <!--[endif]-->
ret 4
}
On aura le fichier ODL suivant :
[
helpstring("Bibliothèquede CPUID"),
uuid(A1AA8AB4-7839-4A21-B374-74B4307EA1C2)
library libCPUID {
importlib("stdole2.tlb");
<!--[if !supportEmptyParas * --> <!--[endif]-->
typedef struct {
unsigned char VendorID[12 * ;
long ProcessorSignature;
long MiscInfo;
} CPUINFO;
[
helpstring("Interface pourCPUID"),
uuid(B359BF14-2A1A-4ABF-826F-4CF90C067D7F),odl
interfaceICpuID: stdole.IUnknown {
void GetCPUInfo([in,out * CPUINFO*lpInfo);
}
}
<!--[if !supportEmptyParas * --> <!--[endif]-->
<!--[if !supportEmptyParas * --> <!--[endif]-->
On aura le code VB suivant :
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private Type asmCode
Code(0 To 9) As Long
End Type
<!--[if !supportEmptyParas * --> <!--[endif]-->
Private m_Code As asmCode
Private m_stdfct As typStdCallFunctionCallerStack
<!--[if !supportEmptyParas * --> <!--[endif]-->
Public FunctionInitCPUID() As ICpuID
'on placele code
With m_Code
.Code(0) =&HFC03353
.Code(1) =&H24448BA2
.Code(2) =&H89188908
.Code(3) =&H48890450
.Code(4) =&H1B808
.Code(5) =&HA20F0000
.Code(6) =&H8244C8B
.Code(7) =&H890C4189
.Code(8) =&HC25B1059
.Code(9) =&HCCCC0004
End With
'on demande un objet pour appeler le code ASM
Set InitCPUID = InitStdCallFunctionCallerStack(m_stdfct,VarPtr(m_Code.Code(0)))
End Function