-
几个常用的字符串操作函数 - [assembly fasm ]2006-01-08
下面一段代码实现了几个常用的字符串操作函数,可在汇编语言程序中调用。注释中已给出了C语言调用时的函数声明方式,传递参数时使用__stdcall调用较为快速。因考虑效率是主要因素,主要演示字符串操作指令的用法,能用到双字操作则用双字操作stosd,其次用字操作stosw,再其次用字节操作stosb,如用到产品中去还需对esi和edi进行保护。
部分代码参考于Linux内核代码中这些串操作函数的实现。
这些代码使用flat assembler汇编实现,因fasm能比masm生成更为优化的代码,如mov eax,8等是字节与字间传递,普通汇编器生成五字节代码,而fasm能自动优化生成二字节代码,ret 0在普通汇编器中生成两字节代码,而fasm能直接优化为1Byte(0xc3),本附件中包含了一个生成的dll文件,只有1K大小,里面用十六进制编辑器查看,还有许多填充的零字节(这是PE文件格式所限制的512Bytes对齐,同时PE头至少占用一个512Bytes,功能性代码需另占一个512Bytes所限制了最小PE文件为1K)。有人曾声称使用汇编做出的最小Windows程序是1.5K的,只有一个MessageBox,事实上用fasm来做这个MessageBox只需1K空间。更为重要一点是fasm是多平台的,目前支持Win,DOS,Linux三个平台,写程序时与操作系统API无关的代码都是可移植的。还有fasm的强大的macro定义功能,关于fasm胜于其他汇编器的优点太多了,这里无法一一道出,打算有时间另写一篇。
下载地址:已更新见下文
format PE GUI 4.0 DLL entry dllstart include 'win32a.inc' section '.flat' code executable dllstart: xor eax,eax inc eax ret ; unsigned __stdcall int mstrlen( void const *pSrc ) mstrlen: pop edx ; pop return address pop esi push edx ; push return address for ret xor ecx,ecx .iter0: lodsb or al,al loopne .iter0 .out0: mov eax,ecx neg eax ret ; void __stdcall mstrcpy( void *pDest, void const *pSrc ) mstrcpy: pop edx ; pop return address pop edi pop esi push edx ; push return address for ret .iter0: lodsb or al,al jz .out0 stosb jmp .iter0 .out0: ret ; void __stdcall mstrcat( void *pDest, void const *pSrc ) mstrcat: pop edx ; pop return address pop edi pop esi push edx ; push return address for ret xor eax,eax ; get 0 in al repne scasb ; to find pDest end null byte .iter0: lodsb or al,al jz .out0 stosb jmp .iter0 .out0: ret ; void __stdcall mmemzero( void *pDest, unsigned int uCount ) mmemzero: pop edx ; pop return address pop edi pop ecx push edx ; push return address for ret xor eax,eax push ecx shr ecx,2 rep stosd pop ecx test ecx,2 jz .next0 stosw .next0: test ecx,1 jz .next1 stosb .next1: ret ; void __stdcall mmemfill( void *pDest, unsigned char cChar, unsigned int uCount ) mmemfill: pop edx ; pop return address pop edi pop eax pop ecx push edx ; push return address for ret push ecx shr ecx,2 rep stosd pop ecx test ecx,2 jz .next0 stosw .next0: test ecx,1 jz .next1 stosb .next1: ret ; void __stdcall mmemcopy( void *pDest, void const *pSrc, unsigned int uCount ) mmemcopy: pop edx ; pop return address pop edi pop esi pop ecx push edx ; push return address for ret push ecx shr ecx,2 rep movsd pop ecx test ecx,2 jz .next0 movsw .next0: test ecx,1 jz .next1 movsb .next1: ret align 16 data export export '_memop.dll',\ mstrlen,'mstrlen',\ mstrcpy,'mstrcpy',\ mstrcat,'mstrcat',\ mmemzero,'mmemzero',\ mmemfill,'mmemfill',\ mmemcopy,'mmemcopy' end data
2005-12-07 18:00:03 Updated:
实际上观看 mmemzero 和 mmemfill 代码中有大部分段落是相同的,只有 al 中到底为零还是为用户所传参数的不同,因此代码是可重用的,改写为如下:
; void __stdcall mmemzero( void *pDest, unsigned int uCount ) mmemzero: pop edx ; pop return address pop edi pop ecx push edx ; push return address for ret xor eax,eax .reuse: push ecx shr ecx,2 rep stosd pop ecx test ecx,2 jz .next0 stosw .next0: test ecx,1 jz .next1 stosb .next1: ret ; void __stdcall mmemfill( void *pDest, unsigned char cChar, unsigned int uCount ) mmemfill: pop edx ; pop return address pop edi pop eax pop ecx push edx ; push return address for ret jmp mmemzero.reuse
如此实现的函数间代码共用是C语言所不可能实现的。
如果看过 Linux 内核中少有的几个汇编语言程序,就能发现有些代码能令人为之拍案叫绝!
如arch/i386/boot/setup.S中有一段如下:# Routine to print asciiz string at ds:si prtstr: lodsb andb %al, %al jz fin call prtchr jmp prtstr fin: ret # Space printing prtsp2: call prtspc # Print double space prtspc: movb $0x20, %al # Print single space (note: fall-thru) # Part of above routine, this one just prints ascii al prtchr: pushw %ax pushw %cx movw $7,%bx movw $0x01, %cx movb $0x0e, %ah int $0x10 popw %cx popw %ax ret beep: movb $0x07, %al jmp prtchr
在区区 35Bytes 中实现了实模式下打印字符串,打印双空格,打印空格,打印al中字符,打印响铃字符等多个实用函数,令人叹服。
2006-01-08 13:26:03 Updated:
经过测试,发现这些代码确实存在问题,已修正。
- 对 scasb 等串指令工作方式理解有误:这些串指令如 repne scasb 搜索停止时 edi 停止于所搜索字节的的下一个地址单元,故 strlen 计算长度会多一个,而 strcat 中也增加了 dec edi 才能工作。
- mmemfilll 中 pop eax 只得到 al 的值,以四字节方式填充是错误的,修改后的代码先将 al 填充到整个 eax 才解决问题。
- 原来的mstrcpy 没有给目的字符串填充末尾零,添加了一个 stosb 指令刚刚好。
更新的下载地址中包含 _memop.asm 和 _memoptest.asm 和两个已编译过的二进制文件,要说的是这些 dll 中提供的函数中使用了 eax,ecx,edx,esi,edi 故不能在C语言程序中调用了,C调用方式或 stdcall 调用方式都要求 callee 保护 ebx,ebp,esi,edi 而只能直接使用 eax,ecx,edx 三个寄存器,caller 自己保护 eax,ecx,edx ,故此段程序代码要使用于C程序中还要加上包裹函数,保护 esi,edi 或者另外设计代码才行。
更新下载地址:http://crquan.blogbus.com/files/1136703788.tar


