返回信息流今天楼主逛C++板块无意间发现一个帖子。原文:http://bbs.byr.cn/#!article/CPP/73722,瞬间产生了好奇,特别特别想知道原因啊(好奇害死猫啊),于是开工了!
实验环境:
操作系统:VM 8.0 +winxp sp3
所用软件:winhex,VC++ 6.0, ollydbg
首先,这个程序从代码上看没有任何问题。接着按照原文楼主描述生成一份乱码的文件data.txt。在guihai回复的指导下,我用winhex观察了下有问题的txt,发现如他所言十六进制的数据完全正常,那么不是编译器的事,应该是notepad的问题了。那么会是什么问题呢?十六进制数据没出错,但是显示乱码,一般情况下就是解码出了问题。先简单试验下,直接打开notepad.exe,文件->打开。在对话框中,选中有问题的文件data.txt,在下面“编码”一栏选择“ANSI”,也就是众所周知的ASCII啦,这时打开,楼主发现熟悉的数字又出现了,很神奇有木有!要是“编码”一栏选择“Unicode”,乱码乱入有木有!问题确认了,notepad把本应该用ASCII方式打开的文件,用Unicode方式打开了。于是楼主上网找啊找,看有没有大牛解释这个原因。找了半天没找到,但是楼主获得了一些信息。Notepad解析txt是按照文件头几个字节来确定文件的编码方式,0xFFFE是Unicode,0xFEFF是Unicode big endian,0xEFBBBF是UTF-8(重要哦,后面要用到)。那么如果三种都不是的话,岂不是是ASCII?没有文档记载,楼主不敢乱下定论,因为从程序生成的正常文件和乱码文件来看,他们的头两个字节都是0x3130,但是一个使用ASCII打开,另一个却是用Unicode打开。楼主好好奇啊,为什么notepad这么神奇呢?(好奇害死猫啊!)没办法,楼主只能自己动手,事实证明楼主风衣足食啦!
打开ollydbg,找到notepad,路径是C:\WINDOWS\system32。加载进来,然后对函数ReadFile下断点。F9把记事本程序运行起来,文件->打开,在对话框中选择乱码文件data.txt,这时鼠标刚一点中data.txt,notepad就被断了下来,停在地址01002581处。
0100256F |. 56 push esi ; /pOverlapped => NULL
01002570 |. 8D45 14 lea eax, dword ptr [ebp+14] ; |
01002573 |. 50 push eax ; |pBytesRead
01002574 |. 68 00040000 push 400 ; |BytesToRead = 400 (1024.)
01002579 |. 8D85 F0F9FFFF lea eax, dword ptr [ebp-610] ; |
0100257F |. 50 push eax ; |Buffer
01002580 |. 53 push ebx ; |hFile
01002581 |. FF15 14110001 call dword ptr [<&KERNEL32.ReadFile>; \ReadFile
01002587 |. 85C0 test eax, eax
01002589 |. 7E 62 jle short 010025ED
0100258B |. 3975 14 cmp dword ptr [ebp+14], esi
0100258E |. 74 5D je short 010025ED
01002590 |. FF75 14 push dword ptr [ebp+14]
01002593 |. 8D85 F0F9FFFF lea eax, dword ptr [ebp-610]
01002599 |. 50 push eax ; 这里发现文件内容
0100259A |. E8 78FDFFFF call 01002317 ; 跟进
然后F8步过,当运行到01002599时发现eax寄存器指向一个字符串,而该字符串的内容就是我们可爱的文件内容,还是正常的数字哦。接着跟进0100259A处的函数。来到函数处。详情就看注释了。
01002317 /$ 57 push edi ; notepad.01008848
01002318 |. 8B7C24 0C mov edi, dword ptr [esp+C]
0100231C |. 33C0 xor eax, eax
0100231E |. 83FF 01 cmp edi, 1
01002321 |. 76 53 jbe short 01002376
01002323 |. 56 push esi
01002324 |. 8B7424 0C mov esi, dword ptr [esp+C]
01002328 |. 0FB70E movzx ecx, word ptr [esi] ;取文件内容头两个字节放入
; ecx,这里为0x3031
0100232B |. 81F9 EFBB0000 cmp ecx, 0BBEF ; UTF-8; Switch (cases BBEF..FFFE)
01002331 |. 74 34 je short 01002367
01002333 |. 81F9 FFFE0000 cmp ecx, 0FEFF ; Unicode
01002339 |. 74 13 je short 0100234E
0100233B |. 81F9 FEFF0000 cmp ecx, 0FFFE ; Unicode big
01002341 |. 74 20 je short 01002363
01002343 |. 57 push edi ; 三种打开方式都不是,采
;用default; Default case of switch
;0100232B
01002344 |. 56 push esi
01002345 |. E8 70460000 call 010069BA ; 跟进
从地址0100232B开始的三个比较就是确定用哪种编码方式打开。(这里注意下Intel芯片采用的是Little-Endian编码方式,就能明白FEFF就是Unicode方式)当然,我们的0x3031只能执行default了,跟进01002345的call。我们来到如下地方
010069BA /$ 55 push ebp
010069BB |. 8BEC mov ebp, esp
010069BD |. 51 push ecx ; 文件头两个字节,编码标志
010069BE |. 834D FC FF or dword ptr [ebp-4], FFFFFFFF
010069C2 |. 8D45 FC lea eax, dword ptr [ebp-4]
010069C5 |. 50 push eax ;以下三个为函数参数
010069C6 |. FF75 0C push dword ptr [ebp+C]
010069C9 |. FF75 08 push dword ptr [ebp+8]
010069CC |. FF15 0C100001 call dword ptr [<&ADVAPI32.IsTextUnic>; ADVAPI32.IsTextUnicode
010069D2 |. C9 leave
010069D3 \. C2 0800 retn 8
这时一眼就看到地址010069cc调用了一个API,名字为IsTextUnicode()。楼主立马去查找了下这个函数的用途。摘了重要的给大家看,完整的请大家动手查查看,加深下映像。IsTextUnicode函数使用一系列统计和决策方法来猜测缓冲区中的内容。IsTextUnicode函数使用一系列统计和决策方法来猜测缓冲区中的内容,由于这并非一种精确的科学,IsTextUnicode函数可能会返回错误的结果。如果IsTextUnicode函数认为缓冲区包含的是Unicode文本,就会返回TRUE;反之返回FALSE。到这里,相信大家都明白了吧。
打完收工!
PS:弄清楚后,才觉得原帖楼主的程序居然产生了这样的巧合,真的是好神奇啊!太巧了!至于IsTextUnicode()怎么实现的,楼主跟进后发现调用了另一个API,RtlIsTextUnicode,这两个函数参数一样,到此楼主就没继续了。算法本来就痛苦,何况还是读汇编。
祝大家中秋快乐!
这是一条镜像帖。来源:北邮人论坛 / cpp / #73759同步于 2013/9/18
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
[心得]关于《往文件写数字老是出问题》的研究
gdl
2013/9/18镜像同步12 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复