吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5981|回复: 27
收起左侧

[原创] 【老刘谈算法001】这位运算玩的真溜—strlen函数的汇编实现分析

[复制链接]
老刘 发表于 2018-6-27 14:00
本帖最后由 老刘 于 2018-6-28 12:48 编辑

新手刷下存在感,大佬们见笑了,轻喷


首先挂下代码,
[Asm] 纯文本查看 复制代码
;原函数作者为Agner Fog,出处为MASM32开发包,在此表示感谢。
;中文注释修改&添加 By 老刘。


    .486
    .model flat, stdcall
    option casemap :none

    .code


OPTION PROLOGUE:NONE 
OPTION EPILOGUE:NONE 

align 4

StrLen proc item:DWORD

    mov     eax, [esp+4]            ;获得参数item,即字符串指针
    lea     edx, [eax+3]            ;edx=指针+3
    push    ebp                     ;备份ebp edi
    push    edi
    mov     ebp, 80808080h

  @@:     
  REPEAT 3
    mov     edi, [eax]              ;edi=读4个字节
    add     eax, 4                  ;字符串指针指到下4个字节起始,即+4
    lea     ecx, [edi-01010101h]    ;ecx=四字节每个字节-1
    not     edi                     ;edi=四字节逻辑取反
    and     ecx, edi                ;ecx=取反后的每个字节和没取反后-1的每个字节进行逻辑与
    and     ecx, ebp                ;判断每个字节的二进制第8位是否为1,为1则说明原字节=0
    jnz     nxt                     ;如果出现了一个null异端,跳到nxt继续判断
  ENDM

    mov     edi, [eax]              ;和上面的一样
    add     eax, 4                  
    lea     ecx, [edi-01010101h]    
    not     edi                     
    and     ecx, edi                
    and     ecx, ebp
    jz      @B                      ;如果没有异端,回去上面循环,有的话都不用跳了,下面就是nxt

  nxt:
    test    ecx, 00008080h          ;测试null是否在前2个字节中
    jnz     @F
    shr     ecx, 16                 ;不在前2字节,ecx右移16个二进制位,即右移2字节,使原来的第3、4字节移位到1、2字节处。
    add     eax, 2                  ;字符串指针+2
  @@:
    shl     cl, 1                   ;第一个字节逻辑左移1位,如果第一个字节为Null,则CF=1,否则即说明第二个字节为Null
    sbb     eax, edx                ;eax=eax-edx-CF=eax-(item+3)-CF=字符串长度
    pop     edi
    pop     ebp

    ret     4

StrLen endp

OPTION PROLOGUE:PrologueDef 
OPTION EPILOGUE:EpilogueDef 


end

作者的底层知识非常牢固,位运算运用的炉火纯青,
代码算不上精简,但非常高效,
为了达到上述目的,其代码人类直接不好理解,让我们将几处难理解的地方细细分析道来。

一、高效 —— 一次判断4个字节
对应Line25~~42,
这段代码运用位运算,在寄存器中一次性判断4个Byte内有无Null。
我们按一个字节进行分析,用数学语言转换代码,下述的十进制数均为UByte类型。
令a=该Byte的无符号值,
令f(x)为逻辑非运算的对应函数,
有f(x)=255-x
b=a-1
此时因为溢出,有b=a-1(a∈[1,255])或b=255(a=0)
我们将其设为函数,有b=g(a)
c=f(a)
d=b and c
进行逻辑与运算时,结果总是不大于两个进行运算的数
所以有d<=b和c中较小的那一个
易得a∈[1,127]时,d<=g(a),此时g(a)max=g(127)=126,
a∈[128,255]时,d<=f(a),f(a)max=f(128)=127,
a=0时,d = 255 and 255 = 255,
e=d and 0x80
a∈[1,127]时,d<=126,此时∵d的第8二进制位上定为0,e=0
同理,a∈[128,255],e=0
a=0,e=0x80
即a不为0时,e=0,a为0时,e=0x80。

二、拒绝浪费寄存器——字符串长度的最终计算
对应Line:20、28、37、48、50、51
作者读取完需判断的字节后,不管3721,先将指针+4(28、37行)
这方便了再次循环,简化了代码,
在48行将指针+2,
50行时令CF=指针所指字节第8位
51行则是最终计算,
由一可知,当Byte为null时,判断下来第8二进制位=1
即当指针所指字节不为null,CF=1
51行得到的结果=当前指针-字符串起始指针-3-1
当Byte不为null时,由其他语句得知下一个byte肯定为null,
此时第8二进制位=0,CF=0
51行得到的结果则少减了1,正好对应真实的字符串长度。
这位老外是真的秀,在追求高效率的时候还不忘精简一波代码~



