素槿
Published on 2025-08-22 / 2 Visits
0

AT&T汇编调用系统调用的三种方式

0. 系统环境

内核和系统版本

$ uname -srvpo
Linux 5.4.0-48-generic #52-Ubuntu SMP Thu Sep 10 10:58:49 UTC 2020 x86_64 GNU/Linux

GCC版本

$ gcc -v
gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)

汇编器版本

$ as --version
GNU assembler (GNU Binutils for Ubuntu) 2.34

32位系统系统调用表: /usr/include/x86_64-linux-gnu/asm/unistd_32.h

64位系统系统调用表:/usr/include/x86_64-linux-gnu/asm/unistd_64.h

这里以一个简单的helloworld程序来说明

#include<stdio.h>
int main()
{
	printf("hello world");
	return 0;
}

1. 方式一:int 中断

# helloworld_int.s
.data                           # 数据段声明
# 系统调用号
.equ SYS_WRITE, 4
.equ SYS_EXIT,  1

# 定义要输出的字符串
msg : .string "Hello World!\n"  # 要输出的字符串
len = . - msg                   # 字符串长度
.text                           # 代码段声明
.global _start                  # 指定入口函数

_start:
    # write(1, msg, len)
    mov     $SYS_WRITE, %rax    # 系统调用号(sys_write)
    mov     $1, %rbx            # 参数一:文件描述符(stdout)
    mov     $msg, %rcx          # 参数二:要显示的字符串
    mov     $len, %rdx          # 参数三:字符串长度
    int     $0x80

    # exit(0)
    mov     $SYS_EXIT, %rax     # 系统调用号(sys_exit)
    mov     $0, %rbx            # 参数一:退出代码
    int     $0x80               # 调用内核功能

汇编执行:

$ as helloworld_int.s -o helloworld.o
$ ld helloworld.o -o helloworld
$ ./helloworld 
Hello World!

在上面的汇编代码中,%rax存放系统的调用号,%rbx,%rcx,%rdx分别存放第一,第二,第三个参数。

使用这种方式调用系统调用的时候要注意,系统调用表的编号对应的是32位的系统系统对应表.

2. 方式二:syscall

# helloworld_syscall.s
.data                           # 数据段声明

# 系统调用号
.equ SYS_WRITE, 1
.equ SYS_EXIT,  60

# 定义要输出的字符串
msg : .string "Hello World!\n"  # 要输出的字符串
len = . - msg                   # 字符串长度

.text                           # 代码段声明
.global _start                  # 指定入口函数
_start:
    # write(1, msg, len)
    mov     $len, %rdx          # 参数三:字符串长度
    mov     $msg, %rsi          # 参数二:要显示的字符串
    mov     $1, %rdi            # 参数一:文件描述符(stdout)
    mov     $SYS_WRITE, %rax    # 系统调用号(sys_write)
    syscall                     # 调用内核功能

    # exit(0)
    mov     $0, %rdi            # 参数一:退出代码
    mov     $SYS_EXIT, %rax     # 系统调用号(sys_exit)
    syscall                     # 调用内核功能

汇编执行:

$ as helloworld_syscall.s -o helloworld_syscall.o
$ ld helloworld_syscall.o -o helloworld_syscall 
$ ./helloworld_syscall 
Hello World!

需要注意的是syscall和int参数的存放位置是不一样的。在上面的汇编代码中,%rax存放系统的调用号,%rdi,%rsi,%rdx分别存放第一,第二,第三个参数。

使用这种方式的时候使用的是64位系统对应的系统调用表.

3. 方式三:CALL libc库

前面的方法只能输出固定的字符串,如果想输出格式化字符串,我们可以链接C库从而调用大量的C库函数。

# helloworld_lib.s
.section .data

number:
    .ascii "number:%d\n\0"

.section .text
.globl  _start
_start:
    mov     $number, %rdi
    mov		$99, %rsi
    call    printf
    mov     $0, %rdi
    call    exit

# -lc : 使用库c
# -I  : 加载链接库,并链接到程序

使用以下命令汇编和链接到C库:

$ as helloworld_lib.s -o helloworld_lib.o
$ ld -I /lib64/ld-linux-x86-64.so.2 -o helloworld_lib helloworld_lib.o -lc

以上的命令中 -I /lib64/ld-linux-x86-64.so.2 表示使ld-linux-x86-64.so.2作为动态链接器-lc表示链接到C库

执行:

$ ./helloworld_lib
number:99

4. GCC

使用GCC编译汇编代码可以自动链接C库。

使用GCC编译和上面的方法没有太大的区别。

但是有两点需要注意:

- 用GCC汇编时需要指定入口函数为main,否则会报错。

- 用GCC编译时,GCC会在main函数执行前调用libc库进行一些初始化操作,会修改一些程序的初始栈帧,比如程序的启动参数等,可能会导致无法正确获取程序的启动参数。

# helloworld_syscall_gcc.s
.section .data
.section .text
msg:
    .string "hello world\n"
.globl main
main:
    # printf("hello world\n")
    mov     $0, %rax        # 必须将eax置零才能成功 call printf
    mov     $msg, %rdi
    call    printf
    # exit(0)
    mov     $0, %rdi    
    call    exit

汇编运行

$ gcc -no-pie helloworld_syscall_gcc.s
$ ./a.out 
hello world