查看單個文章
舊 2006-05-25, 03:59 PM   #2 (permalink)
psac
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設

2) 陣列檔案類型的實現

陣列的概念比較複雜,為了研究方便,這裡只討論一維陣列,並且不是嵌套的。
先看看靜態陣列的定義與實現。

程式碼:
Dim a(3 To 6) As Integer

反彙編程式碼:

004019FF PUSH 2
00401A01 LEA EAX,DWORD PTR SS:[EBP-2C] // 陣列變數
00401A04 XOR ESI,ESI
00401A06 PUSH 工程1.00401694 // 指向程式碼段
00401A0B PUSH EAX
00401A0C MOV DWORD PTR SS:[EBP-34],ESI
00401A0F CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryConstruct2>] // 構造一個陣列

指行到這裡時看[ebp-2c]的內容:
0063F3E4 01 00 92 00 02 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

這些資料除了63F3F0處的位址是__vbaAryConstruct2函數填進去的,其餘的都是從
401694處拷貝過來的。因此__vbaAryConstruct2函數的作用可以這樣理解:先從401694
處拷貝24個字元到ebp-2c處,然後分配一塊空間,把指向新分配的空間的游標填到63F3F0
處。
那麼上面這些資料到底是什麼意思呢?看下面的分析.


00401A18 PUSH 工程1.00401A30 //指向結束位址
00401A1D LEA EDX,DWORD PTR SS:[EBP-34]
00401A20 LEA ECX,DWORD PTR SS:[EBP-2C] //要解壓縮的陣列變數
00401A23 PUSH EDX
00401A24 PUSH 0
00401A26 MOV DWORD PTR SS:[EBP-34],ECX
00401A29 CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>] // 解壓縮一個陣列

為了弄清楚上面提到的那些記憶體資料的含義,我分別定義了不同大小不同檔案類型的陣列來比較,
下面是dump出來的典型資料:

Dim a(3 To 6)
0063F3E4 01 00 92 08 10 00 00 00 .?...
0063F3EC 00 00 00 00 2C 01 41 00 ....,A.
0063F3F4 04 00 00 00 03 00 00 00 ......

Dim a(3 To 6) As String
0063F3E4 01 00 92 01 04 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

Dim a(3 To 6) As Integer
0063F3E4 01 00 92 00 02 00 00 00 .?...
0063F3EC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3F4 04 00 00 00 03 00 00 00 ......

我總結的陣列變數記憶體資料的說明:
0063F3E4 處的兩個字元代表陣列的維數
0063F3E6 處的一個字元 92 代表靜態陣列
0063F3E7 處的一個字元隨著不同檔案類型的變數有不同的變化。
08 : 變體檔案類型
01 : String
00 : Integer,byte,long,single,double,date
0063F3E8 處的兩個字元表示一個陣列元素所佔的記憶體空間字元數。
0063F3EC 處的4個字元總是0,可能是為了對齊。
0063F3F0 處的兩個字元代表分配的空間的位址游標,即陣列資料。
0063F3F4 處的兩個字元代表靜態陣列元素的個數。
0063F3F8 處的兩個字元代表陣列的起始上標。

上面大概的對陣列變數的資料做了說明,為了驗證一下,再看一個3維陣列的定義:

Dim a(1 To 2, 3 To 5, 6 To 9) As Integer

0063F3D4 03 00 92 00 02 00 00 00 .?...
0063F3DC 00 00 00 00 C0 0F 51 00 ....?Q.
0063F3E4 04 00 00 00 06 00 00 00 ......
0063F3EC 03 00 00 00 03 00 00 00 ......
0063F3F4 02 00 00 00 01 00 00 00 ......

可以看出,靜態陣列的訊息在編譯時就被編碼到了程式碼段裡。
靜態陣列的構造用 __vbaAryConstruct2
靜態陣列的解壓縮用 __vbaAryDestruct

///////////////////////////////////////////////////////////

動態陣列又是怎樣實現的呢?
程式碼:
Dim a() As Date
ReDim a(2 To 5)

