VC目标代码的阅读理解2

 

第5章 VC目标代码的阅读理解
5.1 汇编语言形式的目标代码
5.2 C语言部分编译的解析
5.3 C++部分功能实现细节
5.4 目标程序的优化
5.5 C库函数分析
5.6 C程序的目标代码分析   
 

5.5 C库函数分析
5.5.1 函数strlen
5.5.2 函数strpbrk
5.5.3 函数memset 汇编语言
源程序!
汇编语言
源程序!   
 

5.5.1 库函数strlen

.原型及算法


int
strlen
(
const
char *
str
)
{
int
length
= 0;
while
( *
str
++ )
++
length
;
return
(
length
);
}
int strlen (const char * str)
{
int length = 0;
while( *str++ )
++length;
return( length );
}
计数直到遇到字符串结束标记,
不包括结束标记
计数直到遇到字符串结束标记,
不包括结束标记   
 

.汇编源程序实现步骤


.
首先,确保循环开始的首地址双字对齐
。因为
最多相差
3
个字节,而且字符串可能很短,所以从首地址开始依
次逐字节判断

.
其次,每次
4
个字节,循环判断是否遇到字符串结束标

。循环体分
两部分:其一,快速推断出
4
个字节中不
含结束标记,从而继续循环;其二,在极可能遇到结束
标记的情形下,准确定位结束标记

.
最后,计算
字符串的长度
。字符串的长度是字符串结束
标记所在位置与起始位置的差

.首先,确保循环开始的首地址双字对齐。因为最多相差
3个字节,而且字符串可能很短,所以从首地址开始依
次逐字节判断。
.其次,每次4个字节,循环判断是否遇到字符串结束标
记。循环体分两部分:其一,快速推断出4个字节中不
含结束标记,从而继续循环;其二,在极可能遇到结束
标记的情形下,准确定位结束标记。
.最后,计算字符串的长度。字符串的长度是字符串结束
标记所在位置与起始位置的差。


地址对齐!
地址对齐!
减少循环次数!
减少循环次数!

减少
分支

减少分支!
汇编语言
源程序!
汇编语言
源程序!
5.5.1 库函数strlen   
 

.汇编源程序(一)


strlen
proc
mov
ecx
,
[
esp
+ 4]
;
ecx
指向字符串首
test
ecx
,
3
;
地址
4
倍对齐?
je
short
main_loop
;
是,则

str_misaligned
: ;
字节递增
,
实现地址
4
倍对齐
mov
al, byte
ptr
[
ecx
]
add
ecx
, 1
test
al, al
je
short byte_3 ;
遇到结束标记,转
test
ecx
,
3
jne
short
str_misaligned
;
仍没有
4
倍对齐,继续递增
add
eax
,
0
strlen proc
mov ecx, [esp + 4] ;ecx指向字符串首
test ecx, 3 ;地址4倍对齐?
je short main_loop ;是,则转
str_misaligned: ;字节递增,实现地址4倍对齐
mov al, byte ptr [ecx]
add ecx, 1
test al, al
je short byte_3 ;遇到结束标记,转
test ecx, 3
jne short str_misaligned ;仍没有4倍对齐,继续递增
add eax, 0
堆栈传递待
测量字符串
起始地址(偏移
),

eax
返回字符串长度
堆栈传递待测量字符串起始地址(偏移),
由eax返回字符串长度
占位置!为了代码地址对齐

5.5.1 库函数strlen
地址对齐!
地址对齐!   
 

.汇编源程序(二)


 
align
16 ;
要求
16
字节对齐!!
main_loop
: ;
现在地址
4
倍对齐
mov
eax
,
dword
ptr
[
ecx
] ;

4
个字节
mov
edx
, 7efefeffh
add
edx
,
eax
;
较低
字节非
0
,则会向上进位
xor
eax
,
-
1 ;
eax
= FFFFFFFFH
xor
eax
,
edx
;
过滤出待测试的位
add
ecx
, 4 ;
调整指针
test
eax
, 81010100h ;
判断
4
字节中是否含有全
0
字节

