一個速度非常快的Java編譯器(Symantec Java Compiler)sj.exe的改進 (9千字)

看雪資料發表於2015-11-15

這個編譯器是Symantec Cafe 4.0帶的,編譯速度非常快,是javac.exe的100到1000倍,是jikes.exe的4-6倍。不過由於Symantec Cafe不再發展,所以這個編譯器也不再更新,有一下問題需要改進。

sj.exe不支援JDK1.4以上,它會檢查rt.jar中的類的版本,如下:

00415EF4   .  83FB 2D       CMP     EBX, 2D
00415EF7      74 29         JE      SHORT sj.00415F22

改成如下就可以跳過程式碼的檢查:
00415EF4   .  83FB 2D       CMP     EBX, 2D
00415EF7   .  EB 29         JMP     SHORT sj11.00415F22

如果使用wjcompiler.dll,則修改如下地方
21EA4F53:7F 64->90 90

sj.exe在編譯檔案的時候,如果Classpath中包含的jar檔案太大,如
Weblogic 7.0的jar檔案,就會出現記憶體訪問異常,經檢查在下面的
程式碼中出現異常:

004A00C8  /$  89CA          MOV     EDXECX
004A00CA  |.  8B4424 04     MOV     EAX, [ESP+4]
004A00CE  |.  53            PUSH    EBX
004A00CF  |.  57            PUSH    EDI
004A00D0  |.  56            PUSH    ESI
004A00D1  |.  83C0 03       ADD     EAX, 3
004A00D4  |.  83E0 FC       AND     EAX, FFFFFFFC
004A00D7  |.  75 05         JNZ     SHORT sj11.004A00DE
004A00D9  |.  B8 04000000   MOV     EAX, 4
004A00DE  |>  89C6          MOV     ESIEAX
004A00E0  |.  F7D8          NEG     EAX
004A00E2  |.  01E0          ADD     EAXESP
004A00E4  |.  73 28         JNB     SHORT sj11.004A010E
004A00E6  |.  89C1          MOV     ECXEAX
004A00E8  |.  89F3          MOV     EBXESI
004A00EA  |>  851C19        /TEST    [ECX+EBX], EBX
004A00ED  |.  81EB 00100000 |SUB     EBX, 1000
004A00F3  |.^ 73 F5         \JNB     SHORT sj11.004A00EA
004A00F5  |.  8519          TEST    [ECX], EBX
004A00F7  |.  89E9          MOV     ECXEBP
004A00F9  |.  29E1          SUB     ECXESP
004A00FB  |.  2B0A          SUB     ECX, [EDX]
004A00FD  |.  0132          ADD     [EDX], ESI ;異常
004A00FF  |.  89C4          MOV     ESPEAX
004A0101  |.  01C8          ADD     EAXECX
004A0103  |.  89E7          MOV     EDIESP
004A0105  |.  01E6          ADD     ESIESP
004A0107  |.  C1E9 02       SHR     ECX, 2
004A010A  |.  F3:A5         REP     MOVSD
004A010C  |.  EB 02         JMP     SHORT sj11.004A0110
004A010E  |>  31C0          XOR     EAXEAX
004A0110  |>  5E            POP     ESI                                kernel32.77E7EB69
004A0111  |.  5F            POP     EDI                                kernel32.77E7EB69
004A0112  |.  5B            POP     EBX                                kernel32.77E7EB69
004A0113  \.  C3            RETN

經過分析,這段程式碼是類似在棧中分配記憶體,由於迴圈過多,最後導致在還沒有
分配記憶體的棧中訪問而導致異常,用以下方法可以解決這個問題,即透過在堆中
分配記憶體,這樣會浪費一些記憶體,但是既然是編譯器,執行完成後就退出,這些
記憶體馬上能夠被回收。

004A00C8  /$  FF7424 04     PUSH    DWORD PTR [ESP+4]                 /MemSize = 540000 (5505024.)
004A00CC  |.  6A 00         PUSH    0                                 |Flags = GMEM_FIXED
004A00CE  |.  E8 5FF80000   CALL           \GlobalAlloc
004A00D3  \.  C3            RETN