反彙編程式碼:
004019CF PUSH 2 //起始上標
004019D1 PUSH 5 //結束上標
004019D3 PUSH 1 //陣列維數
004019D5 LEA EAX,DWORD PTR SS:[EBP-18]
004019D8 PUSH 7 //變數檔案類型
004019DA PUSH EAX //我們重定義的陣列變數
004019DB XOR ESI,ESI
004019DD PUSH 8 //陣列元素所佔記憶體空間的字元數
004019DF PUSH 80 //動態陣列標記
004019E4 MOV DWORD PTR SS:[EBP-18],ESI
004019E7 CALL DWORD PTR DS:[<&MSVBVM60.__vbaRedim>] // ReDim
004019ED ADD ESP,1C
004019F0 MOV DWORD PTR SS:[EBP-4],ESI
004019F3 PUSH 工程1.00401A05
004019F8 LEA ECX,DWORD PTR SS:[EBP-18] //陣列變數
004019FB PUSH ECX
004019FC PUSH 0
004019FE CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>] //解壓縮陣列

當執行到 004019ED 時,我們檢視[ebp-18]處的記憶體資料,可以看到是

0063F3F8 D0 0F 51 00 ?Q.豇

這是一個游標,我們接著 follow dword in dump,可以看到資料如下:

00510FD0 01 00 80 00 08 00 00 00 .€....
00510FD8 00 00 00 00 2C 01 41 00 ....,A.
00510FE0 04 00 00 00 02 00 00 00 ......

這個結構和靜態陣列的結構沒有什麼不同! ^_^
同時也可以看出,動態陣列是動態分配的,這和靜態陣列訊息被編譯到程式碼段裡不同。

總結:
動態陣列的ReDim(重定義)用 __vbaRedim (注:這是可變參數的函數)
動態陣列的解壓縮用 __vbaAryDestruct

///////////////////////////////////////////////////////////
再看一下常用的陣列操作:
先看兩個函數,Lbound和Ubound。它們的實現分別如下:

=====================================
__vbaLbound ;函數 Lbound ,取陣列上標下界

LEA EAX,DWORD PTR SS:[EBP-2C] ;參數1,陣列
PUSH EAX
PUSH 1 ;參數2,陣列維數
CALL DWORD PTR DS:[<&MSVBVM60.__vbaLboun>; MSVBVM60.__vbaLbound
;結果在eax中返回
=====================================
__vbaUbound ;函數 Ubound ,取陣列上標上界

LEA ECX,DWORD PTR SS:][EBP-2C] ;參數1,陣列
PUSH ECX
PUSH 1 ;參數2,陣列維數
CALL DWORD PTR DS:[<&MSVBVM60.__vbaUboun>;MSVBVM60.__vbaUbound
;結果在eax中返回
=====================================
這兩個函數再操作動態陣列時常使用,這裡先記住他們的實現方法。

還有一個常使用的函數:Erase ,這個函數用來重新啟始化靜態陣列的元素,或者
解壓縮動態陣列的儲存於空間。

LEA EAX,DWORD PTR SS:][EBP-18] ;陣列變數的位址
PUSH EAX
PUSH EDI ;0
CALL DWORD PTR DS:[<&MSVBVM60.__vbaErase>] ;函數Erase


下面編寫一段簡單的程式碼分析一下:
Dim a() As Integer
ReDim a(2 To 5)
a(2) = &HAA
Erase a

Dim b(1 To 3) As Integer
b(1) = &H55
Erase b

反彙編程式碼如下:
00401A4F PUSH 2
00401A51 LEA EAX,DWORD PTR SS:[EBP-30]
00401A54 XOR EDI,EDI
00401A56 PUSH 工程1.004016B8
00401A5B PUSH EAX
00401A5C MOV DWORD PTR SS:[EBP-18],EDI
00401A5F MOV DWORD PTR SS:[EBP-38],EDI
00401A62 CALL DWORD PTR DS:[<&MSVBVM60.__vbaAryConstruct2>]

/////////////////////////////////////上面這段是 Dim b(1 To 3) As Integer

