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