• NASM汇编学习系列(3)——多汇编文件间函数调用


    说明

    1. 本学习系列代码几乎完全摘自:asmtutor.com,如果英文可以的(也可以用谷歌浏览器翻译看),可以直接看asmtutor.com上的教程
    2. 本学习系列目录地址:https://www.cnblogs.com/whuwzp/p/nasm_contents.html
    3. 系统环境搭建:(我用的是ubuntu18.04.4 server,安装gcc、g++)
    sudo apt install nasm
    sudo apt install gcc-multilib
    

    0. 概览

    1. 承前:上一节,我们实现了sprint,但是如果其他asm文件也想用sprint函数怎么办?
    2. 启后:本节,分割功能,在functions.asm中完成sprint函数,在main.asm中调用它(只需要加%include functions.asm),这种方法就可以了。

    1. functions.asm: 函数实现

    以下代码摘自:https://asmtutor.com/#lesson3

    这里实现了slen(计算字符串长度)、sprint(打印)、sprintln(换行打印)、exit(安全退出)

    ;------------------------------------------
    ; int slen(String message)
    ; String length calculation function
    slen:
        push    ebx
        mov     ebx, eax
     
    nextchar:
        cmp     byte [eax], 0
        jz      finished
        inc     eax
        jmp     nextchar
     
    finished:
        sub     eax, ebx
        pop     ebx
        ret
     
     
    ;------------------------------------------
    ; void sprint(String message)
    ; String printing function
    sprint:
        push    edx
        push    ecx
        push    ebx
        push    eax
        call    slen
     
        mov     edx, eax
        pop     eax
     
        mov     ecx, eax
        mov     ebx, 1
        mov     eax, 4
        int     80h
     
        pop     ebx
        pop     ecx
        pop     edx
        ret
     
     
    ;------------------------------------------
    ; void sprintLF(String message)
    ; String printing with line feed function
    sprintLF:
        call    sprint
     
        push    eax         ; push eax onto the stack to preserve it while we use the eax register in this function
        mov     eax, 0Ah    ; move 0Ah into eax - 0Ah is the ascii character for a linefeed
        push    eax         ; push the linefeed onto the stack so we can get the address
        mov     eax, esp    ; move the address of the current stack pointer into eax for sprint
        call    sprint      ; call our sprint function
        pop     eax         ; remove our linefeed character from the stack
        pop     eax         ; restore the original value of eax before our function was called
        ret                 ; return to our program
     
     
    ;------------------------------------------
    ; void exit()
    ; Exit program and restore resources
    quit:
        mov     ebx, 0
        mov     eax, 1
        int     80h
        ret
    

    2. main.asm调用func.asm中的函数

    %include 'functions.asm'包含,然后就可以直接调用了。

    以下摘自:https://asmtutor.com/#lesson7

    ; Hello World Program (Print with line feed)
    ; Compile with: nasm -f elf helloworld-lf.asm
    ; Link with (64 bit systems require elf_i386 option): ld -m elf_i386 helloworld-lf.o -o helloworld-lf
    ; Run with: ./helloworld-lf
     
    %include        'functions.asm' ; 关键
     
    SECTION .data
    msg1    db      'Hello, brave new world!', 0h          ; NOTE we have removed the line feed character 0Ah
    msg2    db      'This is how we recycle in NASM.', 0h  ; NOTE we have removed the line feed character 0Ah
     
    SECTION .text
    global  _start
     
    _start:
     
        mov     eax, msg1
        call    sprintLF    ; NOTE we are calling our new print with linefeed function
     
        mov     eax, msg2
        call    sprintLF    ; NOTE we are calling our new print with linefeed function
     
        call    quit
    

    3. 函数参数传递问题和bug记录

    3.1 函数参数传递问题

    其实这里我们写的函数并不是严格的c中函数:

    1. call sprintLF:其实sprintlf只是一个代码段的偏移而已,call指令相当于push eip, jmp sprintlf
    2. 这里可以看出,我们是用eax传参的,但是c编译成程序的是用压栈的方式传参的,当然,这个并不影响功能的实现,只要我们自己约定好也OK,但是如果是在.c中调用.asm中的函数,在参数传递上就会有影响

    3.3 bug记录

    因为我是看了一下代码后,自己写,所以出现了很多bug,当然也学习类很多,记录一下:

    3.3.1 函数最后忘加ret

    func1:
    	xxx1
    	; 最后没加ret
    func2:
    	xxx2
    	ret
    

    因为忘了加ret,导致call func1,执行了xxx1,直接执行到了func2 的xxx2了。

    这也说明了代码段真的是连续的

    3.3.2 push和pop保持堆栈平衡

    以sprinlf为例,其中push了两次eax,所以最后也pop了两次,这样堆栈才能平衡,否则ret时执行pop eip, jmp eip就会跳转到错误的返回地址。

    sprintLF:
        call    sprint
     
        push    eax         ; push eax onto the stack to preserve it while we use the eax register in this function
        mov     eax, 0Ah    ; move 0Ah into eax - 0Ah is the ascii character for a linefeed
        push    eax         ; push the linefeed onto the stack so we can get the address
        mov     eax, esp    ; move the address of the current stack pointer into eax for sprint
        call    sprint      ; call our sprint function
        pop     eax         ; remove our linefeed character from the stack
        pop     eax         
        ret
    

    3.3.3 调用其他函数前用push和pop保存、还原寄存器内容

    以sprint为例:sprint要调用slen获取字符串长度,在call slen前,push了abcd寄存器到栈上保存,调用后又依次pop还原,是因为:slen中修改了ebx的内容,这样当slen返回后ebx的值就不是调用前的值了,可能会对接下来的运行造成影响,所以先都保存一下在栈上,调用后在还原。

    slen:
        push    ebx
        mov     ebx, eax ;修改了ebx
        xxx
        ret
    sprint:
        push    edx ;保存
        push    ecx ;保存
        push    ebx ;保存
        push    eax ;保存
        call    slen 
        mov     edx, eax ; 返回值为字符串长度,保存于eax,赋给edx,后面int 80 syscall会用到
        pop     eax ;还原
     
        mov     ecx, eax
        mov     ebx, 1
        mov     eax, 4
        int     80h
     
        pop     ebx ;还原
        pop     ecx ;还原
        pop     edx ;还原
        ret
    

    3.3.4 大端小端

    以sprintlf为例,这个函数先调用sprint打印要打印的内容,然后又调用sprint打印换行符。

    sprintLF:
        call    sprint
     
        push    eax         ; 保存eax
        mov     eax, 0Ah    ; 0x0Ah是换行'
    '
        push    eax         ; 此时esp内容为0A 00 00 00
        mov     eax, esp    ; 把esp地址给eax
        call    sprint 		; eax 作为参数,打印esp指向的内容
        pop 	eax			; 为了保持堆栈平衡(pop次数等于push次数)
        pop 	eax			; 还原eax 
        ret
    

    回忆下sprint是如何打印的,它先要找'获取字符串长度,可以疑问在于:0x0Ah只有一个字符,哪来的'?'

    因为esp的内容是0A 00 00 00,0A后面自然跟了一个00。

  • 相关阅读:
    [转] Actor生命周期理解
    [转] Linux History(历史)命令用法 15 例
    [转] CDH6 安装文章链接收集
    [转] org.scalatest.FunSuite Scala Examples
    [转] Mock以及Mockito的使用
    关于 maven 打包直接运行的 fat jar (uber jar) 时需要包含本地文件系统第三方 jar 文件的问题
    [转] flume使用(六):后台启动及日志查看
    [转] etcd 搭建与使用
    [转] 2018年最新桌面CPU性能排行天梯图(含至强处理器)
    让 Linux grep 的输出不换行
  • 原文地址:https://www.cnblogs.com/whuwzp/p/nasm_multifiles.html
Copyright © 2020-2023  润新知