00401A68 PUSH 2
00401A6A PUSH 5
00401A6C PUSH 1
00401A6E LEA ECX,DWORD PTR SS:[EBP-18]
00401A71 PUSH 2
00401A73 PUSH ECX
00401A74 PUSH 2
00401A76 PUSH 80
00401A7B CALL DWORD PTR DS:[<&MSVBVM60.__vbaRedim>]

//////////////////////////////////////上面這段是 Dim a() As Integer
////////////////////////////////////// ReDim a(2 To 5)

00401A81 MOV ECX,DWORD PTR SS:[EBP-18]
00401A84 ADD ESP,1C
00401A87 CMP ECX,EDI
00401A89 JE SHORT 工程1.00401AB0
00401A8B CMP WORD PTR DS:[ECX],1
00401A8F JNZ SHORT 工程1.00401AB0
00401A91 MOV EDX,DWORD PTR DS:[ECX+14] //取出上標
00401A94 MOV EAX,DWORD PTR DS:[ECX+10]
00401A97 MOV ESI,2
00401A9C SUB ESI,EDX
00401A9E CMP ESI,EAX
00401AA0 JB SHORT 工程1.00401AAB
00401AA2 CALL DWORD PTR DS:[<&MSVBVM60.__vbaGenerateBoundsError>]
00401AA8 MOV ECX,DWORD PTR SS:[EBP-18]
00401AAB LEA EAX,DWORD PTR DS:[ESI+ESI] //乘以2,即整數所佔儲存於空間
00401AAE JMP SHORT 工程1.00401AB9
00401AB0 CALL DWORD PTR DS:[<&MSVBVM60.__vbaGenerateBoundsError>]
00401AB6 MOV ECX,DWORD PTR SS:[EBP-18]

////////////////////////////////////////上面這段包含了對產生的動態陣列的檢驗

00401AB9 MOV EDX,DWORD PTR DS:[ECX+C] //取出變數a的值位址
00401ABC MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaErase>]
00401AC2 MOV WORD PTR DS:[EDX+EAX],0AA //對第一個元素賦值

////////////////////////////////////////到這裡是 a(2) = &HAA

00401AC8 LEA EAX,DWORD PTR SS:[EBP-18]
00401ACB PUSH EAX
00401ACC PUSH EDI
00401ACD CALL ESI

////////////////////////////////////////到這裡是 Erase a

00401ACF MOV ECX,DWORD PTR SS:[EBP-24]//取得變數b的值位址
00401AD2 LEA EAX,DWORD PTR SS:[EBP-38]
00401AD5 LEA EDX,DWORD PTR SS:[EBP-30]
00401AD8 PUSH EAX
00401AD9 MOV WORD PTR DS:[ECX],55

////////////////////////////////////////到這裡是 b(1) = &H55
00401ADE PUSH EDI
00401ADF MOV DWORD PTR SS:[EBP-38],EDX
00401AE2 CALL ESI

////////////////////////////////////////到這裡是 Erase b

00401AE4 MOV DWORD PTR SS:[EBP-4],EDI
00401AE7 PUSH 工程1.00401B09
00401AEC MOV ESI,DWORD PTR DS:[<&MSVBVM60.__vbaAryDestruct>]
00401AF2 LEA ECX,DWORD PTR SS:[EBP-18]
00401AF5 XOR EDI,EDI
00401AF7 PUSH ECX
00401AF8 PUSH EDI
00401AF9 CALL ESI //釋a

00401AFB LEA EAX,DWORD PTR SS:[EBP-38]
00401AFE LEA EDX,DWORD PTR SS:[EBP-30]
00401B01 PUSH EAX
00401B02 PUSH EDI
00401B03 MOV DWORD PTR SS:[EBP-38],EDX
00401B06 CALL ESI //解壓縮b

關於陣列就先分析這些,後面還要分析的是 For Each .. Next語句和 Array函數,
不過這兩個都比較複雜些,先放到後面去。


3)結構檔案類型的實現

VB的記錄變數其實就是一些子域的順序排列。

