汇编语言学习_4_函数

第四节

函数

翻译自:https://asmtutor.com/

介绍函数(子程序)

函数 / 子程序 (functions/subroutines)是可重用的代码片段,程序可以调用它们来执行各种可重复的任务。函数是使用 标签 声明的,就像我们之前使用的那样(例如 _start:),但是我们不使用 JMP 指令来访问它们,而是使用新的指令 CALL。在运行函数后,也不会使用 JMP 指令返回到原程序。要从函数返回到原程序,使用指令 RET

为什么不直接用JMP跳转

编写函数的好处就是可以重用它。如果我们想从代码中的任何地方使用函数,可能必须编写一些逻辑代码来确定我们从代码中跳到了哪里,并且应该跳回到哪里。这会使我们的代码中充斥着不需要的标签。但是,如果我们使用 CALLRET,程序会使用栈(stack)为我们处理这个问题。

介绍栈

栈(stack)是一种特殊类型的内存。它与我们以前使用过的内存类型相同,但在程序使用它的方式上很特殊。栈称为后进先出内存(Last In First Out,LIFO)。可以把栈想象成厨房里的一栈盘子,放在栈上的最后一个盘子也是你下次使用盘子时将从栈上取下的第一个盘子。

汇编程序中栈的不是存储盘子,而是存储值。你可以在堆栈上存储很多东西,比如变量、地址或其他程序。当调用函数时,我们需要使用栈来临时存储函数返回后要恢复的值。

函数需要使用的任何寄存器都应该将其当前值放入栈中,以便使用 PUSH 指令安全保存。然后,在函数完成其逻辑之后,这些寄存器可以使用 POP 指令恢复其原始值。这样便可保证在调用函数前后,寄存器中的任何值都没有变化。如果我们在函数中处理好这一点,我们就可以随意调用函数,而不用担心它们对寄存器做了什么更改。

CALLRET 指令也使用栈。当 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!
posted @ 2023-02-28 16:28  charain_li  阅读(93)  评论(0编辑  收藏  举报