深入理解系统调用

Linux 系统架构

 Linux 有两种特权级模式:用户模式和内核模式。用户进程运行在用户模式上,可以通过第三方库和Linux 系统 API (其本质是调用了系统调用)或系统调用进入内核模式,来操作计算机硬件

模式切换的本质 (系统调用的本质)

系统模式的切换依赖于 CPU 提供的工作方式

一般来说,大部分 CPU 至少具有两种工作方式

  • 高特权级 (Ring 0):可以访问任意的数据,包括外围设备,比如网卡、硬盘等
  • 低特权级 (Ring 3):只能访问受限的内存,并且不允许访问外围设备,可被打断

系统模式的切换通过特殊的 CPU 指令发起 (int 0x80)

应用程序 (进程) 无法直接切换 CPU 的工作方式

系统调用是应用程序 (进程) 是请求模式切换的唯一方式

系统调用的真面目

通过 int 0x80 指令来执行系统调用

系统 API 的真面目

大部分的系统 API 都使用了系统调用 

系统调用 和 系统 API 的实现示例

SysCall 这个宏定义根据 type 和 cmd 来具体调用到内核的某个系统调用函数,param1 和 param2 为这个系统调用的两个参数

RegApp 这个函数是要运行某个任务,由于用户进程无法直接运行任务,所以需要通过系统调用借助内核来运行任务

系统调用的本质

program.c


void print(const char* s, int l);
void exit(int code);

void program()
{
    print("Hello World!\n", 13);
    exit(0);
}

void print(const char* s, int l)
{
    asm volatile (
        "movl $4, %%eax\n"
        "movl $1, %%ebx\n"
        "movl %0, %%ecx\n"
        "movl %1, %%edx\n"
        "int $0x80     \n"
        :
        : "r"(s), "r"(l)
        : "eax", "ebx", "ecx", "edx"
    );
}

void exit(int code)
{
    asm volatile (
        "movl $1, %%eax\n"
        "movl %0, %%ebx\n"
        "int $0x80     \n"
        :
        : "r"(code)
        : "eax", "ebx"
    );
}

这段代码没有使用任何的系统 API 接口,直接使用系统调用的方式实现了打印字符串的功能

gcc program.c -m32 -e program -nostartfiles  -o program.out

使用这个命令来编译这段代码,m32表示将目标i代码编译为32位架构的程序,-e program 表示 program 是程序执行的入口,-nostartfiles用于指示编译器不链接标准启动文件

程序执行的结果如下图所示:

成功通过系统调用在屏幕上打印出了 "Hello World!"

值得思考的问题

如何判断一个应用程序 (进程) 触发了系统调用?

可以通过 strace 命令来判断

strace - 系统调用探测器

strace 用于监控进程与内核的交互 (监控系统调用)

strace 用于追踪进程内部状态 (定位运行时问题)

strace 按序输出进程运行过程系统调用名称,参数和返回值

strace -  用法示例

strace 在程序性能分析中的应用

c.c

#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("Hello World!\n");

	return 0;
}

将 c.c 编译为 c.out ,我们用 strace 命令来追踪 c.out 和 program.out

strace -o c.log -T -tt ./c.out

strace -o program.log -T -tt ./program.out

-o 表示将结果重定向到一个文件里,-T 显示每个系统调用的开始时间,-tt 显示每个系统调用所花费的时间 (用 ms 表示)

通过打印可以看出 C 标准库的 printf 函数和直接通过系统调用的方式都是通过 write 系统调用来打印字符串的

strace -c  ./c.out

strace -c  ./program.out

我们通过 -c 选项来查看使用 C 标准库的 printf 函数和直接使用系统调用的方式执行所有系统调用所花费的时间

可以看出 C 标准库的 printf 函数比直接使用系统调用的方式,使用的系统调用次数更多,所花费的时间更长,所以要想我们程序的执行效率高,使用到系统调用的次数越少越好

strace - 用法详解

-e expr:指定一个表达式,用于控制如何追踪

strace 在程序逆向分析中的应用

fcopy.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int file_copy(const char* dst, const char* src)
{
    int dfd = open(dst, O_WRONLY|O_CREAT, 0600);
    int sfd = open(src, O_RDONLY);
    int ret = 0;
    
    if( dfd == -1 )
    {
        ret = -1;
    }
    else if( sfd == -1 )
    {
        ret = -2;
    }
    else if( (dfd != -1) && (sfd != -1) )
    {
        char buf[512] = {0};
        int len = 0;

        while( (len = read(sfd, buf, sizeof(buf))) > 0 )
        {
            write(dfd, buf, len);
        }

        close(dfd);
        close(sfd);
    }
    
    return ret;
}

int main(char argc, char* argv[])
{
    if( argc > 2 )
    {
        if( file_copy(argv[1], argv[2]) == 0) 
        {
            printf("copy completed\n");
        }
        else
        {
            printf("copy failed\n");
        }   
    }
    else
    {
        printf("parameter error\n");
    }

    return 0;
}

我们通过 strace 跟踪这个程序,通过这个程序所调用的系统调用来反向推测这个程序的意图

strace 输出结果都保存在了 fcopy.log 里

 这里的 log 就是不断地读取一个文件的内容来写入到另一个文件中,我们通过 strace 的输出结果可以反向推断出,这个程序的作用就是拷贝一个文件

通过 strace 来定位问题

当前路径下并不存在 test.txt 文件,所以拷贝会失败,我们看下 strace 能否找到问题 

通过 strace 的输出结果,我们可以直接定位到问题,拷贝失败的结果是因为不存在 test.txt 这个文件 

strace 程序数据分析

下面是 strace 默认参数的输出打印

有时候我们想分析一下程序运行过程中的一些读写的数据,strace 默认的一些参数选项没法更好的展示数据, 我们可以通过指定 strace 的参数选项来更好地通过我们的意图来展示数据

我们想每次都展示512字节的数据,可以指定 strace -s 512

我们想以 16 进制的方式展示数据,可以指定 strace -x 或 strace -xx

strace -x 表示不显示的字符以 16 进制的方式展示出来,可见的字符直接展示出来

strace -xx 表示直接以 16 进制的方式展示所有数据

 

我们想展示指定某个文件描述符读的数据,可以指定 strace -e read=set (如:strace -e read=4)

展示出从文件描述符 4 读到的数据,左边是 16 进制展示,右边是字符展示

我们想指定 strace 只输出文件操作,读取,关闭的系统调用,可以指定 strace -e trace=file,read,close

可以看出 write 系统调用的结果就被过滤掉了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值