這句話怎麼理解呢?看看下面的程式碼:

Private Type daterec
year As Integer
month As String * 3
day As Integer
End Type

Dim a As daterec

a.year = 2004
a.month = "Jan"
a.day = 21

反彙編程式碼如下:
004019DF XOR EAX,EAX
004019E1 LEA ECX,DWORD PTR SS:[EBP-1E]
004019E4 MOV DWORD PTR SS:[EBP-20],EAX
004019E7 PUSH 工程1.004014CC //"Jan"
004019EC MOV DWORD PTR SS:[EBP-1C],EAX
004019EF PUSH ECX
004019F0 PUSH 3
004019F2 MOV WORD PTR SS:[EBP-18],AX
004019F6 MOV WORD PTR SS:[EBP-20],7D4 // a.year = 2004
004019FC CALL DWORD PTR DS:[<&MSVBVM60.__vbaLsetFixstr>]//a.month = "Jan"
00401A02 MOV WORD PTR SS:[EBP-18],15 // a.day = 15

執行到這裡時看記憶體[ebp-20]:
0063F3F0 D4 07 4A 00 61 00 6E 00 ?J.a.n.
0063F3F8 15 00 6F 17 F4 F8 63 00 .o豇c.

從 0063F3F0 到 0063F3F9 就是記錄變數 a 的值了。
3. VB6的控制結構

下面要提到的vb的控制結構語句有:if語句,select case語句,while語句,do語句,
for語句。

1)if語句

if語句的典型語法是:

If 條件1 Then
語句塊1
ElseIf 條件2 Then
語句塊2
Else
語句塊3
End If
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
程式碼:
Dim a //注意:這裡定義的是變體變數。
//如果不用變體變數,編譯器將最佳化掉太多的程式碼。
//當你熟悉變體檔案類型後,其他的將很容易分析 ^_^
a = 5

If a < 5 Then
MsgBox ("a < 5")
ElseIf a = 5 Then
MsgBox ("a = 5")
Else
MsgBox ("a > 5")
End If
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
反彙編程式碼:
00401A72 XOR ESI,ESI
00401A74 MOV EDI,5
00401A79 MOV DWORD PTR SS:[EBP-74],ESI
00401A7C LEA EDX,DWORD PTR SS:[EBP-74]
00401A7F LEA ECX,DWORD PTR SS:[EBP-24]
00401A82 MOV DWORD PTR SS:[EBP-24],ESI
00401A85 MOV DWORD PTR SS:[EBP-34],ESI ----> 這些變數是為 MsgBox 使用的
00401A88 MOV DWORD PTR SS:[EBP-44],ESI |
00401A8B MOV DWORD PTR SS:[EBP-54],ESI |
00401A8E MOV DWORD PTR SS:[EBP-64],ESI ---
00401A91 MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401A94 MOV DWORD PTR SS:[EBP-74],2 //整數檔案類型
00401A9B CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarMove>]//賦值
//到這裡 a = 5 //[ebp-24]
00401AA1 LEA EAX,DWORD PTR SS:[EBP-24]
00401AA4 LEA ECX,DWORD PTR SS:[EBP-74]
00401AA7 MOV EBX,8002
00401AAC PUSH EAX
00401AAD PUSH ECX
00401AAE MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401AB1 MOV DWORD PTR SS:[EBP-74],EBX // 8002
00401AB4 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstLt>] // a < 5 ?
00401ABA TEST AX,AX
00401ABD JE SHORT 工程2.00401AE4 //不小於則跳走

00401ABF MOV ECX,80020004
00401AC4 MOV EAX,0A
00401AC9 MOV DWORD PTR SS:[EBP-5C],ECX
00401ACC MOV DWORD PTR SS:[EBP-64],EAX
00401ACF MOV DWORD PTR SS:[EBP-4C],ECX
00401AD2 MOV DWORD PTR SS:[EBP-54],EAX
00401AD5 MOV DWORD PTR SS:[EBP-3C],ECX
00401AD8 MOV DWORD PTR SS:[EBP-44],EAX
00401ADB MOV DWORD PTR SS:[EBP-6C],工程2.004016C4; UNICODE "a < 5"
00401AE2 JMP SHORT 工程2.00401B63