je
short
main_loop
;
否,继续循环
;
--------------------------------
align 16 ;要求16字节对齐!!
main_loop: ;现在地址4倍对齐
mov eax, dword ptr [ecx] ;取4个字节
mov edx, 7efefeffh
add edx, eax ;较低字节非0,则会向上进位
xor eax, -1 ;eax= FFFFFFFFH
xor eax, edx ;过滤出待测试的位
add ecx, 4 ;调整指针
test eax, 81010100h ;判断4字节中是否含有全0字节?
je short main_loop ;否,继续循环
;--------------------------------
5.5.1 库函数strlen
减少循环次数!
减少循环次数! 每次处理
4
个字节,确定可能的结束标记
每次处理4个字节,确定可能的结束标记

精心设计,巧妙独特   
 

.汇编源程序(三)


 
mov
eax
, [
ecx
-
4]
;
重新取刚才的
4
字节
test
al, al
;
是否第
0
字节为全
0
je
short
byte_0
test ah, ah ;
是否第
1
字节为全
0
je
short byte_1
test
eax
, 00ff0000h
;
是否第
2
字节为全
0
je
short byte_2
test
eax
, 0ff000000h
;
是否第
3
字节为全
0
je
short byte_3
jmp
short
main_loop
;
处理例外:位
24
-
30

0
,位
31

1
;
--------------------------------
mov eax, [ecx - 4] ;重新取刚才的4字节
test al, al ;是否第0字节为全0
je short byte_0
test ah, ah ;是否第1字节为全0
je short byte_1
test eax, 00ff0000h ;是否第2字节为全0
je short byte_2
test eax, 0ff000000h ;是否第3字节为全0
je short byte_3
jmp short main_loop ;处理例外:位24-30为0,位31为1
;--------------------------------
精确定位

4
字节中
极有可能
含有结束标记
精确定位:4字节中极有可能含有结束标记
可能存在例外!
5.5.1 库函数strlen   
 

.汇编源程序(四)


byte_3
:
lea
eax
, [
ecx
-
1]
;
eax
指向字符串尾
mov
ecx
, [
esp
+ 4]
;
ecx
指向字符串首
sub
eax
,
ecx
;
计算字符串长度(不含结束标志)
ret
byte_2
:
lea
eax
, [
ecx
-
2]
;
eax
指向字符串尾
mov
ecx
, [
esp
+ 4]
;
ecx
指向字符串首
sub
eax
,
ecx
;
计算字符串长度(不含结束标志)
ret
byte_3:
lea eax, [ecx - 1] ;eax指向字符串尾
mov ecx, [esp + 4] ;ecx指向字符串首
sub eax, ecx ;计算字符串长度(不含结束标志)
ret
byte_2:
lea eax, [ecx - 2] ;eax指向字符串尾
mov ecx, [esp + 4] ;ecx指向字符串首
sub eax, ecx ;计算字符串长度(不含结束标志)
ret
找到结束标记后的处理:
找到结束标记后的处理:
5.5.1 库函数strlen
减少
分支

减少分支!   
 

.汇编源程序(五)


byte_1
:
lea
eax
, [
ecx
-
3]
;
eax
指向字符串尾
mov
ecx
, [
esp
+ 4]
;
ecx
指向字符串首
sub
eax
,
ecx
;
计算字符串长度(不含结束标志)
ret
byte_0
:
lea
eax
, [
ecx
-
4]
;
eax
指向字符串尾
mov
ecx
, [
esp
+ 4]
;
ecx
指向字符串首
sub
eax
,
ecx
;
计算字符串长度(不含结束标志)
ret
strlen
endp
byte_1:
lea eax, [ecx - 3] ;eax指向字符串尾
mov ecx, [esp + 4] ;ecx指向字符串首
sub eax, ecx ;计算字符串长度(不含结束标志)
ret
byte_0:
lea eax, [ecx - 4] ;eax指向字符串尾
mov ecx, [esp + 4] ;ecx指向字符串首
sub eax, ecx ;计算字符串长度(不含结束标志)
ret
strlen endp
找到结束标记后的处理:
找到结束标记后的处理:
5.5.1 库函数strlen
分别处理!
分别处理!为了减少分支   
 