fff
http://dreamerate.blogbus.com/logs/2006/01/1789767.html
看看我近更改过的,效率绝对是一级棒的。已经测试:)
佩服啊,效率就是这样一步步趋向极限的!
(2006-01-12 22:24:48)
呵呵! 刚看了borland的RTL,发觉他们用的搜索字串长度都是用SCASB,不过没有检查参数,如果用户传一个NULL过来,就会崩溃了。这是我最新在使用中发觉的问题。
并且他们传送数据都是先用SCASB计算源串的长度,然后再用
MOV EDX, ECX
SHR ECX, 2 ;除 4
MOVSD
MOV ECX, EDX
AND ECX, 3 ;取 4 的模
MOVSB
好快啊!真是太棒了。
:)
RANK 你的MOVSW会出错的,因为在使用AND取模时,只能用是2的倍数,比如说,只用4:AND ECX, 3
但是不能用3的:AND ECX, 2, 所以MOVSW是不能这样用的,最后,用AND ECX,3就行了
把时间改到了今天,本是想把文章置顶,没想到后边 Latest Comments 里面的链接失效了。真是个惨剧啊。
rank:上面几个函数有BUG。昨天测试发现如下:
1、mstrlen内的neg ecx后再加上dec ecx才能返回正确的字串大小。因为
.iter0:lodsb
or al,al
loopne .iter0
把最后的NULL也计算在内了。我利用repne scasb重写此函数,这个是否会更快呢?
2、MStrCopy拷贝完后,不会在目的串后加NULL,我重写了这函数:
;//////////////////////////////////////////////////////////////////////////////////////
MStrCopy proc uses esi edi pDest:DWORD, pSrc:DWORD
mov edi, dword ptr [pDest]
mov esi, dword ptr [pSrc]
xor ecx, ecx
cld
@begin:
lodsb
stosb
or al, al
jz @final
inc ecx
jmp @begin
@final:
mov eax, ecx
ret
MStrCopy endp
;//////////////////////////////////////////////////////////////////////////////////////
3、MStrCat执行结果也不正确,因为repne scasb后目的指针已加1,意思就是把NULL加进了,导致pSrc加在NULL后而无效。而repne scasb也会判别ECX是否为零,为了避免发生尚未检测完NULL由于ECX为零转换执行下一指令,我把ECX设为-1了,这样就不怕减一减到成零了:)看下面:
MStrCat proc uses esi edi pDest:DWORD, pSrc:DWORD
cld
mov edi, dword ptr [pDest]
mov esi, dword ptr [pSrc]
; 构造一个对于scasb操作ecx-=1不等于0的数
xor ecx, ecx
dec ecx
xor eax, eax ; 搜索NULL字符
repne scasb
dec edi ; 指针减一
xor ecx, ecx ; initialize cat bits
@begin:
lodsb
stosb
or al, al
jz @final
inc ecx
jmp @begin
@final:
mov eax, ecx
ret
MStrCat endp
4、MMemCopy及MMemFill我把MASM32库函数的拷过来了,你看看这俩函数是否会更快呢?
:)
当初只是看见用 mov 实现的这几个函数效率不高才写的,没想到有这么多朋友在关注啊,看来真要好好测一测!
(2006-01-07 09:13:35)
偶至今尚未使用过LINUX。真是丢脸了……
如上打印段代码中的指令:andb, movb, pushw, movw, popw应该是FASM的宏吧?
哦,不是啊,最后一段代码是内核中的汇编语言用的是 GNU as 的格式,以 w 结尾的指令是 word 操作的意思,这是 GNU assembler 的风格,内核中所有的汇编语言都用这种风格,是 AT&T 的汇编语言格式,不是 Intel 的,所以很多指令的写法都与 Intel 公布的不一样,一般看懂还是可以的,自己写的话就有点难为了。 fasm 则与这无关的,主页为(http://flatassember.net/),不过现在好像直接不能访问了,要找个代理吧,(ping 它有IP返回,说明域名解析无错,但IP连不通,我想又是拜网警所赐乱封IP的结果吧)
(2005-12-08 16:05:09)
确实不错啊!代码很好!LINUX就是强!有机会要试试FASM!^_^