A:link { TEXT-DECORATION: underline } A:visited { TEXT-DECORATION: underline } A:active { COLOR: red; TEXT-DECORATION: underline } A:hover { COLOR: #8080ff; TEXT-DECORATION: underline }
Extending Visual Basic 5.0 |
||
Data |
by Spider |
|
08/2001 |
UIC's Home Page |
Published by Quequero |
La m$ non documenta alcune interessanti cosette... |
Qualche mio eventuale commento sul tutorial :))) |
...secondo voi ci credono così lamah da non capire a cosa servono??? |
Secondo me i lamah sono loro, che, come vedremo,... |
|
...non sono nemmeno capaci di fare un po' di ottimizzazione! |
Difficoltà |
( )NewBies ( )Intermedio (X)Avanzato (X)Master |
Il Visual Basic ha molti limiti. Non si possono
usare le callback, non si può includere codice assembly... A noi i limiti non
piacciono giusto? Quindi vedremo come abbattere questi problemi prendendo un po'
per il culo il VB.
Introduzione |
Tools usati |
Essay |
Funzioni callback
Per cominciare vedremo come utilizzare le
funzioni callback in Visual Basic.
Innanzitutto, che cos'è una funzione callback? Una funzione
callback è una funzione che viene richiamata da un'altra
funzione che dispone del suo indirizzo in memoria. Ad esempio,
quando subclassiamo una finestra passiamo alla funzione
SetWindowLong l'indirizzo della nostra Window Procedure, che
abbiamo ottenuto con l'operatore AddressOf. Windows potrà così
richiamare la nostra funzione ad ogni messaggio. In VB,
normalmente, è questo tutto ciò che possiamo fare con le
funzioni callback. Supponiamo però di voler richiamare una
funzione callback da VB. Come fare? Hehehe! Per tanto tempo anch'io
me lo sono chiesto. La risposta l'ho trovata riflettendo sul
funzionamento del subclassing: quando richiamiamo la
SetWindowLong, essa ci restituisce l'indirizzo della vecchia
Window Procedure. Nella nostra Window Procedure, mettiamo una
chiamata alla vecchia Window Procedure utilizzando la funzione
API CallWindowProcA. Questa funzione non fa altro che richiamare
la Window Procedure che c'era prima che noi installassimo il
subclassing. Se la CallWindowProcA può fare ciò con l'indirizzo
di una Window Procedure, perchè non dovrebbe poterlo fare con
una procedura di altro genere? Allora mi sono messo a fare alcuni
esperimenti e, visti i buoni risultati iniziali, ho approfondito
l'argomento. Ho constatato che la CallWindowProcA, sebbene
normalmente funzioni con 5 argomenti, funziona anche con un
numero diverso di argomenti. Il minimo è ovviamente 1, perchè
è sempre necessario l'indirizzo della procedura da richiamare.
Facciamo un esempio pratico:
'###########################################################
'# Questo codice va inserito in un modulo standard
#
'###########################################################
Declare Function CallProc0 Lib "user32" Alias "CallWindowProcA" (ByVal _ FunctionAddress As Long) As Long Public Function Prova() As Long Form1.Print "Ciauz!" Prova = 2001 End Function '<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->
'################################################################ '# Questo nella sezione dichiarazioni di un Form chiamato Form1 # '# contenente un bottone chiamato Command1 # '################################################################ Private Sub Command1_Click() Print CallProc0(AddressOf Prova) End Sub
Eseguitelo e cliccate su Command1. Sul form la
funzione scriverà "Ciauz!", e poi verrà scritto 2001,
che è il valore di ritorno della funzione. Adesso un'altra
versione con 1 argomento:
'###########################################################
'# Questo codice va inserito in un modulo standard
#
'###########################################################
Declare Function CallProc1 Lib "user32" Alias "CallWindowProcA" (ByVal _ FunctionAddress As Long, ByVal Argomento1 As Long) As Long Public Function Prova(ByVal Argomento As Long) As Long Form1.Print "Ciauz!" Form1.Print "L'argomento che mi hai passato è: " & Argomento Prova = Argomento End Function '<-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->
'################################################################ '# Questo nella sezione dichiarazioni di un Form chiamato Form1 # '# contenente un bottone chiamato Command1 # '################################################################ Private Sub Command1_Click() Print CallProc1(AddressOf Prova,2001) End Sub
In questo caso, viene passato un argomento, che
viene visualizzato e restituito come valore di ritorno. Potremmo mettere
decine di argomenti; l'unica cosa da ricordare è che bisogna
inserire sempre come primo argomento il puntatore alla funzione
da richiamare. Infatti, con le convenzioni di chiamata StdCall,
gli argomenti vengono immessi nello stack in ordine inverso. Ciò
significa che il primo argomento sarà l'ultimo ad essere pushato,
e quindi la CallWindowProcA può leggerlo e richiamarlo
indipendentemente dal numero di argomenti passati.
Se il numero di argomenti passati è diverso dal numero di
argomenti ricevuti dalla funzione, si avrà l'errore di run-time
49, cioè "Convenzione di chiamata DLL non valida". Se
si gestisce l'errore, la funzione sarà eseguita ugualmente, ma
non sarà possibile ricevere un eventuale valore di ritorno.
Generalmente è sufficiente utilizzare questo
sistema. Talvolta, però, può essere necessario richiamare la
funzione desiderata il più velocemente possibile, ad esempio se
si deve richiamare molte volte di seguito (ad esempio in un ciclo). In tal caso, è
necessario utilizzare delle API non documentate che, a differenza
della CallWindowProcA, sono apposite per questo scopo. Queste,
contenute in Kernel32.dll, hanno il nome di Callbackxx,
dove xx è un numero decimale che identifica quanti
argomenti passiamo alla funzione. Ad esempio, la Callback4 passerà
0 argomenti. E perchè 4? Perché nello stack viene comunque
messa una dword, dove viene salvato il Return Address. La Callback8 passerà
1 argomento, e vengono messe nello stack 2 dwords (il Return
Address e l'argomento). In generale, per passare n argomenti, si
userà la Callback(n*4+4). Con gli esempi che abbiamo fatto: 0
argomenti * 4+4 = 4, ed useremo la Callback4. 1 * 4 + 4 = 8 (Callback8).
Per formula inversa, la callback28 avrà (28-4):4, cioè 6
argomenti. Si possono passare fino ad un massimo di 15 argomenti
(callback64), ma questo limite non rappresenta un problema, tanto 15 argomenti bastano ed avanzano.
N.B.: A differenza della CallWindowProcA, ogni
Callbackxx è specifica per il numero di argomenti. Con le
Callbackxx, l'indirizzo della funzione da richiamare deve essere
passato come ultimo argomento.
Adesso facciamo un esempio con la Callback8 (1 solo argomento):
'############################################################
'# Questo codice deve essere inserito in un modulo standard #
'############################################################
Declare Function Callback8 Lib "user32"(ByVal Argomento1 As Long, Byval _ FunctAddress As Long) As Long Public Function Prova(ByVal Argomento As Long) As Long Form1.Print "Ciauz!" Form1.Print "L'argomento che mi hai passato è: " & Argomento Prova = Argomento End Function '################################################################ '# Questo nella sezione dichiarazioni di un Form chiamato Form1 # '# contenente un bottone chiamato Command1 # '################################################################ Private Sub Command1_Click() Print Callback8(2001, AddressOf Prova) End Sub
Tuttavia, sebbene le
callbackxx siano decisamente più veloci rispetto alla CallWindowProcA, sono
molto più instabili. Non è possibile gestire eventuali errori.
Un minimo sbaglio e... CRASH!!! Se non è assolutamente necessario, si
consiglia di evitare il loro uso. In ogni caso, salvare molto
spesso.
Già che ci siamo, diamo un'occhiata al codice di CallWindowProcA e di una callbackxx qualunque per capire perché le callbackxx sono molto più veloci.
USER32!CallWindowProcA
MOV ECX,[ESP+04]
JECXZ BFF558E6
CMP WORD PTR [ECX],6890
JNZ BFF558E6
CMP WORD PTR [ECX+06],6890
JNZ BFF558E6
CMP DWORD PTR [ECX+08],00030000
JZ BFF558DB
CMP DWORD PTR [ECX+08],00030001
JNZ BFF558E6
CMP BYTE PTR [ECX+0C],E9
JNZ BFF558E6
JMP BFF5470F
POP ECX
POP EAX
PUSH ECX
JMP EAX
Ora osserviamo il codice di una Callbackxx,
ad esempio la Callback4:
KERNEL32!Callback4
POP EDX
XCHG EDX,[ESP]
PUSH BFF93333 ;BFF93333 è
l'indirizzo del ret
JMP EDX
RET
Vedete la differenza? 17 righe della CallWindowProcA contro le 5 della Callback4 (così come le altre callbackxx). Ecco perché le callbackxx sono molto più veloci. Dato che è semplicissimo, spieghiamo il codice della callback4:
POP EDX
;Poppa in edx il return address
XCHG EDX,[ESP] ;Scambia il
return address con la callback che vogliamo chiamare
PUSH BFF93333 ;pusha l'indirizzo
del ret ---+
JMP EDX ;salta
alla nostra callback |
RET
;
<---------+
In questo modo, al momento del jmp, nello stack rimarranno due dwords: la prima è il Return Address per la callback. Tale dword equivale a BFF93333 che è l'indirizzo del ret. La seconda dword è il vero return address. In realtà sarebbe stato ancora più veloce così:
POP EDX
XCHG EDX,[ESP]
JMP EDX
In questo caso, invece di lasciare nello stack
2 return address, lasciamo solo quello vero, e ci risparmiamo il push e il ret.
Da notare che si sarebbe potuta fare una callback generica con questo codice:
POP EDX
JMP EDX
L'indirizzo della callback andrebbe messo prima
(come per la CallWindowProcA), ma sarebbe velocissimo e basterebbe una sola
funzione API per richiamare un numero qualunque di argomenti.
Ovviamente Windogs usa l'algoritmo più lento (nonché più dispendioso in
termini di spazio), ma che volete... il kernel l'ha fatto mamma microsoft, mica
possiamo aspettarci granché...
Il meccanismo delle Callbackxx è sempre lo stesso. L'unica istruzione che cambia è la seconda, che diventa "XCHG EDX,[ESP+04]" per la Callback8, "XCHG EDX,[ESP+08]" per la Callback12 e così via.
Per questo paragrafo abbiamo finito. Adesso
parleremo di un argomento molto più complicato ma utilissimo, perché ci
permette di fare in un programma praticamente ciò che vogliamo: includere
codice assembler in VB.
Codice assembler
Le basi
A differenza di ogni buon compilatore, il VB
non ha l'inline assembler. In C basta usare "__asm{}" e possiamo
scrivere tutto il codice assembler che ci pare. In VB ciò non è possibile, e
questo vuol dire che teoricamente non possiamo usare codice assembler se
non scrivendoci in C o in asm una dll a parte. Questo teoricamente, ma praticamente
possiamo prendere per il culo il VB e codare ciò che ci pare, e tra poco
vedremo come fare. Mi sa che questo capitolo sarà un po' lunghetto. Beh,
armiamoci di pazienza e cominciamo.
In realtà noi non inseriremo nei programmi istruzioni mnemoniche (ovvero non chiameremo per nome le istruzioni), bensì inseriremo il codice compilato. Il VB però non ha l'istruzione db del MASM o del TASM, quindi noi come faremo? Niente di più semplice: Lo inseriremo in un array (dimenticavo... in VB si chiamano "matrici")... dicevo, inseriremo il nostro codice in una matrice, che riempiremo con il nostro codice. E poi come faremo a richiamarlo? Non lo sapete??? Ma allora cosa abbiamo studiato nel paragrafo precedente??? Useremo una delle funzioni callback precedentemente esaminate. :-) Però per richiamare una callback ci serve il suo indirizzo... Come faremo ad averlo? Semplicemente con la funzione non documentata "VarPtr". Apriamo una breve parentesi sulla funzione VarPtr.
VarPtr (ptr As Any) As Long La funzione VarPtr restituisce un puntatore ad una variabile che può essere di qualunque tipo eccetto oggetti e stringhe di lunghezza variabile. In questi due casi si usano rispettivamente ObjPtr e StrPtr (entrambe rigorosamente non documentate). Il codice della funzione VarPtr è il seguente: mov eax,[esp+04] Semplicissimo: quando noi passiamo l'argomento ptr lo passiamo come ByRef, ovvero passiamo alla VarPtr il puntatore alla variabile, cioè proprio quello che vogliamo sapere, e quindi la funzione VarPtr non fa altro che restituirci il valore che noi gli passiamo. Infatti, se noi passiamo un valore come ByVal, la VarPtr restituirà il valore che gli abbiamo passato. |
Capito? Spero di si. In pratica noi ci troviamo l'indirizzo dell'array contenente il nostro codice precedentemente assemblato, e poi lo richiamiamo come se fosse una callback. Visto che era facile? Beh, mica tanto! Tra l'altro fra un po' analizzeremo i vari problemi dovuti a questo sistema, e allora sì che saranno guai...
Dunque, facciamo un esempio, che poi spiegheremo dettagliatamente. Uso la CallWindowProcA perché per chiamare le callback è la più sicura e semplice da usare, ma nessuno ci impedisce di usare una callbackxx. Tanto per dimostrare le potenzialità di questo sistema, faremo qualcosa che in VB non si può fare. Spè che ci penso......... Ok, ho un'idea. Faremo una funzione che ci restituisce 1 se c'è SoftICE e 0 se non c'è :-))))))). Suppongo che non abbiate mai visto un programma VB con controlli anti-SoftICE... Beh ora lo vedrete! :))) Useremo il classico trick dell'interrupt 68 con funzione 43.
Private Declare Function CallProc0
Lib "user32" Alias
"CallWindowProcA" (ByVal ProcAddress As
Long) As Long
Private Sub Form_Load()
Dim MC(1 To 18)
As Byte 'MC sta per
MachineCode
Dim Puntatore As Long
'Codice della funzione:
'mov ah,043h
'int 068h
'cmp ax,0F386h
'jz sice
'xor eax,eax
'jmp fine
'sice:xor eax,eax
'inc eax
'fine:ret
MC(1) = &HB4 'mov ah,043h
MC(2) = &H43
MC(3) = &HCD 'int 068h
MC(4) = &H68
MC(5) = &H66 'cmp ax,0F386h
MC(6) = &H3D
MC(7) = &H86
MC(8) = &HF3
MC(9) = &H74 'jz sice
MC(10) = &H4
MC(11) = &H33 'xor eax,eax
MC(12) = &HC0
MC(13) = &HEB 'jmp fine
MC(14) = &H3
MC(15) = &H33 'xor eax,eax
MC(16) = &HC0
MC(17) = &H40 'inc eax
MC(18) = &HC3 'ret
Puntatore = VarPtr(MC(1))
If CallProc0(Puntatore) = 1 Then
MsgBox "Ahò il SoftICE lo devi chiudere
ok?", vbCritical
End
End If
End Sub
E adesso spieghiamo riga per riga:
Private Declare Function CallProc0 Lib "user32"
Alias "CallWindowProcA" (ByVal ProcAddress
As Long) As Long
Qui non c'è molto da spiegare:
dichiariamo la CallWindowProcA con un solo argomento, che sarà ovviamente la
callback (in questo caso il codice assembly) che vogliamo richiamare, la quale
NON riceverà argomenti.
'Codice della funzione:
'mov ah,043h
'int 068h
'cmp ax,0F386h
'jz sice
'xor eax,eax
'jmp fine
'sice:xor eax,eax
'inc eax
'fine:ret
Questo è il codice assembly originario, che ho lasciato come commenti. E' per
MASM, ma con poche modifiche dovrebbe andare bene anche per il TASM (forse va
già bene).
Ovviamente io l'ho già compilato perché a noi servono gli opcodes, che
prenderemo con l'Hex Workshop o un altro editor esadecimale.
MC(1) = &HB4 'mov ah,043h
MC(2) = &H43
MC(3) = &HCD 'int 068h
MC(4) = &H68
MC(5) = &H66 'cmp ax,0F386h
MC(6) = &H3D
MC(7) = &H86
MC(8) = &HF3
MC(9) = &H74 'jz sice
MC(10) = &H4
MC(11) = &H33 'xor eax,eax
MC(12) = &HC0
MC(13) = &HEB 'jmp fine
MC(14) = &H3
MC(15) = &H33 'xor eax,eax
MC(16) = &HC0
MC(17) = &H40 'inc eax
MC(18) = &HC3 'ret
Questa parte riempie la matrice con il
nostro codice già compilato e prelevato con un Hex Editor.
Puntatore = VarPtr(MC(1))
Questa riga mette nella variabile
"Puntatore" l'indirizzo della matrice, che ci servirà per richiamarla
con CallWindoProcA.
If CallProc0(Puntatore) = 1
Then
MsgBox "Ahò il SoftICE lo devi chiudere
ok?", vbCritical
End
End If
Questa parte richiama il nostro codice asm.
Se il valore di ritorno è 1 vuol dire che c'è il SoftICE, e allora ti dice molto
delicatamente di chiuderlo, e poi blocca l'esecuzione con un brusco
"End". Se il valore di ritorno è 0 invece il programma runna
tranquillamente. :-)
Andiamo un po' più sui dettagli
Adesso cominciamo a studiare meglio questo sistema e analizzarne le potenzialità.
Il codice precedentemente analizzato non è una procedura StdCall e non ha argomenti. Ciò non vuol dire che noi non possiamo utilizzare queste due opzioni. Adesso analizzeremo un altro esempio di codice asm: sarà una procedura StdCall con un argomento (ovviamente potremmo usarne anche più di uno). Stavolta non faremo nulla che in VB non si può fare. E' solo per farvi vedere come passare argomenti.
La funzione cha faremo sarà semplicissima: ci azzererà
una variabile di cui passiamo il puntatore (in realtà per passare il puntatore
non è necessario usare VarPtr, ma è sufficiente passare l'argomento come ByRef).
Segue il codice:
Private Declare Function CallProc1
Lib "user32" Alias "CallWindowProcA" (ProcAddress
As Any, Argomento1 As Any)
As Long
Private Sub Form_Click()
Dim MC(0 To 14)
As Byte
Dim Test As Long
'Questo è il codice MASM della funzione:
' azzera proc stdcall Variabile:DWORD
' xor eax,eax
' mov ebx,Variabile
' xchg eax, [ebx]
' ret
' azzera endp
'Badare che il codice viene così compilato:
' push ebp
' mov ebp,esp
' xor eax,eax
' mov ebx,[ebp+08]
' xchg eax,[ebx]
' leave
' ret 04
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp,esp
MC(3) = &HEC
MC(4) = &H33 'xor eax,eax
MC(5) = &HC0
MC(6) = &H8B 'mov ebx,[ebp+08]
MC(7) = &H5D
MC(8) = &H8
MC(9) = &H87 'xchg eax,[ebx]
MC(10) = &H3
MC(11) = &HC9 'leave
MC(12) = &HC2 'ret 04
MC(13) = &H4
MC(14) = &H0
Test = 2001
Print "Il vecchio valore di
Test era: " & CallProc1(MC(1), Test)
Print "Il nuovo valore è:
" & Test
End Sub
Come al solito spieghiamo tutto:
Private Declare Function CallProc1
Lib "user32" Alias "CallWindowProcA" (ProcAddress
As Any, Argomento1 As Any)
As Long
Dichiara la CallWindowProcA con 1 argomento
(oltre ovviamente all'indirizzo della nostra procedura). Questa volta però
dichiariamo gli argomenti come ByRef, e come tipo mettiamo "Any".
Questo accorgimento ci dà la possibilità di gestire la funzione come vogliamo,
perché così disattiviamo il controllo dei tipi. Se vogliamo passare un
argomento come ByVal, basta "dirlo" quando richiamiamo la funzione.
Per farlo basta anteporre la parola chiave "ByVal" prima
dell'argomento che vogliamo passare per valore.
Facendo così abbiamo più libertà di scelta nel modo in cui vogliamo chiamare
la funzione. Tuttavia dobbiamo prestare maggiore attenzione perché il VB non si
accorgerà se noi passiamo un argomento in modo errato.
Dim MC(1 To 14)
As Byte
Dim Test As Long
Qui nulla da spiegare, giusto? :-)
'Questo è il codice MASM della funzione:
' azzera proc stdcall Variabile:DWORD
' xor eax,eax
' mov ebx,Variabile
' xchg eax, [ebx]
' ret
' azzera endp
'Questa procedura azzera una variabile di cui passiamo
'il puntatore, e come valore di ritorno restituisce
'il suo vecchio valore.
'Badare che il codice viene così compilato:
' push ebp
' mov ebp,esp
' xor eax,eax
' mov ebx,[ebp+08]
' xchg eax,[ebx]
' leave
' ret 04
Come al solito mettiamo come commento il
codice asm...
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp,esp
MC(3) = &HEC
MC(4) = &H33 'xor eax,eax
MC(5) = &HC0
MC(6) = &H8B 'mov ebx,[ebp+08]
MC(7) = &H5D
MC(8) = &H8
MC(9) = &H87 'xchg eax,[ebx]
MC(10) = &H3
MC(11) = &HC9 'leave
MC(12) = &HC2 'ret 04
MC(13) = &H4
MC(14) = &H0
...e riempiamo la matrice
Test = 2001
Mettiamo un valore qualunque nella
variabile "Test".
Print
"Il vecchio valore di Test era: " & CallProc1(MC(1), Test)
Print "Il nuovo valore
è: " & Test
E infine richiamiamo il codice asm e lo
testiamo. Se tutto va bene, appena noi faremo click sul form in fase di
esecuzione dovrà apparire il seguente messaggio:
Il vecchio valore di Test era: 2001
Il nuovo valore è: 0
Ok, adesso passiamo ad un altro utile argomento.
Chiamate API
Come avrete notato nel codice asm analizzato finora non c'è nessuna chiamata API. In questo paragrafo spiegheremo come fare ad includerle nel nostro codice.
Se provate in MASM a mettere una chiamata API
ed eseguire il codice asm, SICURAMENTE avrete un bel CRASH. Perché?
Semplicemente perché quando in un programma chiamiamo una funzione API, la
richiamiamo indirettamente: la nostra call richiama un jmp che ci spedisce nel
vero indirizzo della funzione API. Ma l'indirizzo puntato dal jmp non è sempre
uguale, ma viene fornito dal sistema operativo.
E se noi vogliamo inserire ad ogni costo questa cazz di chiamata API? Semplice:
ci troviamo manualmente il suo indirizzo con GetProcAddress e lo forniamo al
nostro codice asm. Per farlo il modo migliore sarebbe mettere una call diretta e
fixarla manualmente modificando l'opcode, ma questo approccio è molto
complesso. Perciò noi faremo in un altro modo: passeremo l'indirizzo della
funzione API come argomento in modo che il nostro codice la possa richiamare con
una semplice call. Come al solito facciamo un esempio:
Private Declare Function CallProc1
Lib "user32" Alias "CallWindowProcA" (ProcAddress
As Any, ByVal Argomento1
As Long) As Long
Private Declare Function LoadLibrary Lib
"kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName
As String) As Long
Private Declare Function FreeLibrary Lib
"kernel32" (ByVal hLibModule As
Long) As Long
Private Declare Function GetProcAddress Lib
"kernel32" (ByVal hModule As
Long, ByVal lpProcName As
String) As Long
Private Sub Form_Click()
Dim MC(1 To 12)
As Byte
Dim hUser32 As Long
Dim MessageBeep_Address As
Long
'Questo è il codice MASM della funzione:
'APICall proc stdcall MessageBeep_Address:DWORD
' push NULL
' Call MessageBeep_Address
' ret
'APICall endp
'Che viene così assemblato:
' push ebp
' mov ebp,esp
' push 0
' call [ebp+08]
' leave
' ret 04
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp, esp
MC(3) = &HEC
MC(4) = &H6A 'push 0
MC(5) = &H0
MC(6) = &HFF 'call ebp+08
MC(7) = &H55
MC(8) = &H8
MC(9) = &HC9 'leave
MC(10) = &HC2 'ret 04
MC(11) = &H4
MC(12) = &H0
hUser32 = LoadLibrary("user32.dll")
MessageBeep_Address = GetProcAddress(hUser32, "MessageBeep")
FreeLibrary hUser32
CallProc1 MC(1), MessageBeep_Address
End Sub
Spieghiamo:
Private Declare Function CallProc1
Lib "user32" Alias
"CallWindowProcA" (ProcAddress As Any, ByVal Argomento1
As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32"
Alias "LoadLibraryA" ( ByVal lpLibFileName
As String) As Long
Private Declare Function FreeLibrary Lib "kernel32"
( ByVal hLibModule As Long)
As Long
Private Declare Function GetProcAddress Lib "kernel32"
( ByVal hModule As Long,
ByVal lpProcName As String)
As Long
Le solite dichiarazioni. Questa volta ci
sono anche le API che ci serviranno per trovare l'indirizzo della MessageBeep
Dim MC(1 To 12)
As Byte
Dim hUser32 As Long
Dim MessageBeep_Address As
Long
La variabile "hUser32" conterrà
l'handle della user32.dll. La Variabile "MessageBeep_Address", invece,
conterrà l'indirizzo della MessageBeep (ma vàaa!!!)
'Questo è il codice MASM della funzione:
'APICall proc stdcall MessageBeep_Address:DWORD
' push NULL
' Call MessageBeep_Address
' ret
'APICall endp
'Che viene così assemblato:
' push ebp
' mov ebp,esp
' push 0
' call [ebp+08]
' leave
' ret 04
La solita documentazione...
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp, esp
MC(3) = &HEC
MC(4) = &H6A 'push 0
MC(5) = &H0
MC(6) = &HFF 'call ebp+08
MC(7) = &H55
MC(8) = &H8
MC(9) = &HC9 'leave
MC(10) = &HC2 'ret 04
MC(11) = &H4
MC(12) = &H0
...e il solito riempimento della matrice.
hUser32 = LoadLibrary("user32.dll")
Si fa fornire da Windows l'handle
dell'user32.
MessageBeep_Address = GetProcAddress(hUser32, "MessageBeep")
E poi trova l'indirizzo della MessageBeep
FreeLibrary hUser32
Segnaliamo a Windows che non ci serve più
l'handle dell'user32, così evitiamo inutili sprechi di risorse.
CallProc1 MC(1), MessageBeep_Address
Ed infine richiamiamo il nostro codice asm,
che in pratica altro non fa che emettere un semplice beep, ovvero fa in 24 righe
quello che l'istruzione "Beep" farebbe in una sola riga... ma questo
è solo un esempio, voi nel vostro codice potrete fare quello che volete.
Avrei voluto analizzare anche l'altro approccio, cioè il fixing a runtime dell'opcode della call, ma siccome l'opcode della call dipende dall'indirizzo della call stessa le cose si farebbero complicate, perché dovremmo fare calcoli su calcoli per ricalcolarci il corretto opcode... insomma sarebbe un casino di lavoro... Già che è palloso così, se poi ci complichiamo la vita inutilmente!...
In ultimo analizziamo l'ultima cosa: i problemi. Finora è filato tutto liscio, e di solito infatti non ci sono problemi, ma non sempre è così.
Problemi
Talvolta può succedere che scriviate il vostro
codice asm, fate tutto il lavoro di routine, richiamate il codice asm e...
Errore di run-time 49: Convenzione di chiamata dll non valida. Che cazzo dici?
Ricontrolliamo il codice asm... è giusto. Ricontrolliamo parametri
passati... sono giusti... e allora che cazzo vuoi?
Boh! Ancora non ho capito nemmeno io il motivo di questo errore dello stack.
Paradossalmente può avvenire quando salviamo tutti i registri e li
ripristiniamo prima del ret. Se invece non salviamo i registri l'errore non
avviene.
Da notare che questo errore avviene solo in P-Code. Se compiliamo il programma
in codice nativo non si dovrebbero avere problemi.
Ci sono due modi di risolvere il problema: possiamo lasciare il codice così
com'è, dato che compilando in codice nativo non ci sono problemi. Ciò però
potrebbe impedirci di debuggare il nostro programma. Il secondo sistema consiste
semplicemente nel non salvare i registri. Questo non dovrebbe
provocarci mai nessun problema per un motivo: dato che noi richiamiamo il codice
asm con una funzione API (generalmente la CallWindowProcA), la nostra chiamata
al codice assembly sarà trattata dal compilatore come una qualunque chiamata API. Ma siccome il
compilatore non può sapere in anticipo quali registri saranno modificati da una
qualunque funzione API, suppongo che prima di richiamarne una si salvi eventuali valori
importanti, e così noi stiamo più tranquilli. :-)
Un altro errore cui si può andare incontro è
l'uso di offset fissi. Ad esempio, dà parecchi fastidi l'uso di stringhe
all'interno del nostro codice asm. Il modo migliore per risolvere questo
problema è mettere la stringa in una matrice (allo stesso modo del codice asm)
e poi passare al codice assembly l'indirizzo di tale matrice (trovato al solito
con VarPtr o più semplicemente passando l'argomento come ByRef).
Facciamo un esempio, dato che questo argomento è importante perché l'uso di
stringhe è molto comune.
Private Declare Function CallProc3
Lib "user32" Alias "CallWindowProcA" (ProcAddress
As Any, Testo_Address As
Any, Caption_Address As Any, ByVal MessageBoxA_Address
As Long) As Long
Private Declare Function LoadLibrary Lib
"kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName
As String) As Long
Private Declare Function FreeLibrary Lib
"kernel32" (ByVal hLibModule As
Long) As Long
Private Declare Function GetProcAddress Lib
"kernel32" (ByVal hModule As
Long, ByVal lpProcName As String)
As Long
Private Sub Form_Click()
Dim MC(1 To 20)
As Byte
Dim Testo(1 To
35) As Byte
Dim MyCaption(1 To
37) As Byte
Dim hUser32 As Long
Dim MessageBoxA_Address As Long
'Questo è il codice MASM della funzione:
'MsgBox proc stdcall Testo_Address:DWORD, Caption_Address:DWORD,
MessageBoxA_Address:DWORD
' push MB_OK + MB_ICONINFORMATION
' push Caption_Address
' push Testo_Address
' push Null
' Call MessageBoxA_Address
' ret
'MsgBox endp
'Che viene così compilato:
' push ebp
' mov ebp,esp
' push 40
' push [ebp+0C]
' push [ebp+08]
' push 0
' call
' leave
' ret 0C
'Codice asm
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp,esp
MC(3) = &HEC
MC(4) = &H6A 'push 40h
MC(5) = &H40
MC(6) = &HFF 'push [ebp+0C]
MC(7) = &H75
MC(8) = &HC
MC(9) = &HFF 'push [ebp+08]
MC(10) = &H75
MC(11) = &H8
MC(12) = &H6A 'push 0
MC(13) = &H0
MC(14) = &HFF 'call [ebp+10]
MC(15) = &H55
MC(16) = &H10
MC(17) = &HC9 'leave
MC(18) = &HC2 'ret
MC(19) = &HC
MC(20) = &H0
'Testo MsgBox
Testo(1) = &H51 'Q
Testo(2) = &H75 'u
Testo(3) = &H65 'e
Testo(4) = &H73 's
Testo(5) = &H74 't
Testo(6) = &H6F 'o
Testo(7) = &H20 '
Testo(8) = &HE8 'è
Testo(9) = &H20 '
Testo(10) = &H69 'i
Testo(11) = &H6C 'l
Testo(12) = &H20 '
Testo(13) = &H74 't
Testo(14) = &H65 'e
Testo(15) = &H73 's
Testo(16) = &H74 't
Testo(17) = &H6F 'o
Testo(18) = &H20 '
Testo(19) = &H64 'd
Testo(20) = &H65 'e
Testo(21) = &H6C 'l
Testo(22) = &H6C 'l
Testo(23) = &H61 'a
Testo(24) = &H20 '
Testo(25) = &H4D 'M
Testo(26) = &H65 'e
Testo(27) = &H73 's
Testo(28) = &H73 's
Testo(29) = &H61 'a
Testo(30) = &H67 'g
Testo(31) = &H65 'e
Testo(32) = &H42 'B
Testo(33) = &H6F 'o
Testo(34) = &H78 'x
Testo(35) = &H0
'Caption MsgBox
MyCaption(1) = &H51 'Q
MyCaption(2) = &H75 'u
MyCaption(3) = &H65 'e
MyCaption(4) = &H73 's
MyCaption(5) = &H74 't
MyCaption(6) = &H61 'a
MyCaption(7) = &H20 '
MyCaption(8) = &HE8 'è
MyCaption(9) = &H20 '
MyCaption(10) = &H6C 'l
MyCaption(11) = &H61 'a
MyCaption(12) = &H20 '
MyCaption(13) = &H43 'C
MyCaption(14) = &H61 'a
MyCaption(15) = &H70 'p
MyCaption(16) = &H74 't
MyCaption(17) = &H69 'i
MyCaption(18) = &H6F 'o
MyCaption(19) = &H6E 'n
MyCaption(20) = &H20 '
MyCaption(21) = &H64 'd
MyCaption(22) = &H65 'e
MyCaption(23) = &H6C 'l
MyCaption(24) = &H6C 'l
MyCaption(25) = &H61 'a
MyCaption(26) = &H20 '
MyCaption(27) = &H4D 'M
MyCaption(28) = &H65 'e
MyCaption(29) = &H73 's
MyCaption(30) = &H73 's
MyCaption(31) = &H61 'a
MyCaption(32) = &H67 'g
MyCaption(33) = &H65 'e
MyCaption(34) = &H42 'B
MyCaption(35) = &H6F 'o
MyCaption(36) = &H78 'x
MyCaption(37) = &H0
hUser32 = LoadLibrary("user32.dll")
MessageBoxA_Address = GetProcAddress(hUser32, "MessageBoxA")
FreeLibrary hUser32
CallProc3 MC(1), Testo(1), MyCaption(1), MessageBoxA_Address
End Sub
Azzo in questo codice utilizziamo tutto quello che abbiamo analizzato! Dalle callback alle stringhe, dalle API alle StdCall. Ok, facciamo l'ultimo sforzo a analizziamo quest'ultimo codice:
Private Declare Function CallProc3
Lib "user32" Alias "CallWindowProcA" (ProcAddress
As Any, Testo_Address As
Any, Caption_Address As Any, ByVal MessageBoxA_Address
As Long) As Long
Private Declare Function LoadLibrary Lib "kernel32"
Alias "LoadLibraryA" ( ByVal lpLibFileName
As String) As Long
Private Declare Function FreeLibrary Lib "kernel32"
( ByVal hLibModule As Long)
As Long
Private Declare Function GetProcAddress Lib "kernel32"
( ByVal hModule As Long,
ByVal lpProcName As String)
As Long
Private Sub Form_Click()
Dim MC(1 To 20)
As Byte
Dim Testo(1 To 35)
As Byte
Dim MyCaption(1 To 37)
As Byte
Dim hUser32 As Long
Dim MessageBoxA_Address As Long
Le solite dichiarazioni...
'Questo è il codice MASM della funzione:
'MsgBox proc stdcall Testo_Address:DWORD, Caption_Address:DWORD,
MessageBoxA_Address:DWORD
' push MB_OK + MB_ICONINFORMATION
' push Caption_Address
' push Testo_Address
' push Null
' Call MessageBoxA_Address
' ret
'MsgBox endp
'Che viene così compilato:
' push ebp
' mov ebp,esp
' push 40
' push [ebp+0C]
' push [ebp+08]
' push 0
' call
' leave
' ret 0C
La solita documentazione...
'Codice asm
MC(1) = &H55 'push ebp
MC(2) = &H8B 'mov ebp,esp
MC(3) = &HEC
MC(4) = &H6A 'push 40h
MC(5) = &H40
MC(6) = &HFF 'push [ebp+0C]
MC(7) = &H75
MC(8) = &HC
MC(9) = &HFF 'push [ebp+08]
MC(10) = &H75
MC(11) = &H8
MC(12) = &H6A 'push 0
MC(13) = &H0
MC(14) = &HFF 'call [ebp+10]
MC(15) = &H55
MC(16) = &H10
MC(17) = &HC9 'leave
MC(18) = &HC2 'ret
MC(19) = &HC
MC(20) = &H0
Il solito riempimento della matrice che
conterrà il nostro codice asm...
'Testo MsgBox
Testo(1) = &H51 'Q
Testo(2) = &H75 'u
Testo(3) = &H65 'e
Testo(4) = &H73 's
Testo(5) = &H74 't
Testo(6) = &H6F 'o
Testo(7) = &H20 '
Testo(8) = &HE8 'è
Testo(9) = &H20 '
Testo(10) = &H69 'i
Testo(11) = &H6C 'l
Testo(12) = &H20 '
Testo(13) = &H74 't
Testo(14) = &H65 'e
Testo(15) = &H73 's
Testo(16) = &H74 't
Testo(17) = &H6F 'o
Testo(18) = &H20 '
Testo(19) = &H64 'd
Testo(20) = &H65 'e
Testo(21) = &H6C 'l
Testo(22) = &H6C 'l
Testo(23) = &H61 'a
Testo(24) = &H20 '
Testo(25) = &H4D 'M
Testo(26) = &H65 'e
Testo(27) = &H73 's
Testo(28) = &H73 's
Testo(29) = &H61 'a
Testo(30) = &H67 'g
Testo(31) = &H65 'e
Testo(32) = &H42 'B
Testo(33) = &H6F 'o
Testo(34) = &H78 'x
Testo(35) = &H0
'Caption MsgBox
MyCaption(1) = &H51 'Q
MyCaption(2) = &H75 'u
MyCaption(3) = &H65 'e
MyCaption(4) = &H73 's
MyCaption(5) = &H74 't
MyCaption(6) = &H61 'a
MyCaption(7) = &H20 '
MyCaption(8) = &HE8 'è
MyCaption(9) = &H20 '
MyCaption(10) = &H6C 'l
MyCaption(11) = &H61 'a
MyCaption(12) = &H20 '
MyCaption(13) = &H43 'C
MyCaption(14) = &H61 'a
MyCaption(15) = &H70 'p
MyCaption(16) = &H74 't
MyCaption(17) = &H69 'i
MyCaption(18) = &H6F 'o
MyCaption(19) = &H6E 'n
MyCaption(20) = &H20 '
MyCaption(21) = &H64 'd
MyCaption(22) = &H65 'e
MyCaption(23) = &H6C 'l
MyCaption(24) = &H6C 'l
MyCaption(25) = &H61 'a
MyCaption(26) = &H20 '
MyCaption(27) = &H4D 'M
MyCaption(28) = &H65 'e
MyCaption(29) = &H73 's
MyCaption(30) = &H73 's
MyCaption(31) = &H61 'a
MyCaption(32) = &H67 'g
MyCaption(33) = &H65 'e
MyCaption(34) = &H42 'B
MyCaption(35) = &H6F 'o
MyCaption(36) = &H78 'x
MyCaption(37) = &H0
Finalmente una novità: riempiamo le
matrici che conterranno le nostre stringhe.
hUser32 = LoadLibrary("user32.dll")
MessageBoxA_Address = GetProcAddress(hUser32, "MessageBoxA")
FreeLibrary hUser32
Ci troviamo l'indirizzo della MessageBoxA...
CallProc3 MC(1), Testo(1), MyCaption(1), MessageBoxA_Address
E infine richiamiamo 'sto cacchio di codice
asm. Se tutto va bene al click sul form dovrebbe apparire una MsgBox.
Passiamo all'ultimo argomento. Sì, lo so che
siete stanchi, ma ormai manca poco alla fine... E poi, voi che leggete, pensate
a me che scrivo!
Debugging
Occhei, analizziamo quest'ultimo breve argomento.
Un giorno accendo il PC, prendo il Visual Basic e apro anche il Masm, preparo il mio bel codice asm, premo F5 e... CRASH! Embè!? Cosa ho sbagliato?
Potrebbe succedervi di trovarvi in questa
situazione. Io mi ci sono trovato proprio mentre scrivevo uno degli esempi di
questo tute. Per analizzare il codice con il SoftICE possiamo seguire due vie: o
mettiamo un breakpoint sulla funzione API che usiamo per chiamare il codice asm,
oppure mettiamo un int3 come entrypoint. Nel secondo caso dobbiamo poi mettere
in sice un "bpint 3", e il debugger popperà all'entrypoint del nostro
codice asm. Qui noi ripristineremo l'opcode che avevamo sostituito con l'int3.
Ovviamente questo sistema va bene se l'errore è nel codice asm. Se invece avete
sbagliato qualcosa da un'altra parte è inutile debuggare: ricontrollatevi il
codice vb diecimila volte, prima o poi risolverete l'arcano! :)
The end!
...pant...pant...pant... Azz quant'è venuto lungo! C'ho messo una settimana a finirlo!
Ciauuuuuz,
Spider
Note finali |
Saluto naturalmente Quequero,
poi un saluto va a Yado, ai miei coetanei Bubbo e TheMR e a tutti gli altri
studenti e frequentatori della UIC.
Un saluto particolare va ad AndreaGeddon, che mi sopporta ed è disponibilissimo ad aiutarmi via e-mail
(grazieeeeeeeeeeeeeeeeee!!!!!!).
Disclaimer |
Questo è un tute di programmazione, quindi 'sto disclaimer che a ca**o serve? Comunque io ci tengo lo stesso a ricordare che il software va comprato e non rubato, dovete registrare il prodotto dopo il periodo di valutazione perché altrimenti la mamma si arrabbia. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial.
Questo tute ha un periodo di valutazione di 5 minuti, se l'avete letto e c'avete messo di più avete l'obbligo legale e morale di mandarmi una e-mail con il numero della vostra carta di credito o, se non vi fidate di me, di inviarmi per raccomandata lire 19.900 (10,27 euro). Le informazioni quivi contenute sono protette dalle leggi italiane e internazionali sul copydown ©, e ogni violazione di tali leggi comporterà l'immediata perdita di tutti i vostri averi che passeranno a me :-P
Byeeeeez!