汇编实验

汇编实验

最后一批有意思的练习题

写在前面

  • 终于完成了Intel x86汇编的习题(我认为应该写的)+基础知识,基本上花了3周左右(除去假期中间的咕咕咕极长时间),可喜可贺,也该开学了。
  • 不coding死路一条
  • 不要停下来啊(指学习)
  • 下一阶段的目标就是数学+算法+Python网络编程
  • 不学数学读研死路一条

什么,你说假期里的前端学习?咕咕咕
汇编代码咋高亮啊,好像不支持…

Str_find过程

编写过程Str_find在目的串中查找第一次出现的源串,并返回其位置。输入参数为源串指针和目的指针。如果查找成功,过程将零标志位ZF置1,用EAX返回指向目的的串的匹配位置。否则ZF清零,EAX无定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
Str_find PROTO, source_str:PTR BYTE, target_str:PTR BYTE,source_len:DWORD,target_len:DWORD
.data
source BYTE "Life is short so I use Python", 0
target BYTE "hor", 0
pos DWORD ?

.code
main PROC
INVOKE Str_find, ADDR source, ADDR target, LENGTHOF source, LENGTHOF target
jnz notFound
mov pos, eax ;保存
notFound:
INVOKE ExitProcess, 0
main ENDP

Str_find PROC USES ecx esi edi ebx edx,
source_str:PTR BYTE,
target_str:PTR BYTE,
source_len:DWORD,
target_len:DWORD
mov edi, source_str
mov esi, target_str
mov dl, [esi] ;首字符
mov ecx, source_len
L1:
cmp dl, [edi]
je L3
add edi, 1
loop L1
jmp quit ;找了一轮没有匹配
L3:
push ecx
mov ebx, ecx ;源字符串还剩余多少
mov ecx, target_len
sub ecx, 2 ;只剩长度-1个字符需要比较
jz find ;只剩一个元素
cmp ecx, ebx ;比较所剩长度
ja L4 ;源串所剩长度小于子串,结束
mov ebx, 1
L2:
mov dl, [esi+ebx] ;Target_str中取下一个
cmp [edi+ebx], dl
jne next
inc ebx
loop L2
find:
mov eax, edi ;找到子串
dec eax ;减1代表首元素
test edi, 0 ;ZF置0
L4:
pop ecx ;别忘恢复栈
quit:
ret
next:
pop ecx
jmp L1
Str_find ENDP
END main

链表

用汇编语言实现一个单项链表(课本例子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
INCLUDE Irvine32.inc

ListNode STRUCT
NodeData DWORD ?
NextPtr DWORD ?
ListNode ENDS

TotalNodeCount = 15
NULL = 0
Counter = 0

.data
LinkedList LABEL PTR ListNode ;定义一个标号,标识一下头,不分配内存
REPEAT TotalNodeCount
Counter = Counter + 1
ListNode <Counter, $ + Counter*SIZEOF ListNode>
ENDM

ListNode<0, 0> ;尾节点

.code
main PROC
mov esi, OFFSET LinkedList
;显示NodeData的值
NextNode:
;检查是否是尾节点
mov eax, (ListNode PTR [esi]).NextPtr
cmp eax, NULL
je quit

;显示结点数据
mov eax, (ListNode PTR [esi]).NodeData
call WriteDec
call Crlf

;获取下一个节点的指针
mov esi, (ListNode PTR [esi]).NextPtr
jmp NextNode
quit:
call WaitMsg
exit
main ENDP
END main

需要注意:

  • 先定义一个标识头,不然到最后没有办法找到链表的起始位置
  • 使用REPEAT伪指令来重复执行一个代码块,其格式如下:

REPEAT constExpression
    statement
ENDM

constExpression是一个无符号整数常量,用于确定重复的次数,这里我们重复执行
Counter = Counter + 1
ListNode <Counter, $ + Counter*SIZEOF ListNode>
来填充这个链表(将NextPtr指向下一块ListNode大小的地方,由于定义时存储连续的,使用$+偏移量来指向下一个)

  • 无法直接[esi].NextPtr来访问,必须使用ListNode PTR [esi],因为无法表明其所属的结构

Str_trim过程拓展

Irvine32链接库里的Str_trim过程从空字节结束的字符串中移除所有与选定的尾部字符匹配的字符,这个过程的逻辑很有意思,因为程序需要检查很多种情况,以#作为尾字符为例:
1)字符串为空
2)字符串一个或多个尾字符的前面有其他字符,如"Hello#"
3)字符串只有一个字符,且为尾字符,如"#"
4)字符串不含尾部字符,如"Hello"
5)字符串在一个或多个尾部字符跟随一个或多个尾部字符,如"Hello##"
现在我们先研究一下其源码,以例子来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
INCLUDE Irvine32.inc
ShowString PROTO,
string_addr:PTR BYTE
StrTrim PROTO,
pString:PTR BYTE,
char: BYTE
GetLength PROTO,
ptrString:PTR BYTE

