我刚刚进入汇编(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');
}
如果这太简单了,可能会有一个更好的例子。有什么想法吗?
答案 0 :(得分:2)
嗯,首先,你需要一个很好的例子来吸引你的牙齿,分开什么时候和什么样的召唤惯例,什么时候不适用。首先让我们谈谈NASM本身的功能,如果我正确地提炼出你的问题,那就是你的目标:“一个半现实/实用的功能集”。
(假设Linux是操作系统 - Linux / FreeBSD / MIPS /等之间存在差异。)在NASM中,定义函数所需要做的就是提供label
,the function code
和ret
(返回)。
在汇编程序中调用汇编函数本身不需要特殊的calling convention
本身。 您负责根据您的功能在适当的位置提供正确的数据/地址。它可以是任何地方,寄存器,堆栈,内存地址等。当您在函数中处理数据时,只需以ret
(返回)结束。因此,NASM中汇编函数定义的基本语法是:
label:
suff...
ret
然后根据需要设置寄存器,并使用以下代码在代码中调用函数:
call label
从函数返回的寄存器的状态是但是函数留下了。因此,您还必须注意为任何将被函数调用破坏的数据或寄存器提供临时存储。
调用约定适用于与外部语言接口的地方(例如,在程序集内调用libc
函数,或从C中调用程序集例程/函数)。在这里,您有x86
和x86_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