Skip to content

Latest commit

 

History

History
158 lines (114 loc) · 4.51 KB

translation-Say-hello-to-x64-Assembly-part-7.md

File metadata and controls

158 lines (114 loc) · 4.51 KB
title date categories tags
译文: Say hello to x64 Assembly [part 7]
2016-08-17 08:39:10 -0700
style
Say hello to x64 Assembly
translation

C 与汇编的嵌套

Say hello to x86_64 Assembly 的第七篇咯,这里会看一下怎么在 C 中使用汇编。 事实上有三种方法:

  • 在 C 中调用汇编(Call assembly routines from C code)
  • 在汇编中调用 C(Call c routines from assembly code)
  • 在 C 中使用内嵌汇编(Use inline assembly in C code)

下面写三个简单的 Hello world 程序来演示这些。

在 C 中调用汇编

#include <string.h>

int main() {
    char* str = "Hello World\n";
    int len = strlen(str);
    printHelloWorld(str, len);
    return 0;
}

可以看到 C 代码中定义了两个变量:Hello world 字符串与其长度。接下来调用 printHelloWorld 这个汇编函数,并且把这两变量当作参数传递给了它。因为我们使用的是 x86_64 Linux 系统,所以必须知道 x86_64 Linux 是怎样传参的,这样我们便知道怎么写 printHelloWorld 了。当我们调用函数的时候,通过通过 rdi, rsi, rdx, rcx, r8, r9 六个通用寄存器传递前六个参数,其余的通过堆栈传递。这样我们便可以从 rdi 与 rsi 中获得前两个参数,从而进行系统调用,再通过 ret 指令返回:

global printHelloWorld

section .text
printHelloWorld:
    ;; 1 arg
    mov r10, rdi
    ;; 2 arg
    mov r11, rsi
    ;; call write syscall
    mov rax 1
    mov rdi 1
    mov rsi r10
    mov rdx, r11
    syscall
    ret

OK,这么编译(Makefile 文件):

build:
    nasm -f elf64 -o casm.o casm.asm
    gcc casm.o casm.c -o casm

内嵌汇编

这方法允许在 C 中直接使用汇编代码,不过有特殊的语法。大概是这样的:

asm [volatile] ("assembly code" : output operand: input operand : clobbeers);

在 gcc 的文档中,volatile 关键词:

典型的 asm 拓展语法的使用是操作输入来产生输出。然而,你的 asm 语法可能带来副作用。若这样的话,你可能需要使用 the volatile qualifier 来禁止某些优化。

每个操作数都由约束字符串修饰,他们在 C 表达式中被括号括起来。这里有一些约束规范:

  • r -- 在通用寄存器中变量的值保持不变
  • g -- 任何寄存器、内存或者立即数都被允许,除非那些寄存器并不是通用寄存器
  • f -- 浮点寄存器
  • m -- 内存操作数被允许操作任何种类的地址,这些地址都是一般机器支持的

hello world 是这样:

#include <string.h>

int main() {
    char* str = "Hello World\n";
    long len = strlen(str);
    int ret = 0;

    __asm__("movq $1, %%rax \n\t"
            "movq $1, %%rdi \n\t"
            "movq %1, %%rsi \n\t"
            "movl %2, %%edx \n\t"
            "syscall"
            : "=g"(ret)
            : "g"(str), "g" (len));

    return 0;
}

同先前的栗子一样,内嵌汇编也定义了同样的两个变量。首先,正如平时写 Hello world 汇编程序一样,我们把 1 放入 raxrdi 寄存器(写入系统调用标号与标准输出)。接下来对 rsirdi 做了相似的操作,但第一个操作数开始于 % 标志,而不是 $。这是因为 %1 代指的是 str,它是要输出的字符串,而字符串长度(len)则是 %2,这样我们便利用 %n 符号把 strlen 放入了 rsisdi,n 为 output operand。%% 为寄存器的前缀。

这是为了帮助 GCC 区分操作数与寄存器。操作数有单独的 % 前缀。

这么编译:

build:
    gcc casm.c -o casm

关于 GCC 的所有内嵌汇编文档,可以在找到。

在汇编中调用 C

最后一部分。打印一个 Hello world 的函数:

#include <stdio.h>

extern int print();

int print() {
    printf("Hello World\n");
    return 0;
}

可以在汇编代码中定义这个外部函数,然后正如先前文章中使用 call 指令一样调用它:

global _start

extern print

section .text

_start:
    call print
    mov rax, 60
    mov rdi, 0
    syscall

编译:

build:
    gcc -c casm.c -o c.o
    nasm -f elf64 casm -o casm.o
    ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc casm.o c.o -o casm

结语

...