.data
test_1 BYTE "##Hello##", 0

.code
main PROC
INVOKE StrTrim, ADDR test_1, '#'
INVOKE ShowString, ADDR test_1
call WaitMsg
main ENDP

GetLength PROC USES edi,
ptrString: PTR BYTE
mov edi, ptrString
mov eax, 0

L1:
cmp BYTE PTR [edi], 0 ;判断是否结束
je L2
inc edi
inc eax ;eax返回长度
jmp L1
L2:
ret
GetLength ENDP

ShowString PROC USES eax edx,
string_addr:PTR BYTE

xor eax, eax ;清空eax
mov al ,'['
call WriteChar
mov edx, DWORD PTR string_addr
call WriteString
mov al, ']'
call WriteChar
call Crlf
ret
ShowString ENDP

StrTrim PROC USES eax ecx edi,
pString:PTR BYTE,
char: BYTE

mov edi, pString
INVOKE GetLength, pString
cmp eax, 0 ;长度是否为0
je L3
mov ecx, eax ;保存长度
dec eax
add edi, eax ;指向最后一个字符
L1:
mov al, [edi]
cmp al, char ;是否为匹配字符?
jne L2 ;不是,跳转
dec edi ;是,向前一位再次比较
loop L1
L2:
mov BYTE PTR [edi+1], 0 ;插入一个空字节
L3:
ret
StrTrim ENDP
END main

为了防止和库中冲突,我修改了函数名
留意StrTrim过程,写的很巧妙,倒着匹配,匹配到就插入一个0表示结束,没有就接着比

裁剪前导字符

现在要求删去给定前导字符,如"##Hello" --> “Hello”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
;只修改部分的StrTrim的代码
StrTrim PROC USES eax ecx edi,
pString:PTR BYTE,
char: BYTE

mov edi, pString
INVOKE GetLength, pString
cmp eax, 0 ;长度是否为0
je L3
mov ecx, eax ;保存长度
L1:
mov al, [edi]
cmp al, char ;是否为匹配字符?
jne L3 ;不是,跳转
push ecx
push edi
move:
mov al, [edi+1] ;向前移动一位
mov [edi], al
add edi, 1
loop move
pop edi
pop ecx
loop L1
L3:
ret
StrTrim ENDP

我们只需要在匹配上后把后面的依次向前移位就行,注意带上结尾的’0’
注意:mov [edi], [edi+1]是不行的,我竟然这么写了,无法内存到内存

去除一组字符

现在要求过滤一组字符串,使主调程序能从字符串末尾删除一组字符。
分析一下就是设置一个“指针”从后向前在过滤的字符串中寻找,有就将“指针”向前移一位,直到匹配不到,这个“指针”就是间接寻址的一个寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
StrTrim PROC USES eax ecx edi ebx esi,
pString:PTR BYTE,
pchar: PTR BYTE

mov edi, pString
INVOKE GetLength, pString
cmp eax, 0 ;长度是否为0
je L3
mov ecx, eax ;保存长度
dec eax
add edi, eax ;指向最后一个字符
INVOKE GetLength, pchar ;获取过滤字符串的长度
mov ebx, eax ;存在EBX里
L1:
mov al, [edi]
push ecx
mov ecx, ebx
mov esi, pchar
filtration:
cmp al, BYTE PTR [esi] ;是否为匹配字符?
je L2 ;是,跳出循环
add esi, 1
loop filtration
pop ecx ;别忘恢复ECX
jmp L4 ;扫一周也没匹配到,直接在当前尾部加0结束
L2:
sub edi, 1 ;前移1位
pop ecx
loop L1
L4:
mov BYTE PTR [edi+1], 0
L3:
ret
StrTrim ENDP
Author

Ctwo

Posted on

2019-08-16

Updated on

2020-10-25

Licensed under

Comments