NASM汇编语言基础_子程序subroutines
0x00 简介
子程序(又称 子例程)相当于函数。它们是可重用的代码段,程序可以调用它们来执行各种可重复的任务。子例程使用标签声明,就像我们以前使用过的标签一样( 例如,_start:)但是,我们不使用 JMP 指令来调用它们 —— 而是使用新的指令 CALL。在运行函数后,我们也不会使用 JMP 指令返回到我们的程序。为了从子程序返回到我们的程序,我们改用指令 RET。
0x01 原理
_那么我们为什么不使用 JMP 指令调用子程序:
编写子程序的一大好处是我们可以重复调用它。如果我们想要能够在代码中的任何位置使用子程序,我们必须编写一些逻辑来确定我们在代码中跳转到的位置 以及 应该跳回的位置。这将使我们的代码与不需要的标签混杂在一起。但是,如果我们使用 CALL 和 RET,程序集会使用称为堆栈的数据结构来处理此问题。
_什么是堆栈:
堆栈是一种特殊的数据结构。这与我们以前所使用过的内存类型相同,但它在程序如何使用内存方面很特别:堆栈是所谓的 后进先出(LIFO) 结构。一个形象的例子是我们把书籍叠在桌子上,最后放上去的那本书总是位于顶层,所以我们拿书的时候也从最后放上去的那本开始。
尽管程序集中的堆栈不存储在纸质书本中,堆栈用于存储值。你可以在堆栈上存储许多内容,例如变量、地址或其他程序。当我们调用函数(子程序)来临时存储稍后将被复原的值时,我们就需要运用到堆栈。
函数需要使用到的任何寄存器都应该将其当前的值存放到堆栈上,我们使用 PUSH 指令对其进行安全保存。然后,在函数完成执行之后,可以使用 POP 指令还原这些寄存器的原始值。这意味着寄存器中的任何值在调用函数之前与之后都是相同的。如果我们在子程序中处理这一点,我们可以调用函数,而不必担心它们对我们的寄存器进行了哪些更改。
CALL 和 RET 指令也使用堆栈。调用函数时,从调用它时 的地址将被 PUSH 到堆栈上。然后,RET 会弹出此地址,程序将跳回代码中的位置。这就是为什么你应该总是 JMP 到标签,但应该调用函数。
0x02 实现
;hello.asm
;子例程版本
;使用 nasm -f elf hello.asm 汇编
;使用 ld -m elf_i386 hello.o -o hello 链接
;使用 ./hello 运行
SECTION .data
msg db hello, ASSWECAN !!!, 0ah, 0dh, 0
SECTION .text
global _start
_start:
mov eax, msg ;把 msg 的初始地址存入eax寄存器
call strlen ;调用函数 strlen 来计算字符串长度
mov edx, eax ;strlen 函数将结果存放在了eax寄存器中
mov ecx, msg ;接下来的与上个程序相似
mov ebx, 1
mov eax, 4
int 80h
mov ebx, 0
mov eax, 1
int 80h
strlen:;这是我们编写的第一个函数声明
push ebx;我们将ebx的数据保存到堆栈中,这样它就不会被函数改变
mov ebx, eax;与上一个程序相似
nextchar:
cmp byte [eax], 0
jz finished
inc eax
jmp nextchar
finished:
sub eax, ebx
pop ebx;将堆栈中保存的ebx值返回到ebx寄存器中
ret;返回调用函数的地方
|NASM汇编语言基础_子程序subroutines
NASM汇编语言基础_子程序subroutines 函数调用 堆栈 汇编语言