汉梦茫茫——汉化 vc 6.0 札记 By 竹闲(于2008-6-26发表)

  vc2005 支持 unicode,这意味着其 函数名、过程名、
变量名均已可使用汉字。然,大家对此漠然视之。
其实即令是对vc 6.0, 祗要些微修葺, 亦可支持汉字unicode,
改动如下:


㈠. vc 6.0 对c文件的编译前端模块为 c1.dll,
其自我说明为 "Visual C Compiler Front End",
版本号为 12.00.8168.0, 体积为 66'7697字节。

  在该二进制文件的 0x27a33 偏移处的0x0E個字节
为“80, 88 08 d1 65, 10 80 c6 80, 08 d0 65 10,23”
  ↑ ↑       ↑          ↑
(改为c6,80       65          6)

(更改理由见附录①),更改这四個字节後,
c文件的函数名、变量名、宏 在编译时 就均可使用
汉字了。

㈡. msdev在调试时的表达式求值模块是 eecxx.cll,其自
我说明为 "Expression Evaluator (ANSI C++)for Debug",
版本号为 6.00.8168.0, 体积为 18'4388字节。


  在该二进制文件的 0x14437
偏移处的 0x17個字节为“85, C0 59 75 1D, 8A 07 3C 5F
 74 17 3C 3F, 74 13 3C 24, 74 0F 3C 40, 74 0B”

(按照附录②, 应改为: “EB, 2C 74 1E 3C, 80 73 1A 3B
 7c 24 0C 75, 37 90 90 90, 90 90 90 90, 90 90”)

再将 0x1447a偏移处的 “74 dd”改为“EB BD”。
更改这0x19個字节後,就可在msdev的调试环境看到
 汉字函数名、汉字变量名。

