x86 函数调用约定外行的一般结构



我刚刚进入汇编(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.

就像什么。你在说什么,从那个例子中我不知道如何做任何事情。

大会

和召集大会也发生了同样的情况。互联网上没有一些示例可以提供任何有意义的介绍,以在汇编中编写自己的函数。到目前为止,我挖掘的资源包括:

  • http://x86-64.org/documentation/abi.pdf(关于函数调用约定的第 3.2 节(
  • http://codearcana.com/posts/2013/05/21/a-brief-introduction-to-x86-calling-conventions.html
  • http://en.wikibooks.org/wiki/X86_Disassembly/Calling_Conventions
  • http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames
  • http://en.wikibooks.org/wiki/X86_Disassembly/Calling_Convention_Examples
  • http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_Calling_Conventions
  • http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/

我也尝试在一些简单的 C 代码上查看 cc 和 clang 等 C 编译器的输出,但看起来它们大部分时间都优化了函数(还不确定(。

所以我的问题是,如何使用 x86 调用约定在汇编中实现一组半现实/实用的函数?你会如何分解它,并向外行解释这组函数的各个部分,以及它在汇编中是如何工作的?

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

function write(string) {
  console.log(string);
}
function main() {
  write('Hello world');
}

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

好吧,首先,你需要一个很好的例子来深入了解并区分何时以及何时适用以及何时适用调用约定。首先,让我们谈谈 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

最新更新