前阵子看熊力的《WINDOWS用户态程序高效排错》的时候,在他BLOG(blogs.msdn.com/lixiong)里的用户反馈里看到的,据说是系统STRLEN的汇编代码
下面是熊力对这段代码的分析:
“这里对一个DWORD (EAX)的判断方法是:
1. 对EAX+0x7efefeff
2. 对EAX取反
3. 把1和2的结果作XOR,然后跟0x81010100h作test运算
研究了好久,理解如下:
问题的关键点在于,当且仅当EAX四个byte都不为0的时候,运算结果会是下面的pattern:
0??? ???0 ???? ???0 ???? ???0 ???? ????
分别解释如下:
如果第一个byte为0, 考虑第二个byte的最后一个bit。不管这个bit是0还是1,计算公式是:
(x+0) XOR (!x) =x xor !x=0
如果第一个byte不为0,肯定产生进位,考虑第二个byte的最后一个bit。不管这个bit是0还是1,计算公式是:
(x+1)XOR(!x)=!x xor !x=1
这就是上面0??? ???0 ???? ???0 ???? ???0 ???? ????第二个byte的第一个bit是0的来历
同理,第二,三,四个byte中的的第一个bit的0也是在前面所有的byte都不为0的时候才会出现,否则就会出现至少一个1
换句话说,上面的代码无法区分最高一个byte最高bit为0,其他bit为1的情况。这是这种算法的一个死穴。当出现比如0x80112233这样的 DWORD的时候,test eax,81010100h 计算的结果跟0x00112233一样。当然最后的结果不会有问题,因为 byte_3 -- byte_0里面会再次作判断。所以,如果用一连串的0x80112233作为字符串内容,strcpy的效率会大大下降
对于一个DWORD,导致这个因素的可能是
2^24/2^32=1/2^8=1/256
算是比较罕见了
从逻辑上说,最高byte是无法区分本身为1,或者是低byte进位的情况。所以单独的DWORD是无法判断出所有情况的,当前的做法已经算很有想法的了
”