x86函数调用公约对外行的一般结构?

时间:2014-12-22 03:43:13

标签: assembly

我刚刚进入汇编(OSX上的NASM中的x86-64),现在想看看如何在其中实现函数。

tl; dr 如何使用x86调用约定在汇编中实现半实际/实用的函数集?那么你将如何分解并解释这组功能的各个部分,以及它如何在装配中起作用呢?


我已经找到了一些不错的资源,但是没有一个能给出一个真实的例子,你可以真正沉浸其中。

这是一个快速类比。就像你正在学习ruby的动态本质一样,教程就是这样对你说的:

Run this in your console:

> puts "Hello World"

Now you can see how powerful ruby's dynamic nature truly can be.

就像 WHAT 。你在说什么,我不知道那个例子怎么做。

使用汇编和调用约定也是如此。互联网上没有任何例子可以提供有关在汇编中编写自己的函数的任何有意义的介绍。我到目前为止所挖掘的资源包括:

我也尝试在一些简单的C代码上看到像Ccc和clang这样的C编译器的输出,但它大多数时候看起来像they optimize out functions(还不确定)。

所以我的问题是,如何使用x86调用约定在汇编中实现半实际/实用的函数集?你会如何分解它并解释这些功能的各个部分,以及它如何在装配中起作用,对一个外行人?

我没有一个很好的示例问题试图实现,但也许只是一个简单的write函数,你可以传递任何字符串,并将其打印到stdout。 JavaScript中的等价物如下所示:

function write(string) {
  console.log(string);
}

function main() {
  write('Hello world');
}

如果这太简单了,可能会有一个更好的例子。有什么想法吗?

1 个答案:

答案 0 :(得分:2)

嗯,首先,你需要一个很好的例子来吸引你的牙齿,分开什么时候和什么样的召唤惯例,什么时候不适用。首先让我们谈谈NASM本身的功能,如果我正确地提炼出你的问题,那就是你的目标:“一个半现实/实用的功能集”。

(假设Linux是操作系统 - Linux / FreeBSD / MIPS /等之间存在差异。)在NASM中,定义函数所需要做的就是提供labelthe function coderet(返回)。

在汇编程序中调用汇编函数本身不需要特殊的calling convention本身。 负责根据您的功能在适当的位置提供正确的数据/地址。它可以是任何地方,寄存器,堆栈,内存地址等。当您在函数中处理数据时,只需以ret(返回)结束。因此,NASM中汇编函数定义的基本语法是:

label:
    suff...
    ret

然后根据需要设置寄存器,并使用以下代码在代码中调用函数:

call  label

从函数返回的寄存器的状态是但是函数留下了。因此,您还必须注意为任何将被函数调用破坏的数据或寄存器提供临时存储。

调用约定适用于与外部语言接口的地方(例如,在程序集内调用libc函数,或从C中调用程序集例程/函数)。在这里,您有x86x86_64的单独调用约定。这本身就是一个完全不同的讨论。所涉及的寄存器在评论中有解释,其中包含有关保留哪些地址,哪些地址被破坏,以及caller各自的责任以及callee负责的内容的其他细微之处。如果这是你的询问,请发表评论,我会看看我是否可以指出你正确的方向(这不是短期主题)。

下面有希望举一个例子,你可以“深入了解”有关构建半实际/实用功能集所需的基本构建模块,以便与NASM组装。除了上面概述的基本功能语法之外,NASM还提供了简单的macro功能,这些功能非常适合帮助增加或自动化您为其他方式编写函数的许多简单任务。 (适用相同的规则 - 您负责在呼叫之前设置数据/寄存器)。

下面是你在直接组装中实现的基本x86_64“hello world”,然后再通过函数调用,并通过宏进行格式化等补充。这些是你在构建函数集时必须使用的基本工具。如果您有疑问,请告诉我们:

; macro to print all or part of a string
; takes two arguments:
;  1. address of string
;  2. character to write to stdout
%macro  strn    2
        mov     rax, 1
        mov     rdi, 1
        mov     rsi, %1
        mov     rdx, %2
        syscall
%endmacro

section .data
    onln times 8 db 0xa     ; 8 newlines
    tab times 8 db 0x20     ; 8 spaces
    string1 db  0xa, "  Hello StackOverflow!!!", 0xa, 0xa, 0
    string2 db  "Hello Plain Assembly", 0  ; no pad or newlines

section .text
    global _start

    _start:
        ; first print sring1 with no functions and no macros
        ; calculate the length of string
        mov     rdi, string1        ; string1 to destination index
        xor     rcx, rcx            ; zero rcx
        not     rcx                 ; set rcx = -1
        xor     al,al               ; zero the al register (initialize to NUL)
        cld                         ; clear the direction flag
        repnz   scasb               ; get the string length (dec rcx through NUL)
        not     rcx                 ; rev all bits of negative results in absolute value
        dec     rcx                 ; -1 to skip the null-terminator, rcx contains length
        mov     rdx, rcx            ; put length in rdx
        ; write string to stdout
        mov     rsi, string1        ; string1 to source index
        mov     rax, 1              ; set write to command
        mov     rdi, rax            ; set destination index to rax (stdout)
        syscall                     ; call kernel

        ; now print string2 using 'strprn'
        mov     rdi, string2        ; put string2 in rdi (as need by strprn)
        call    strprn              ; call function strprn

        ; now let's setup a bit of formatting for string 2 & print it again
        strn    onln, 2             ; macro to output 2 newlines from 'onln' (string2 has none)
        strn    tab, 2              ; macro to indent by 2 chars (1st 2 spaces in tab)
        mov     rdi, string2        ; put string2 in rdi (as need by strprn)
        call    strprn              ; call function strprn
        strn    onln, 2             ; macro to output 2 newlines from 'onln' after sting2

        ; exit 
        xor     rdi,rdi             ; zero rdi (rdi hold return value)
        mov     rax, 0x3c           ; set syscall number to 60 (0x3c hex)
        syscall                     ; call kernel

; Two functions below:
; 'strsz'  (basic strlen())
; 'strprn' (basic puts())

; szstr computes the lenght of a string.
; rdi - string address
; rdx - contains string length (returned)
section .text
        strsz:
                xor     rcx, rcx                ; zero rcx
                not     rcx                     ; set rcx = -1 (uses bitwise id: ~x = -x-1)
                xor     al,al                   ; zero the al register (initialize to NUL)
                cld                             ; clear the direction flag
                repnz scasb                     ; get the string length (dec rcx through NUL)
                not     rcx                     ; rev all bits of negative -> absolute value
                dec     rcx                     ; -1 to skip the null-term, rcx contains length
                mov     rdx, rcx                ; size returned in rdx, ready to call write
                ret

; strprn writes a string to the file descriptor.
; rdi - string address
; rdx - contains string length
section .text
        strprn:
                push    rdi                     ; push string address onto stack
                call    strsz                   ; call strsz to get length
                pop     rsi                     ; pop string to rsi (source index)
                mov     rax, 0x1                ; put write/stdout number in rax (both 1)
                mov     rdi, rax                ; set destination index to rax (stdout)
                syscall                         ; call kernel
                ret

; compile & build
;
;  nasm -f elf64 -o hello-stack_64_wfns.o hello-stack_64_wfns.asm
;  ld  -o hello-stack_64_wfns hello-stack_64_wfns.o

<强>输出:

$ ./bin/hello-stack_64_wfns

  Hello StackOverflow!!!

Hello Plain Assembly

  Hello Plain Assembly