该代码还有局限性,当出现双字节字符时就会判断成2个长度
不过对于一个用英文编程的老外来说,也可以理解,
代码质量可以说是很高了,只是让分析它的人非常蛋痛(ˉ▽ˉ;)…
可能这就是“最高效率和可读性不可兼得”吧!

免费评分

参与人数 10吾爱币 +9 热心值 +10 收起 理由
QB56 + 1 + 1 热心回复!
戏言19 + 1 + 1 谢谢@Thanks!
氧气哥哥 + 1 + 1 已经处理,感谢您对吾爱破解论坛的支持!
额度更深刻 + 1 + 1 用心讨论,共获提升!
子五十 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
xinkui + 1 + 1 谢谢@Thanks!
k123006 + 1 + 1 谢谢@Thanks!
mihacker + 1 用心讨论,共获提升!
iteamo + 1 + 1 这个代码是写给他自己看的.....
welcome7758521 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 老刘 发表于 2018-6-27 17:28
本帖最后由 老刘 于 2018-6-27 17:30 编辑
yikuaidao 发表于 2018-6-27 15:54
请使用不同位数的cpu运算比较一下运算效率(字符串随机生成)
主要是比较4字节判断和1字节判断,哪种效率高

本来想做长字串验证,800K的文本切割成100byte的,但masm连100个byte长的变量都不能搞,真是服
所以就做的短字串多次求长,一亿次,
结果如下(i3)

[Asm] 纯文本查看 复制代码
C:\masm32\_test>(
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test2.exe
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test2.exe
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test2.exe
More? )
4.8515625
4.8671875
4.80859375

C:\masm32\_test>(
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test.exe
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test.exe
More? cscript -nologo %windir%\ElapsedTime.VBS C:\masm32\_test\test.exe
More? )
2.1171875
2.13671875
2.42578125

C:\masm32\_test>

test.asm
[Asm] 纯文本查看 复制代码
Include masm32rt.inc
.code
start:
        mov ecx,100000000
        @@:
                push ecx
                mov eax,cfm$("Hello,World.")
                Invoke StrLen,EAX
                pop ecx
                dec ecx
                cmp ecx,0
        jne @B
        Invoke ExitProcess,NULL
end start

test2.asm
[Asm] 纯文本查看 复制代码
Include masm32rt.inc
.code
StrLen2 proc item:DWORD
        push ecx
    xor ecx,ecx
    mov eax,item
    
    @@:
    mov cl,byte ptr [eax]
    inc eax
    jcxz @F
    jmp @B
    
    @@:
    dec eax
    sub eax,item
    pop ecx
    ret 4
StrLen2 endp

start:
        mov ecx,100000000
        @@:
                mov eax,cfm$("Hello,World.")
                Invoke StrLen2,EAX
                dec ecx
                cmp ecx,0
        jne @B
        Invoke ExitProcess,NULL
end start

哈哈,我想这已经可以说明一些问题了
 楼主| 老刘 发表于 2018-6-27 14:34 来自手机
welcome7758521 发表于 2018-6-27 14:19
收听大佬了,希望大佬讲的细节和延伸知识也可以说一下。

哈哈,同为初学,
第一次写这样的文章,原来写的篇幅较长,又觉得自己比较啰嗦,就改短了,
位运算,大端小端,指针这些算是基础知识吧,就没有细说,
对照着王爽老师的书看看应该还是很好理解的吧
司徒大豆 发表于 2018-6-27 14:08
szp0305 发表于 2018-6-27 14:19
汇编无非就是转移跳转出栈压栈等,哈哈
welcome7758521 发表于 2018-6-27 14:19
收听大佬了,希望大佬讲的细节和延伸知识也可以说一下。
lyliucn 发表于 2018-6-27 14:28
汇编很不好看,有的还看不懂。
gxkyrftx 发表于 2018-6-27 14:35
大佬还在看雪上发
 楼主| 老刘 发表于 2018-6-27 14:36 来自手机
lyliucn 发表于 2018-6-27 14:28
汇编很不好看,有的还看不懂。

为了效率丢掉可读性系列,
原来我觉得这函数挺简单的,
被他搞这么复杂,蛋疼T_T
 楼主| 老刘 发表于 2018-6-27 14:38 来自手机
gxkyrftx 发表于 2018-6-27 14:35
大佬还在看雪上发

不敢不敢,同为初学
看来俩论坛自带联通蛤
Cc-Saber 发表于 2018-6-27 14:43
学习了,感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-17 00:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表