㈢. 对于C++文件, 则较为繁琐:
首先, 将它的编译前端模块 c1xx.dll
(其自我说明为 "Visual C++ Compiler Front End",
版本号为 12.00.8168.0, 体积为 118'3795字节)
的 0x5869f 偏移处的0x0E個字节 改正。(更改理由同㈠)
 “80, 88 08 11 4d, 10 80 c6 80, 08 10 4d 10,23”
  ↑ ↑       ↑          ↑
(改为c6,80       65          6)
让cpp文件的函数名、变量名、宏 在编译时可使用汉字。


其次, 将 window的system目录下的运行时间库 msVCrt.dll
在 0xA8B4 偏移处(这是对 6.00.8397.0版 而言,
对6.10.9844.0版 则为 0x3B62)的0x43個字节
 原为       “8B 07 8A 10, 3A 55 0C 74, 3A 80 FA 5F
 74 2C 80 FA, 24 74 27 80, FA 61 7C 05, 80 FA 7A 7E
 1D 80 FA 41, 7C 05 80 FA, 5A 7E 13 80, FA 30 7C 05
                +----------------------------+
 80 FA 39 7E, 09|F6 05 XX, XX XX XX 01, 74 3A|43 40
                +----------------------------+
 89 07 80 38, 00 75 BD”(更改理由见附录③)


 改正为     “8A 01 89 0f, 3A 45 0C 74, 35 08 c0 74
 31 78 2B 3c, 24 74 27 3c, 5F 74 23 3c, 30 72 16 3c
 39 76 1B 3c, 41 72 0E 3c, 5A 76 13 3c, 61 72 06 3c
                +----------------------------+
 7A 76 0B 90, 90|F6 05 XX, XX XX XX 01, 74 3A|43 41
                +----------------------------+
 EB C2 90 90, 90 90 90”。

 让cpp文件生成obj 的 汉字标识符 不带“@@”、方便调试。
 注:方框所在处 内容 毋需改动,因与dll版本有关。

㈣. 改完了。若不涉及c++编程,祗改㈠㈡的0x1D個字节足矣。
 对我这类喜欢命令行编译的老家伙,光改㈠的四個字节也行。
 以下是 更改的原因 与 所涉的反汇编代码。
 各位 觉得厌烦的读贴人士, 请关闭本窗口吧。

——————————————————————————
附录:
① c1.dll在编译前,将 在1065d008处的《分枝枚举表》、
在1065d108处的《分枝指针表》 二表的後 0x80 字节
设置成了固定值, 相关代码为:
 10626a33: 808808d1651080    or  1065d108[eax],80h
 10626a3a: c68008d0651023    mov 1065d008[eax],23h
 10626a41: e9c7c3feff        jmp 10612e0d

 10612e0d: 40            inc eax
 10612e0e: 3d00010000        cmp eax,100h
 ...
 10612e07: 0f85263c0100        jnz 10626a33

最後得《分枝枚举表》如下:
1065d008:
 db  0 26 27 28, 29 21 21 21;    ascii(00~07)
 db 21  1  5  1,  1  1 21 21;    ascii(08~0f)
 db 0A dup(21);            ascii(10~19)
 db        0, 5 dup(21);    ascii(1A~1f)
 db  1 0A 16 0B,  6 0C 10 15;    ascii(20~27)
 db 14 1c  8 11, 1B 12  9  3;    ascii(28~2f)
 db 0A dup(1D);            ascii('0'~'9')
 db      1A 20, 13 0D 14  0;    ascii(3a~3f)
 db 22;                ascii(40)
 db    1A dup(6);        ascii('A'~'Z')
 db         1F,  0 19 0e, 6;    ascii(5b~5f)
 db 24;                ascii(60)
 db    1A dup(6);        ascii('a'~'z')
 db         1e, 0F 18 17 21;    ascii(7b~7f)
 db 21;                ascii(80)
 db 7e dup(23);            ascii(81~FE)
 db 21;                ascii(ff)

及《分枝指针表》如下:
1065d108:
 db 80 60 60 60, 60 60 60 60;    ascii(00~07)
 db 60 40 a0 40, 40 40 60 60;    ascii(08~0f)
 db 60 60 60 60, 60 60 60 60;    ascii(10~17)
 db 60 60 80 60, 60 60 60 60;    ascii(18~1f)
 db c0 60 e0 60, 61 60 60 e0;    ascii(20~27)
 db 60 60 80 60, 60 60 64 c0;    ascii(28~2f)
 db 8 dup(6e), 2 dup(66);    ascii('0'~'9')
 db       5 dup(60), A0;    ascii(3a~3f)
 db 60;                ascii(40)
 db 2 dup(67,77), 67,67,65,75;    ascii('A'~'G')
 db 75, 6 dup(65), 75;        ascii('H'~'O')
 db 65,75, 9 dup(65);        ascii('P'~'Z')
 db         60, 80 60 60 65;    ascii(5B~5F)
 db 60;                ascii(60)
 db 2 dup(67,77), 67,67,65,75;    ascii('a'~'g')
 db 75, 6 dup(65), 75;        ascii('h'~'o')
 db 65,75, 9 dup(65);        ascii('p'~'z')
 db 5 dup(60);            ascii(7B~7F)
 db 21;                ascii(80)
 db 7e dup(80);            ascii(81~FE)
 db 21;                ascii(ff)

c1.dll 在 fgetch(c文件) 时
于 10603033 处始对《分枝枚举表》作判断,
于 10603cfa 处始对《分枝指针表》作判断。

② eecxx.cll 在求标识符长度时的 strlen() 函数片段如下:
;[]--------------------------------------------[]
;| in:    edi=[esp+0Ch];    指向 str_标识符[0]    |
;|    al =[edi]                |
;|out:    edi ;        指向 str_标识符[strlen]    |
;[]--------------------------------------------[]
 525144'2D:0FB6C0        MOVZX    EAX,AL
 525144'30:50 FF1560105052    MSVCRT._ismbcalpha(EAX)

 525144'37:85C0            TEST    EAX,EAX
 525144'39:59            POP    ECX
 525144'3A:751D            JNZ    525144'59
 525144'3C:8A07            AL=[EDI]
 525144'3E:3C5F 7417        if(AL=='_')JZ 525144'59
 525144'42:3C3F 7413        if(AL=='?')JZ 525144'59
 525144'46:3C24 740F        if(AL=='$')JZ 525144'59
 525144'4A:3C40 740B        if(AL=='@')JZ 525144'59

 525144'4E:3C7E 7533        if(AL!='~')JNZ 525144'85
 525144'52:837C241401        if(DWORD [ESP+14]!=1)
 525144'57:752C                JNZ 525144'85
 525144'59:0FB64701 47        eax=by[edi++]
 525144'5E:50 FF15C4105052    MSVCRT._ismbcalnum(EAX)
 525144'65:85C0            TEST    EAX,EAX
 525144'67:59            POP    ECX
 525144'68:75EF            JNZ    525144'59
 525144'6A:8A07            AL=[EDI]
 525144'6C:3C5F 74E9        if(AL=='_')JZ 525144'59
 525144'70:3C3F 74E5        if(AL=='?')JZ 525144'59
 525144'74:3C24 74E1        if(AL=='$')JZ 525144'59
 525144'78:3C40            if(AL=='@')

 525144'7A:74DD            JZ 525144'59

  这段程序看到汉字时, 由于 _ismbcalpha(汉字)≡0、
_ismbcalnum(汉字)≡0、汉字与“_?$@”也毫无干系, 所以就
跳到程序出口、让edi指向汉字 而指不到真正字符串末的'\0',
导致调试时看不到 汉字函数名、汉字变量名。

  可利用 cs:[525144'37,525144'4D] 与 
cs:[525144'65,525144'7B] 代码冗余的破绽,
将前者改为
 525144'37:EB2C        JMP        525144'65
 525144'39:741E        JZ        525144'59
 525144'3B:3C80 731A    if(AL>=80h)JAE    525144'59
 525144'3F:3B7c240C    if(edi!=[esp+0Ch])
 525144'43:7537        JNZ    525144'7C
 525144'45:        9 dup (90h)

将後者改为:
 525144'7A:ebBD        JMP         525144'39

其中525144'3B处是判断汉字, 让程序莫要因之跳到出口。

③ c2.dll 在编译时, 调用 msVCrt.__unDName 。
而 msVCrt.cll 在求标识符长度时的 strlen() 函数片段
在 6.00.8397.0版本中 内容如下:
;[]--------------------------------------------[]
;| in:    ebx    = 0                |
;|    [edi]    = &(str[0])    ;该杀的 char**    |
;|    ecx    = [edi]        ; char *    |
;|    [EBP+0C]= '@'                |
;|                        |
;|out:    ebx    = strlen(str)            |
;|    [edi]    = &(str[strlen])        |
;[]--------------------------------------------[]
780098B4:8B07 8A10    do{    EAX=[EDI], DL=[EAX]
780098B8:3A550C 743A        if(dl==[EBP+0C])break;
780098BD:80FA5F 742C        if(DL=='_')JZ  normal;
780098C2:80FA24 7427        if(DL=='$')JZ  normal;
780098C7:80FA61 7C05        if(DL>='a'
780098CC:80FA7A 7E1D         &&DL<='z')JNG normal;
780098D1:80FA41 7C05        if(DL>='A'
780098D6:80FA5A 7E13         &&DL<='Z')JNG normal;
780098DB:80FA30 7C05        if(DL>='0'
780098E0:80FA39 7E09         &&DL<='9')JNG normal;

;        对 6.00.8397.0版,“标誌位”是 [7803B0BE]
;        对 6.10.9844.0版,“标誌位”是 [780403E2]
780098E5:F605XXXXXXXX01        if(!(BYTE [标誌位]&1))
780098EC:743A                JZ $+3Ch

780098EE:43        normal:    EBX++
780098EF:40 8907        [EDI]=++EAX
780098F2:803800 75BD       }while(BYTE [EAX])
               
  这段程序看到汉字时, 就“JZ $+3Ch”了,
导致 obj文件内不能正常包含 用户所见的标识符,
(注:cpp文件正常生成 带调试讯息的obj 时,
  将“用户看到的标识符”与“程序看到的标识符”分开存放。
  例如声明函数 void our_function(void)时,
   用户所见的标识符 就是 our_function
  而程序所见的标识符 就是 our_function@@YAXXZ。
  当该 strlen函数胡乱返回以後,cl找不到“@@”、未能合法
  截断“程序看到的标识符”来生成“用户看到的标识符”。)
最後在调试时看不到 汉字函数名、汉字变量名。

  这段程序写得比较差, 临时变量居然放在DL内,
改为放在al内可以节省代码空间、插入对汉字的判断,可改为
780098B4:8A01        do{    al=[ecx]
780098B6:890f            [EDI]=ECX
780098B8:3A450C 7435        if(al==[EBP+0C])break;
780098BD:08c0 7431        if(!AL)break;
780098C1:782B            if(80&AL)  JS   normal;
780098C3:3c24 7427        if(AL=='$')JZ   normal;
780098C7:3c5F 7423        if(AL=='_')JZ   normal;
780098CB:3c30 7216        if(AL>='0'
780098CF:3c39 761B         &&AL<='9')JBE  normal;
780098D3:3c41 720E        if(AL>='A'
780098D7:3c5A 7613         &&AL<='Z')JBE  normal;
780098DB:3c61 7206        if(AL>='a'
780098DF:3c7A 760B         &&AL<='z')JBE  normal;
780098E3:90 90
780098E5:F605XXXXXXXX01        if(!(BYTE [标誌位]&1))
780098EC 743A                JZ $+3Ch
780098EE:43        normal:    INC    EBX
780098EF:41            INC    ECX
780098F0:EBC2          }while(1);
780098F2:9090909090
 让汉字可以“JS normal”。

 那句“[EDI]=ECX”本来放在循环外为佳,
但已无隙可在“JZ $+3Ch”之前 修好[EDI],祗好作罢。
顺便鄙视一下微软:糟糕的程序代码(见上)、
龌龊的捆绑伎俩(居然将vc的运行时间库放在系统目录下)、
卑鄙的商业行为(故意在中国放纵window盗版)、
无耻的宣传口径(Gates迄今仍否认Kildall是DOS之父)
……

——————————————————————————
 骂完了外国之後, 我们自己呢?
HopeSoft 转营许久了, KnlSoft的 trw 早停了,
KingSoft的 wps 也要跟谷歌掺合了。
程序员自顾未暇、奔忙衣食,遑论推行汉化事业。
我们的民族软件啊……
哽声。无语。
 然,胸中块垒难平,不能无语。且藉亡人之故曲,
聊记一肚子牢骚。
 (双眼反白 望天,手指轻扣桌面 打拍子,口中喃喃自语、
未哼完已作吐血状):红尘里,美梦有千般方向。
痴痴梦幻的心爱,梦随人茫茫……

                 竹闲
                 2008.6.22

并不是所有的贴子都是原创,此时作者均指发表的人而不是文章的作者,作者会说明是否是转贴