在進行對UTF-8編碼方式的原始檔進行編譯的時候,不能成功。UTF-8的編碼方式
透過sj -j .65001的方式來編譯,其中65001是Windows中的CP_UTF8。透過檢查
程式碼發現sj使用GetCPInof來獲取編碼的最大位元組數,而在Windows中,CP_UTF8
編碼返回的最大位元組數是4,而實際應當是3,因此需要把這裡修改掉,原始碼如下:
004A351D   |.  51              PUSH    ECX                               /pCPInfo = 0012F434
004A351E   |.  52              PUSH    EDX                               |CodePage = FDE9
004A351F   |.  FF15 88284000   CALL    [<&KERNEL32.GetCPInfo>]           \GetCPInfo
004A3525   |.  85C0            TEST    EAXEAX
004A3527   |.  0F84 79010000   JE      sj111.004A36A6
004A352D   |.  66:8B4C24 1C    MOV     CX, [ESP+1C]
004A3532   |.  68 02020000     PUSH    202
004A3537   |.  81E1 FFFF0000   AND     ECX, 0FFFF
004A353D   |.  890D 2C144D00   MOV     [4D142C], ECX ; 儲存最大位元組數

修改後程式碼為:
004A351D   |.  51              PUSH    ECX                               /pCPInfo = 0012F434
004A351E   |.  52              PUSH    EDX                               |CodePage = FDE9
004A351F   |.  FF15 88284000   CALL    [<&KERNEL32.GetCPInfo>]           \GetCPInfo
004A3525   |.  85C0            TEST    EAXEAX
004A3527   |.  0F84 79010000   JE      sj111.004A36A6
004A352D   |.  66:8B4C24 1C    MOV     CX, [ESP+1C]
004A3532   |.  68 02020000     PUSH    202
004A3537   |.  81E1 FFFF0000   AND     ECX, 0FFFF
004A353D       E9 6EC50000     JMP     sj111.004AFAB0 ; 修改後的程式碼
004A3542       90              NOP

以下程式碼判斷編碼方式是否為65001(0xFDE9),如果是就將最大位元組數改成3。
004AFAB0       60              PUSHAD
004AFAB1       A1 30144D00     MOV     EAX, [4D1430] ; CodePage
004AFAB6       3D E9FD0000     CMP     EAX, 0FDE9
004AFABB       75 0C           JNZ     SHORT sj111.004AFAC9
004AFABD       C705 2C144D00 0>MOV     DWORD PTR [4D142C], 3 ; MaxBytes
004AFAC7       EB 06           JMP     SHORT sj111.004AFACF
004AFAC9       890D 2C144D00   MOV     [4D142C], ECX
004AFACF       61              POPAD
004AFAD0     ^ E9 6D3AFFFF     JMP     sj111.004A3542

