我刚刚进入汇编(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 中,定义函数所需要做的就是提供一个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