.提高运行效率的方法


.
尽可能
使得每次访问双字存储单元的
地址双

对齐
。而
且,循环
代码片段也从
16
字节对齐的地址处开始

.
减少
循环次数

.
减少
分支
。一次推断

4

连续字节
不含结束
标记。安
排就地
返回。
.

空间换时间
。安排四段很相似的计算字符串长度的代

,有效
减少分支。
.尽可能使得每次访问双字存储单元的地址双字对齐。而
且,循环代码片段也从16字节对齐的地址处开始。
.减少循环次数。
.减少分支。一次推断出4个连续字节不含结束标记。安
排就地返回。
.以空间换时间。安排四段很相似的计算字符串长度的代
码,有效减少分支。


5.5.1 库函数strlen   
 

5.5.2 库函数strpbrk
.原型及算法


 
char *
strpbrk
(
const
char *
str
,
const
char *
control
)
char * strpbrk(const char *str, const char *control)
库函数
strpbrk
的功能:
获得
在字符串
str
中出现的第一个来自于字符串
control
中字
符的地址偏移

或者
说,指向字符串
str
中的第一个属于字符串
control
的字
符的
指针;
如果
不存在,则返回空指针。
库函数strpbrk的功能:
获得在字符串str中出现的第一个来自于字符串control中字
符的地址偏移;
或者说,指向字符串str中的第一个属于字符串control的字
符的指针;
如果不存在,则返回空指针。   
 

.原型及算法


char
*
strpbrk
(
const
char *
str
,
const
char *control
)
{ unsigned
char map[32];
int
count;
for
(count = 0; count < 32; count++)
map[count
] = 0;
while
(*control)
{ map
[*control >> 3] |= (1 << (*control & 7));
control
++;
}
while
(*string)
{ if
(map[*string >> 3] & (1 << (*string & 7)))
return(string
)
;
string
++;
}
return(NULL
)
;
}
char * strpbrk(const char *str, const char *control)
{ unsigned char map[32];
int count;
for (count = 0; count < 32; count++)
map[count] = 0;
while (*control)
{ map[*control >> 3] |= (1 << (*control & 7));
control++;
}
while (*string)
{ if (map[*string >> 3] & (1 << (*string & 7)))
return(string);
string++;
}
return(NULL);
}
初始化位图!
初始化位图!
标记位
图!
标记位图!
根据
位图,查找!
根据位图,查找!
5.5.2 库函数strpbrk   
 

.汇编源程序实现步骤


.
首先
,初始化

32
个字节构成的长
256
位的位图。
.
然后
,标记
位图。对
control
字符串中出现的每个字符,
在位图的对应位上做标记

.
最后
,依次搜索
首次出现的特定字符。从字符串
str

首字符开始,依次逐一判断位图中对应的位是否有标记,
直到有标记字符出现为止

.首先,初始化由32个字节构成的长256位的位图。
.然后,标记位图。对control字符串中出现的每个字符,
在位图的对应位上做标记。
.最后,依次搜索首次出现的特定字符。从字符串str的
首字符开始,依次逐一判断位图中对应的位是否有标记,
直到有标记字符出现为止。


二重循环成为一重循环!
二重循环成为一重循环!
空间换时间!
空间换时间!
汇编语言
源程序!
汇编语言
源程序!
5.5.2 库函数strpbrk
char *
strpbrk
(
const
char *
str
,
const
char *
control
);
char * strpbrk(const char *str, const char *control);   
 

.汇编源程序(一)


strpbrk
proc
PUSH
EBP
MOV
EBP,
ESP
PUSH ESI
xor
eax
,
eax
push
eax
; 32
push
eax
push
eax
push
eax
; 128
push
eax
push
eax
push
eax
push
eax
; 256
strpbrk proc
PUSH EBP
MOV EBP, ESP
PUSH ESI
xor eax, eax
push eax ; 32
push eax
push eax
push eax ; 128
push eax
push eax
push eax
push eax ; 256

