平台为x86-64
系统调用是用户层调用linux服务的最常用的方式,但是大家通常使用封装好的库函数,比如libc提供的API进行使用,本篇文章使用嵌入式汇编实现time的系统调用。
首先使用libc编写一段程序做为对比:
#include<stdio.h>
#include<time.h>
int main(void)
{
time_t timer = 0;
struct tm *tblock;
timer = time(NULL); //获取当前时间
tblock = localtime(&timer);
char *str=asctime(tblock); //将tm结构体转换成字符串
printf("Local time is: %s", str);
return 0;
}
编译:
root@henry-002:/usr/test_code# gcc time_libc.c -o time_libc --static
执行:
root@henry-002:/usr/test_code# ./time_libc
Local time is: Tue Mar 16 16:48:38 2021
执行结果正常,说明使用libc调用time正常,然后反汇编elf文件:
root@henry-002:/usr/test_code# objdump -dlt time_libc > time_libc.dis
我们找到主要的执行部分:
000000000040105e <main>:
main():
40105e: 55 push %rbp
40105f: 48 89 e5 mov %rsp,%rbp
401062: 48 83 ec 20 sub $0x20,%rsp
401066: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
40106d: 00
40106e: bf 00 00 00 00 mov $0x0,%edi
401073: e8 e8 26 03 00 callq 433760 <time>
401078: 48 89 45 e8 mov %rax,-0x18(%rbp)
40107c: 48 8d 45 e8 lea -0x18(%rbp),%rax
401080: 48 89 c7 mov %rax,%rdi
401083: e8 c8 26 03 00 callq 433750 <localtime>
401088: 48 89 45 f0 mov %rax,-0x10(%rbp)
40108c: 48 8b 45 f0 mov -0x10(%rbp),%rax
401090: 48 89 c7 mov %rax,%rdi
401093: e8 c8 25 03 00 callq 433660 <asctime>
401098: 48 89 45 f8 mov %rax,-0x8(%rbp)
40109c: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010a0: 48 89 c6 mov %rax,%rsi
4010a3: bf 64 3b 49 00 mov $0x493b64,%edi
4010a8: b8 00 00 00 00 mov $0x0,%eax
4010ad: e8 0e 6e 00 00 callq 407ec0 <_IO_printf>
4010b2: b8 00 00 00 00 mov $0x0,%eax
4010b7: c9 leaveq
4010b8: c3 retq
4010b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
.............
0000000000433760 <time>:
time():
433760: b8 c9 00 00 00 mov $0xc9,%eax
433765: 0f 05 syscall
433767: c3 retq
433768: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
43376f: 00
根据以上分析,我们了解到了time的系统调用号是0xc9,嵌入式汇编如下:
#include<stdio.h>
#include<time.h>
int main(void)
{
time_t timer = 0;
struct tm *tblock;
//获取当前时间
asm volatile(
"mov $0,%%edi\n\t" //edi存储入参,入参为NULL,既0
"mov $0xc9,%%eax\n\t" //系统调用号为0xc9
"syscall\n\t" //执行系统调用
"mov %%eax,%0\n\t" //eax存储的是返回值,将返回值赋值给输出参数timer
:"=m"(timer) //输出为内存只写变量timer
);
tblock = localtime(&timer);
char *str=asctime(tblock); //将tm结构体转换成字符串
printf("Local time is: %s", str);
return 0;
}
编译:
root@henry-002:/usr/test_code# gcc time_syscall.c -o time_syscall --static
执行结果正常:
root@henry-002:/usr/test_code# ./time_syscall
Local time is: Tue Mar 16 16:41:18 2021
反汇编:
root@henry-002:/usr/test_code# objdump -dlt time_syscall > time_syscall.dis
找到主要的部分:
000000000040105e <main>:
main():
40105e: 55 push %rbp
40105f: 48 89 e5 mov %rsp,%rbp
401062: 48 83 ec 20 sub $0x20,%rsp
401066: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
40106d: 00
40106e: bf 00 00 00 00 mov $0x0,%edi
401073: b8 c9 00 00 00 mov $0xc9,%eax
401078: 0f 05 syscall
40107a: 89 45 e8 mov %eax,-0x18(%rbp)
40107d: 48 8d 45 e8 lea -0x18(%rbp),%rax
401081: 48 89 c7 mov %rax,%rdi
401084: e8 c7 26 03 00 callq 433750 <localtime>
401089: 48 89 45 f0 mov %rax,-0x10(%rbp)
40108d: 48 8b 45 f0 mov -0x10(%rbp),%rax
401091: 48 89 c7 mov %rax,%rdi
401094: e8 c7 25 03 00 callq 433660 <asctime>
401099: 48 89 45 f8 mov %rax,-0x8(%rbp)
40109d: 48 8b 45 f8 mov -0x8(%rbp),%rax
4010a1: 48 89 c6 mov %rax,%rsi
4010a4: bf 64 3b 49 00 mov $0x493b64,%edi
4010a9: b8 00 00 00 00 mov $0x0,%eax
4010ae: e8 0d 6e 00 00 callq 407ec0 <_IO_printf>
4010b3: b8 00 00 00 00 mov $0x0,%eax
4010b8: c9 leaveq
4010b9: c3 retq
4010ba: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
需要注意的地方是:
1.给系统调用传递参数,很多书籍和材料上写是用ebx,编译的时候需要使用32bit模式编译,既增加-m32编译参数,我用的是64bit平台,可能这部分有区别。
2.另外就是系统调用号,可能有区别,但是系统调用号会随着linux的版本升级而增加,libc到底是如何确定系统调用号的,需要再研究一下,后续有结果再更新。
3.使用edi传递入参必须是0,否则会执行错误。
4.syscall不能用int 80代替。