Board logo

标题: [讨论]cmd 究竟是基于 Ansi 还是 Unicode 的? [打印本页]

作者: CrLf    时间: 2011-10-24 17:56     标题: [讨论]cmd 究竟是基于 Ansi 还是 Unicode 的?

本帖最后由 CrLf 于 2011-10-24 20:25 编辑

闲聊时好几次听寒夜版主说过,cmd 内部是以 Unicode 编码来“暗箱操作”的,但是一直在腹诽这到底是推论还是猜测...
不过到至今也没有找到有力的反例,倒是收集了几个间接支持该论点的证据:

【1,在 for 中,参数变量名以 Unicode 排列】
这是 plp626 在某个关于 for 参数的讨论帖中给出的示范代码:
  1. :: cmd命令行下粘贴如下代码
  2. :: "老" 到 "耣" 的unicode编码(连续地)为 "8001" 到 "8023"
  3. set ss=1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
  4. for /f "tokens=1-31" %老 in ("%ss%")do @echo %老 %耂 %考 %耄 %者 %耆 %耇 %耈 %耉 %耊 %耋 %而 %耍 %耎 %耏 %耐 %耑 %耒 %耓 %耔 %耕 %耖 %耗 %耘 %耙 %耚 %耛 %耜 %耝 %耞 %耟 %耠 %耡 %耢 %耣
复制代码
很有意思,将 老~耣 这 23 个字分别保存为 Ansi 与 Unicode 格式,可以看到 Ansi 编码中这些字的十六进制是无序的排列,而 Unicode 编码中则是顺序排列的,这说明至少在 for 命令中,参数变量名是以 Unicode 编码顺序排序。

【2,个人猜测,变量有可能是以 Unicode 存储】
第二个证据要说的还是 plp 的帖子,不过是关于 chcp 切分字符的另一帖,该贴的核心内容是通过 chcp 437 切换到英文代码页,可以使原本不可分的宽字节可以被拆分为两个字符存储和输出。
有意思的是,chcp 之前的宽字节不受影响,已经存储在变量中的宽字节是不会被拆分的。我一直无法理解,就算能把宽字节拆开,又如何在存储时告诉 cmd 这两个字节不是一组的呢?虽然 chcp 时字节会按照 nls 表发生变化,但我们知道 Ansi(其实是 GBK)中的宽字节由 0x80~0xff 范围内的字符组成,如何让 cmd 区别对待谁是宽字符、谁是单字节字符、谁又是被拆分成两个字符的宽字符呢?
举个例子,比如骚包这个两个字同时出现的时候,就一定是“骚包”的意思吗?我要是造个句子“离骚包含屈原晚年的苦闷与矛盾”你该如何解读呢?宽字节就像语言中的固定用词,平时一组一组出现,但是特殊情况下,“骚包”不一定是骚包,他也可能是骚和包,你又怎么知道它是骚包还是骚和包呢...
我曾经揣测了好几种可能,比如在相邻字符之间安置分隔符,或者为不同代码页安排不同的变量表,但是前者无法解释 cmd 用什么符号来作为分隔符,因为除 00 是变量与变量之间的分隔符外,所有符号都能被保存为变量内容,那就没有符号能够用来作为字符之间的分隔符了。而第二个猜测则是在寒夜用 debug 查看变量表时不攻自破,因为同一时间段能观察到的活动的变量表唯一。
现在回想起来,似乎还有另一种可能,cmd 保存变量时或许是用 Unicode 编码存储的,因为在 Unicode 编码中所有字符都是双字节的,这就能解释 cmd 是如何区别对待宽字符、单字节字符、被拆分成两个字符的宽字符了——因为它们保存到变量时都是被统一转换为双字节,比如“骚”“包”被转换成“ 骚”“ 包”,而“骚包”依然是“骚包”,这样也就不存在被拆分的宽字符在变量中又合并的问题了。
我做了个实验,先在一个批处理中赋值了 8192*500 个几乎全是汉字的字符串,又在另一个批处理中赋值了 8192*500 个全是数字的字符串,观察二者内存占用都在 19 兆出头,相差仅 200k,假如 cmd 是以 ansi 编码存储变量,那数字占用的内存应该只有汉字的一半,Unicode 格式的字节数则始终是字符数的两倍(控制字符除外),所以这也能间接证明 cmd 有可能是以 Unicode 编码保存变量的。
我原以为此猜测有一个致命漏洞,就是变量内容中是不允许存在 00 的,但是 Unicode 编码必然无法避免 00 的存在,这不是自相矛盾吗?不过寒夜版主提醒我 Unicode 中的 00 是以 00 00 的形式成对存在的,所以就不担心变量内容中的 00 导致变量被错误分割的问题了。

【3、cmd /u 拒绝为外部命令提供 Ansi 转 Unicode 服务?】
我们知道 cmd /u 能输出 Unicode 格式的文本,这种输出不赠送文件头,所以一般只用于附加输出到已有文件头的文件。
但是有个有趣的现象,当我们使用 "cmd /u /c 大部分外部命令" 时是得不到 Unicode 格式输出的,而所有的内部命令以及少部分“原本就能将结果以 Unicode 格式输出”的外部命令(如 wmic、robocopy)则能够通过 cmd /u 输出为 Unicode 文本。
这是否说明,其实 cmd /u 并不是将 Ansi 输出转化为 Unicode 编码,而是 cmd 内部本来就是以 Unicode 进行操作,不使用 /u 开关时将转换为 Ansi 再输出,当我们使用了 cmd /u 时,cmd 只是不作为而已?

以上三条证据摆在一起,是否在暗示着 cmd 私底下好像还真挺有可能是用 Unicode 来干活的?
作者: raymai97    时间: 2011-10-24 18:02

我只知道每次储存批处理时都要选ansi,否则会出错
作者: lllsoslll    时间: 2011-10-24 20:16

windows 多国语言发行
应该是后者,
作者: garyng    时间: 2011-10-24 22:19

本帖最后由 garyng 于 2011-10-24 22:30 编辑

我觉得应该是ANSI吧~
如果是Unicode
那如果Unicode编码保存的脚本怎么不能执行?
也许CMD表面是ANSI而暗地里却用着Unicode?
作者: defanive    时间: 2011-10-25 02:06

查看cmd.exe的导入列表可以看到,导入kernel32.dll的API基本上全部都是W版
API Hook的结果,cmd设置变量值的时候调用的是SetEnvironmentVariableW,变量储存的结果当然也是Unicode,查看内存也可以看到,变量全部都是以Unicode储存的
但是cmd读取批处理文件的时候却是用ANSI处理的,API Hook的结果比较支持,而且内存中可以找到正在执行的代码,是以ANSI储存
作者: dnfreeuser    时间: 2011-10-25 08:15

主要看你说的内部是哪个内部。

cmd实现可能是ansi,但到系统内核的时候,都是unicode了
作者: Demon    时间: 2012-7-19 18:09

必然是Unicode,不用怀疑。

用OllyDbg载入,bp ReadFile后运行,就会断在读取脚本的地方。

[attach]5497[/attach]

可以看到每次读取0x1FFF(8191)个字节(注意不是字符)到缓冲区。

F8单步,不一会就到了MultiByteToWideChar函数,将刚才读取的字节转换成Unicode储存在另一个缓冲区,之后的处理都是建立在Unicode之上的。

[attach]5498[/attach]




欢迎光临 批处理之家 (http://www.bathome.net/) Powered by Discuz! 7.2