00401AE4 LEA ECX,DWORD PTR SS:[EBP-24]
00401AE7 LEA EDX,DWORD PTR SS:[EBP-74]
00401AEA PUSH ECX
00401AEB PUSH EDX
00401AEC MOV DWORD PTR SS:[EBP-6C],EDI // 5
00401AEF MOV DWORD PTR SS:[EBP-74],EBX // 8002
00401AF2 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarTstEq>]// a = 5 ?
00401AF8 TEST AX,AX
00401AFB MOV ECX,80020004
00401B00 MOV EAX,0A
00401B05 MOV DWORD PTR SS:[EBP-5C],ECX
00401B08 MOV DWORD PTR SS:[EBP-64],EAX
00401B0B MOV DWORD PTR SS:[EBP-4C],ECX
00401B0E MOV DWORD PTR SS:[EBP-54],EAX
00401B11 MOV DWORD PTR SS:[EBP-3C],ECX
00401B14 MOV DWORD PTR SS:[EBP-44],EAX
00401B17 JE SHORT 工程2.00401B5C //不相等則跳走

00401B19 LEA EDX,DWORD PTR SS:[EBP-74]
00401B1C LEA ECX,DWORD PTR SS:[EBP-34]
00401B1F MOV DWORD PTR SS:[EBP-6C],工程2.004016D4; UNICODE "a = 5"
00401B26 MOV DWORD PTR SS:[EBP-74],8
00401B2D CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarDup>]
00401B33 LEA EAX,DWORD PTR SS:[EBP-64]
00401B36 LEA ECX,DWORD PTR SS:[EBP-54]
00401B39 PUSH EAX
00401B3A LEA EDX,DWORD PTR SS:[EBP-44]
00401B3D PUSH ECX
00401B3E PUSH EDX
00401B3F LEA EAX,DWORD PTR SS:[EBP-34]
00401B42 PUSH ESI
00401B43 PUSH EAX
00401B44 CALL DWORD PTR DS:[<&MSVBVM60.#595>]; MSVBVM60.rtcMsgBox
00401B4A LEA ECX,DWORD PTR SS:[EBP-64]
00401B4D LEA EDX,DWORD PTR SS:[EBP-54]
00401B50 PUSH ECX
00401B51 LEA EAX,DWORD PTR SS:[EBP-44]
00401B54 PUSH EDX
00401B55 LEA ECX,DWORD PTR SS:[EBP-34]
00401B58 PUSH EAX
00401B59 PUSH ECX
00401B5A JMP SHORT 工程2.00401B9D
// 其他條件滿足則執行這裡
00401B5C MOV DWORD PTR SS:[EBP-6C],工程2.004016E4; UNICODE "a > 5"
00401B63 LEA EDX,DWORD PTR SS:[EBP-74]
00401B66 LEA ECX,DWORD PTR SS:[EBP-34]
00401B69 MOV DWORD PTR SS:[EBP-74],8
00401B70 CALL DWORD PTR DS:[<&MSVBVM60.__vbaVarDup>]
00401B76 LEA EDX,DWORD PTR SS:[EBP-64]
00401B79 LEA EAX,DWORD PTR SS:[EBP-54]
00401B7C PUSH EDX
00401B7D LEA ECX,DWORD PTR SS:[EBP-44]
00401B80 PUSH EAX
00401B81 PUSH ECX
00401B82 LEA EDX,DWORD PTR SS:[EBP-34]
00401B85 PUSH ESI
00401B86 PUSH EDX
00401B87 CALL DWORD PTR DS:[<&MSVBVM60.#595>]; MSVBVM60.rtcMsgBox

流程大概如此。上面出現了一些VB內部函數,如果你不太熟悉那些函數的意義,可以
到DFCG上下載一份我整理的vb內部函數。
===========================================================
__________________
http://bbsimg.qianlong.com/upload/01/08/29/68/1082968_1136014649812.gif
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次