第四节
函数
介绍函数(子程序)
函数 / 子程序 (functions/subroutines)是可重用的代码片段,程序可以调用它们来执行各种可重复的任务。函数是使用 标签 声明的,就像我们之前使用的那样(例如 _start:
),但是我们不使用 JMP
指令来访问它们,而是使用新的指令 CALL
。在运行函数后,也不会使用 JMP
指令返回到原程序。要从函数返回到原程序,使用指令 RET
。
为什么不直接用JMP
跳转
编写函数的好处就是可以重用它。如果我们想从代码中的任何地方使用函数,可能必须编写一些逻辑代码来确定我们从代码中跳到了哪里,并且应该跳回到哪里。这会使我们的代码中充斥着不需要的标签。但是,如果我们使用 CALL
和 RET
,程序会使用栈(stack)为我们处理这个问题。
介绍栈
栈(stack)是一种特殊类型的内存。它与我们以前使用过的内存类型相同,但在程序使用它的方式上很特殊。栈称为后进先出内存(Last In First Out,LIFO)。可以把栈想象成厨房里的一栈盘子,放在栈上的最后一个盘子也是你下次使用盘子时将从栈上取下的第一个盘子。
汇编程序中栈的不是存储盘子,而是存储值。你可以在堆栈上存储很多东西,比如变量、地址或其他程序。当调用函数时,我们需要使用栈来临时存储函数返回后要恢复的值。
函数需要使用的任何寄存器都应该将其当前值放入栈中,以便使用 PUSH
指令安全保存。然后,在函数完成其逻辑之后,这些寄存器可以使用 POP
指令恢复其原始值。这样便可保证在调用函数前后,寄存器中的任何值都没有变化。如果我们在函数中处理好这一点,我们就可以随意调用函数,而不用担心它们对寄存器做了什么更改。
CALL
和 RET
指令也使用栈。当 CALL
一个函数时,调用地址被push到栈上。函数结束时,RET
将这个地址从栈中pop,以跳回到调用位置。
; Hello World Program (Subroutines)
; 编译: nasm -f elf helloworld-len.asm
; 链接 (64 bit 系统需要 elf_i386 选项): ld -m elf_i386 helloworld-len.o -o helloworld-len
; 运行: ./helloworld-len
SECTION .data
msg db 'Hello, brave new world!', 0Ah
SECTION .text
global _start
_start:
mov eax, msg ; 将信息字符串地址移动到 EAX
call strlen ; 调用函数去计算字符串长度
mov edx, eax ; 函数将返回值放在 EAX
mov ecx, msg ; 与之前的代码相同
mov ebx, 1
mov eax, 4
int 80h
mov ebx, 0
mov eax, 1
int 80h
strlen: ; 这是我们第一个函数的声明
push ebx ; 将EBX中的值保存于栈上,因为strlen会使用该它
mov ebx, eax ; 将EAX中msg的地址移EBX(现在二者指向内存中同一处)
nextchar: ; 与第三节相同
cmp byte [eax], 0
jz finished
inc eax
jmp nextchar
finished:
sub eax, ebx
pop ebx ; 将栈上元素放回 EBX
ret ; 返回函数
~$ nasm -f elf helloworld-len.asm
~$ ld -m elf_i386 helloworld-len.o -o helloworld-len
~$ ./helloworld-len
Hello, brave new world!
标签:函数,汇编语言,mov,helloworld,学习,eax,len,使用
From: https://www.cnblogs.com/chai-yuan/p/17164804.html