堆栈传递字符串
str

control
的起始地址(偏移

,

eax
返回指向第一个出现字符的指针(可能空指针)
由堆栈传递字符串str和control的起始地址(偏移),
由eax返回指向第一个出现字符的指针(可能空指针)
5.5.2 库函数strpbrk
高效地
初始化位图
高效地初始化位图
在堆栈中建立256位的位图   
 

.汇编源程序(二)


 
mov
edx
, [
ebp+12
] ;
取得字符串
control
首地址
lea
ecx
, [ecx+0
]
;
listnext
:
mov
al, [
edx
]
or
al, al ;
到字符串
control
尾?
jz
short
listdone
;
是,已经建立好位图
add
edx
, 1
bts
DWORD PTR [
esp
],
eax
;
设置位图中的对应位
jmp
short
listnext
listdone
:
mov edx, [ebp+12] ;取得字符串control首地址
lea ecx, [ecx+0]
;
listnext:
mov al, [edx]
or al, al ;到字符串control尾?
jz short listdone ;是,已经建立好位图
add edx, 1
bts DWORD PTR [esp], eax ;设置位图中的对应位
jmp short listnext
listdone:
占位置!为了代码地址对齐
占位置!为了代码地址对齐
位操作指令的使用
位操作指令的使用
5.5.2 库函数strpbrk
标记位图


control
中出现字符
标记位图,
按control中出现字符   
 

.汇编源程序(三)


listdone
:
mov
esi
, [
ebp+8
] ;
取得字符串
str
的首地址
mov
edi
,
edi
dstnext
:
mov
al, [
esi
]
or
al, al ;
到字符串
str
尾?
jz
short
dstdone
;
是,准备返回
add
esi
, 1
bt
DWORD PTR [
esp
],
eax
;
判断位图
中对应
位是否被标记
jnc
short
dstnext
;
无标记,未出现在
control

; ;
至此,出现
属于
control
的字符
lea
eax
, [esi
-
1] ;
准备返回指针
dstdone
:
listdone:
mov esi, [ebp+8] ;取得字符串str的首地址
mov edi, edi
dstnext:
mov al, [esi]
or al, al ;到字符串str尾?
jz short dstdone ;是,准备返回
add esi, 1
bt DWORD PTR [esp], eax ;判断位图中对应位是否被标记
jnc short dstnext ;无标记,未出现在control中
; ;至此,出现属于control的字符
lea eax, [esi-1] ;准备返回指针
dstdone:
位操作指令的使用
位操作指令的使用
占位置!为了地址对齐
占位置!为了地址对齐
5.5.2 库函数strpbrk
查找
!
查找! 根据字符,对照位图   
 

.汇编源程序(四)


dstdone
:
add
esp
, 32 ;
撤销作为局部变量的位图
POP
ESI ;
恢复被保护的寄存器
LEAVE
;
撤销堆栈框架
ret
strpbrk
endp
dstdone:
add esp, 32 ;撤销作为局部变量的位图
POP ESI ;恢复被保护的寄存器
LEAVE ;撤销堆栈框架
ret
strpbrk endp
新指令
5.5.2 库函数strpbrk   
 

.提高运行效率的方法


.
充分
利用
IA
-
32
系列处理器的指令

.
在标记位图和检测位图时,
都采用了位
操作指令。
.
利用“
push
eax

指令,初始化位图

.
指令
LEAVE
.
指令地址对齐
。循环体
开始指令处于双字边界

.

空间换时间
。采用
位图的思路,以较小的堆栈空间,
避免了二重循环。
.充分利用IA-32系列处理器的指令。
.在标记位图和检测位图时,都采用了位操作指令。
.利用“push eax”指令,初始化位图。
.指令LEAVE


.指令地址对齐。循环体开始指令处于双字边界。
.以空间换时间。采用位图的思路,以较小的堆栈空间,
避免了二重循环。


5.5.2 库函数strpbrk   
 

5.5.3 库函数memset
.原型及算法


VC2010
集成开发环境使用的内部
函数

功能:把
某个内存区域的每个字节填充成指定
值。
VC2010集成开发环境使用的内部函数,
功能:把某个内存区域的每个字节填充成指定值。
char
*
memset
(
char *
dst
,
char
value
,
unsigned
int
count
);
char * memset(char *dst, char value, unsigned int count);
指向内存区域 填充值 填充字节数   
 

.汇编源程序实现主要步骤


.
优先考虑采用
SSE2
技术。
.
首先,使得准备批量填充的首地址双字对齐

.
其次,实施批量填充,每次双字

(采用带有重复前缀的字符串操作)
.
最后,扫尾工作。
.优先考虑采用SSE2技术。
.首先,使得准备批量填充的首地址双字对齐。
.其次,实施批量填充,每次双字。
(采用带有重复前缀的字符串操作)
.最后,扫尾工作。


汇编语言
源程序!
汇编语言
源程序!
5.5.3 库函数memset
SSE2(Streaming SIMD Extensions 2)
数据流单指令多数据扩展指令集 2   
 

.汇编源程序(一)


memset
proc
mov
edx
,
[
esp
+ 0ch]
;
EDX =
长度(字节数)
mov
ecx
,
[
esp
+ 4]
;
ECX =
目标首地址
test
edx
,
edx
;
长度为
0

jz
short
toend
;
如长度为
0
,直接结束
xor
eax
,
eax
mov
al,
[
esp
+ 8]
;
AL =
填充值
;
memset proc
mov edx, [esp + 0ch] ;EDX = 长度(字节数)
mov ecx, [esp + 4] ;ECX = 目标首地址
test edx, edx ;长度为0?
jz short toend ;如长度为0,直接结束
xor eax, eax
mov al, [esp + 8] ;AL = 填充值
;

堆栈获取缓冲区起始地址等参数,
为了高效,不建立堆栈框架
从堆栈获取缓冲区起始地址等参数,
为了高效,不建立堆栈框架
直接通过ESP访问堆栈
5.5.3 库函数memset   
 

.汇编源程序(二)


 
test
al, al
;
填充值是
0

jne
dword_align
;
否,跳转
cmp
edx
, 080h
;
填充区域较大吗?
jb
dword_align
;
否,跳转
cmp
DWORD PTR
__sse2_available
, 0 ;
支持
SSE2 ?
je
dword_align
;
否,跳转
jmp
_
VEC_memzero
;
利用
SSE2
实现(不再返回)
test al, al ;填充值是0?
jne dword_align ;否,跳转
cmp edx, 080h ;填充区域较大吗?
jb dword_align ;否,跳转
cmp DWORD PTR __sse2_available, 0 ;支持 SSE2 ?
je dword_align ;否,跳转
jmp _VEC_memzero ;利用 SSE2 实现(不再返回)
当填充
0
,并且填充区域较大时,
优先考虑采用
SSE2
技术
当填充0,并且填充区域较大时,
优先考虑采用SSE2技术
5.5.3 库函数memset
SSE2(Streaming SIMD Extensions 2)
数据流单指令多数据扩展指令集 2   
 

.汇编源程序(三)


dword_align
:
push
edi
;
保护
EDI
mov
edi
,
ecx
;
EDI =
目标首地址
cmp
edx
, 4
;
长度小于
4
字节?
jb
tail
;
是,直接跳转到扫尾处理
neg
ecx
;
目标
首地址在对齐之前的字节数
and
ecx
, 3
;
ECX =
在双字对齐之前的字节数
jz
short
dwords
;
已经双字对齐,跳转
sub
edx
,
ecx
;
EDX =
稍后需要批量处理的长度
adjust_loop
:
mov
[
edi
], al
;
为了
双字对齐,少量填充
add
edi
, 1
sub
ecx
, 1
jnz
adjust_loop
dword_align:
push edi ;保护EDI
mov edi, ecx ;EDI = 目标首地址
cmp edx, 4 ;长度小于4字节?
jb tail ;是,直接跳转到扫尾处理
neg ecx ;目标首地址在对齐之前的字节数
and ecx, 3 ;ECX = 在双字对齐之前的字节数
jz short dwords ;已经双字对齐,跳转
sub edx, ecx ;EDX = 稍后需要批量处理的长度
adjust_loop:
mov [edi], al ;为了双字对齐,少量填充
add edi, 1
sub ecx, 1
jnz adjust_loop
确保批量填充的开始地址为双字地址对齐
确保批量填充的开始地址为双字地址对齐
逐字节填充
5.5.3 库函数memset   
 

.汇编源程序(四)


 
mov
ecx
,
eax
;
ecx
=0 |0 |0 |
val
shl
eax
, 8
;
eax
=0 |0 |
val
|0
add
eax
,
ecx
;
eax
=0 |0 |
val
|
val
mov
ecx
,
eax
;
ecx
=0 |0 |val|val
shl
eax
, 10h
;
eax
=val|val|0 |0
add
eax
,
ecx
;
eax
=
val
|
val
|
val
|
val
;
mov ecx, eax ; ecx=0 |0 |0 |val
shl eax, 8 ; eax=0 |0 |val|0
add eax, ecx ; eax=0 |0 |val|val
mov ecx, eax ; ecx=0 |0 |val|val
shl eax, 10h ; eax=val|val|0 |0
add eax, ecx ; eax=val|val|val|val
;
形成双字(
4
字节)的填充值
形成双字(4字节)的填充值
5.5.3 库函数memset   
 

.汇编源程序(五)


 
mov
ecx
,
edx
;
ECX =
长度
and
edx
, 3
;
EDX =
尾数(
4
字节填充后的剩余)
shr
ecx
, 2
;
ECX = 4
字节为单位的长度
jz
tail
;
如为
0
,直接跳转到扫尾处理
rep
stosd
;
批量填充

main_loop_tail
:
test
edx
,
edx
;
是否有“尾巴”?
jz
finish
;
没有,填充完毕
mov ecx, edx ;ECX = 长度
and edx, 3 ;EDX = 尾数(4字节填充后的剩余)
shr ecx, 2 ;ECX = 4字节为单位的长度
jz tail ;如为0,直接跳转到扫尾处理
rep stosd ;批量填充!
main_loop_tail:
test edx, edx ;是否有“尾巴”?
jz finish ;没有,填充完毕
批量填充
批量填充
5.5.3 库函数memset   
 

.汇编源程序(六)


tail
:
mov
[
edi
], al
;
每次扫尾
1
字节
add
edi
,
1
sub
edx
,
1
jnz
tail
finish
:
mov
eax
, [
esp
+ 8]
;
准备
返回

pop
edi
;
恢复保存的
EDI
ret
toend
:
mov
eax
, [
esp
+ 4]
;
准备返回

ret
memset
endp
tail:
mov [edi], al ;每次扫尾1字节
add edi, 1
sub edx, 1
jnz tail
finish:
mov eax, [esp + 8] ;准备返回值
pop edi ;恢复保存的EDI
ret
toend:
mov eax, [esp + 4] ;准备返回值
ret
memset endp
扫尾工作及返回
扫尾工作及返回
5.5.3 库函数memset   
 

.提高运行效率的方法


.
作为
内部函数,没有建立堆栈
框架。
.
如果

较大内存区域实施清零
,优先采用
SSE2
技术

.

循环多次连续访问内存单元之前,调整访问的存储单
元起始地址,从而保证内存单元地址处于双字对齐。
.
采用
字符串操作指令,尽可能每次填充双字。
.作为内部函数,没有建立堆栈框架。
.如果对较大内存区域实施清零,优先采用SSE2技术。
.在循环多次连续访问内存单元之前,调整访问的存储单
元起始地址,从而保证内存单元地址处于双字对齐。
.采用字符串操作指令,尽可能每次填充双字。


5.5.3 库函数memset
地址对齐!
地址对齐!
减少循环次数!
减少循环次数!   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值