然後在進行編碼轉換的時候需要去掉一個判斷,原始碼如下:
004A4810   |.  19ED            SBB     EBPEBP
004A4812   |.  31C9            XOR     ECXECX
004A4814   |.  8A0E            MOV     CL, [ESI]
004A4816   |.  45              INC     EBP
004A4817   |.  F6444A 01 80    TEST    BYTE PTR [EDX+ECX*2+1], 80 ; 判斷是否單位元組
004A481C   |.  74 3B           JE      SHORT sj111.004A4859
004A481E   |.  8B0D 2C144D00   MOV     ECX, [4D142C]
004A4824   |.  83F9 02         CMP     ECX, 2
004A4827   |.  7C 59           JL      SHORT sj111.004A4882
004A4829   |.  8B15 2C144D00   MOV     EDX, [4D142C]
004A482F   |.  3B5424 1C       CMP     EDX, [ESP+1C]                      sj111.004D145C
004A4833   |.  77 4D           JA      SHORT sj111.004A4882
004A4835   |.  55              PUSH    EBP                               /WideBufSize = 394660 (3753568.)
004A4836   |.  53              PUSH    EBX                               |WideCharBuf = 00000002
004A4837   |.  51              PUSH    ECX                               |StringSize = 12F434 (1242164.)
004A4838   |.  8B0D D8474D00   MOV     ECX, [4D47D8]                     |
004A483E   |.  8B15 30144D00   MOV     EDX, [4D1430]                     |
004A4844   |.  56              PUSH    ESI                               |StringToMap = NULL
004A4845   |.  51              PUSH    ECX                               |Options = 0
004A4846   |.  52              PUSH    EDX                               |CodePage = FDE9
004A4847   |.  FF15 74284000   CALL    [<&KERNEL32.MultiByteToWideChar>>; \MultiByteToWideChar
004A484D   |.  85C0            TEST    EAXEAX

可以將004A481C的程式碼NOP掉,所有的編碼都以多位元組來處理。
還有在進行轉換的時候需要將Options設定為0,這是個全域性變數,在4D47D8。

在將UNICODE轉換成UTF-8的時候,有一段程式碼需要修改,原始碼如下:
0049FFA1   |.  51              PUSH    ECX                          /pDefaultCharUsed = 00145B87
0049FFA2   |.  56              PUSH    ESI                          |pDefaultChar = NULL
0049FFA3   |.  52              PUSH    EDX                          |MultiByteCount = 7FFA0002 (2147090434.)
0049FFA4   |.  55              PUSH    EBP                          |MultiByteStr = 00145B80
0049FFA5   |.  6A FF           PUSH    -1                           |WideCharCount = FFFFFFFF (-1.)
0049FFA7   |.  8B3D 30144D00   MOV     EDI, [4D1430]                |
0049FFAD   |.  8B5C24 34       MOV     EBX, [ESP+34]                |sj11.004BEEB5
0049FFB1   |.  53              PUSH    EBX                          |WideCharStr = "tt.java"
0049FFB2   |.  68 20020000     PUSH    220                          |Options = WC_COMPOSITECHECK|WC_SEPCHARS
0049FFB7   |.  57              PUSH    EDI                          |CodePage = CP_ACP
0049FFB8   |.  FF15 CC284000   CALL    [<&KERNEL32.WideCharToMulti>; \WideCharToMultiByte

需要將0049FFA7的CodePage修改成CP_ACP,修改後程式碼如下:
0049FFA1   |.  51              PUSH    ECX                          /pDefaultCharUsed = 00145B87
0049FFA2   |.  56              PUSH    ESI                          |pDefaultChar = NULL
0049FFA3   |.  52              PUSH    EDX                          |MultiByteCount = 7FFA0002 (2147090434.)
0049FFA4   |.  55              PUSH    EBP                          |MultiByteStr = 00145B80
0049FFA5   |.  6A FF           PUSH    -1                           |WideCharCount = FFFFFFFF (-1.)
0049FFA7       E9 34FB0000     JMP     sj11.004AFAE0
0049FFAC       90              NOP
0049FFAD   |.  8B5C24 34       MOV     EBX, [ESP+34]                |sj11.004BEEB5
0049FFB1   |.  53              PUSH    EBX                          |WideCharStr = "tt.java"
0049FFB2   |.  68 20020000     PUSH    220                          |Options = WC_COMPOSITECHECK|WC_SEPCHARS
0049FFB7   |.  57              PUSH    EDI                          |CodePage = CP_ACP
0049FFB8   |.  FF15 CC284000   CALL    [<&KERNEL32.WideCharToMulti>; \WideCharToMultiByte

以下程式碼判斷編碼方式是否為65001(0xFDE9),如果是就將CodePage設定為CP_ACP
004AFAE0       60              PUSHAD
004AFAE1       A1 30144D00     MOV     EAX, [4D1430]
004AFAE6       3D E9FD0000     CMP     EAX, 0FDE9
004AFAEB       75 05           JNZ     SHORT sj11.004AFAF2
004AFAED       61              POPAD
004AFAEE       33FF            XOR     EDIEDI
004AFAF0       EB 07           JMP     SHORT sj11.004AFAF9
004AFAF2       61              POPAD
004AFAF3       8B3D 30144D00   MOV     EDI, [4D1430]
004AFAF9     ^ E9 AF04FFFF     JMP     sj11.0049FFAD

相關文章