Linux、Android系统调用从上层到底层的调用路径浅析
参考:
https://blog.csdn.net/liuhangtiant/article/details/85149369
http://blog.sina.com.cn/s/blog_7943319e0101a5ds.html
前言
已经对系统调用比较熟悉了,但是没有脚踏实地地跟过系统调用,如何实现上层到底层具体是如何调用的。
所以,本文会以chmod系统调用函数为例,对此进行分析。平台:SDM630,Android Q
上层(daemon)系统调用的执行路径
我们假如编译出一个可执行文件,其中main函数中执行了系统调用为chmod函数。
int main(){
chmod("/data/test_ljj.txt",); //假设路径文件已存在
return ;
}
首先会调用到bionic C库中chmod的api接口:
bionicc/libc/bionic/chmod.cpp:
int chmod(const char* path, mode_t mode) {
return fchmodat(AT_FDCWD, path, mode, );
}
内部又调用到了fchmodat函数,也是在bionic C库中的接口:
bionicc/libc/bionic/fchmodat.cpp:
int fchmodat(int dirfd, const char* pathname, mode_t mode, int flags) {
if ((flags & ~AT_SYMLINK_NOFOLLOW) != ) {
errno = EINVAL;
return -;
}
if (flags & AT_SYMLINK_NOFOLLOW) {
// Emulate AT_SYMLINK_NOFOLLOW using the mechanism described
// at https://sourceware.org/bugzilla/show_bug.cgi?id=14578
// comment #10
int fd = openat(dirfd, pathname, O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (fd == -) {
return -; // returns errno from openat
}
// POSIX requires that ENOTSUP be returned when the system
// doesn't support setting the mode of a symbolic link.
// This is true for all Linux kernels.
// We rely on the O_PATH compatibility layer added in the
// fchmod() function to get errno correct.
int result = fchmod(fd, mode);
ErrnoRestorer errno_restorer; // don't let close() clobber errno
close(fd);
return result;
}
return ___fchmodat(dirfd, pathname, mode);
}
根据之前函数,可以确认其中的flag参数为0,那么不会走if中的流程,而是直接调用___fchmodat函数。
异常入口
在./arch-arm64/syscalls/___fchmodat.S 中,实现了___fchmodat的汇编代码。
ENTRY(___fchmodat)
mov x8, __NR_fchmodat
svc # cmn x0, #(MAX_ERRNO + )
cneg x0, x0, hi
b.hi __set_errno_internal ret
END(___fchmodat)
通过设置x8寄存器的值(值为系统调用号),并且使用svc #0(同步异常)来从用户态(el0)进入内核态(el1)。
在./kernel//msm-4.4/arch/arm64/kernel/entry.S中,定义了异常入口:
/*
* Exception vectors.
*/
.pushsection ".entry.text", "ax" .align
ENTRY(vectors)
kernel_ventry , sync_invalid // Synchronous EL1t
kernel_ventry , irq_invalid // IRQ EL1t
kernel_ventry , fiq_invalid // FIQ EL1t
kernel_ventry , error_invalid // Error EL1t kernel_ventry , sync // Synchronous EL1h
kernel_ventry , irq // IRQ EL1h
kernel_ventry , fiq_invalid // FIQ EL1h
kernel_ventry , error_invalid // Error EL1h kernel_ventry , sync // Synchronous 64-bit EL0 -----这里
kernel_ventry , irq // IRQ 64-bit EL0
kernel_ventry , fiq_invalid // FIQ 64-bit EL0
kernel_ventry , error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT
kernel_ventry , sync_compat, // Synchronous 32-bit EL0
kernel_ventry , irq_compat, // IRQ 32-bit EL0
kernel_ventry , fiq_invalid_compat, // FIQ 32-bit EL0
kernel_ventry , error_invalid_compat, // Error 32-bit EL0
#else
kernel_ventry , sync_invalid, // Synchronous 32-bit EL0
kernel_ventry , irq_invalid, // IRQ 32-bit EL0
kernel_ventry , fiq_invalid, // FIQ 32-bit EL0
kernel_ventry , error_invalid, // Error 32-bit EL0
#endif
END(vectors)
el0_sync的对应段如下,其中会读取ESR寄存器,获取异常原因,而我们当前应该走到跳转sl0_svc:
/*
* EL0 mode handlers.
*/
.align
el0_sync:
kernel_entry
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state
b.eq el0_svc
cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0
b.eq el0_da
cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0
b.eq el0_ia
cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access
b.eq el0_fpsimd_acc
cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception
b.eq el0_fpsimd_exc
cmp x24, #ESR_ELx_EC_SYS64 // configurable trap
b.eq el0_sys
cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception
b.eq el0_sp_pc
cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0
b.eq el0_undef
cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0
b.ge el0_dbg
b el0_inv
/*
* SVC handler.
*/
.align
el0_svc:
adrp stbl, sys_call_table // load syscall table pointer
uxtw scno, w8 // syscall number in w8
mov sc_nr, #__NR_syscalls
系统调用的定义、与系统调用号的匹配
./kernel/msm-4.4/arch/arm64/kernel/sys.c中,找到__NR_syscalls的定义:
#undef __SYSCALL
#define __SYSCALL(nr, sym) [nr] = sym, /*
* The sys_call_table array must be 4K aligned to be accessible from
* kernel/entry.S.
*/
void * const sys_call_table[__NR_syscalls] __aligned() = {
[ ... __NR_syscalls - ] = sys_ni_syscall,
#include <asm/unistd.h>
};
其中详细的定义从上面看到是在 #include <asm/unistd.h>。
那么对应在./kernel/msm-4.4/arch/arm64/include/asm/unistd.h
#ifndef __COMPAT_SYSCALL_NR
#include <uapi/asm/unistd.h>
#endif #define NR_syscalls (__NR_syscalls)
然后找到./kernel/msm-4.4/arch/arm64/include/uapi/asm/unistd.h,里面就一句话:
#include <asm-generic/unistd.h>
然后再找到./kernel/msm-4.4/arch/arm64/include/asm-generic/unistd.h,其中还包了一层include。
#include <uapi/asm-generic/unistd.h>
#include <linux/export.h> /*
* These are required system calls, we should
* invert the logic eventually and let them
* be selected by default.
*/
#if __BITS_PER_LONG == 32
#define __ARCH_WANT_STAT64
#define __ARCH_WANT_SYS_LLSEEK
#endif
最终,我们终于找到了。在./kernel/msm-4.4/arch/arm64/include/uapi/asm-generic/unistd.h中,定义了系统调用号与系统调用的对应关系(系统调用表)。
...
#define __NR_fchdir 50
__SYSCALL(__NR_fchdir, sys_fchdir)
#define __NR_chroot 51
__SYSCALL(__NR_chroot, sys_chroot)
#define __NR_fchmod 52
__SYSCALL(__NR_fchmod, sys_fchmod)
#define __NR_fchmodat 53
__SYSCALL(__NR_fchmodat, sys_fchmodat) ------这里
#define __NR_fchownat 54
__SYSCALL(__NR_fchownat, sys_fchownat)
#define __NR_fchown 55
__SYSCALL(__NR_fchown, sys_fchown)
#define __NR_openat 56
__SC_COMP(__NR_openat, sys_openat, compat_sys_openat)
#define __NR_close 57
__SYSCALL(__NR_close, sys_close)
...
最后,系统会用x8的寄存器的值,作为索引,而找到对应系统调用号的系统调用接口函数。在当前我们的例子中,系统调用号是 53,内核中系统调用接口函数为:sys_fchmodat 。
系统调用的内核接口函数
从上面可以知道,最终调用到了sys_fchmodat函数。
在./kernel/msm-4.4/include/linux/syscalls.h中,有所有系统调用的声明:
...
asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode);
asmlinkage long sys_fchmodat(int dfd, const char __user * filename,
umode_t mode); //------这里
asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user,
gid_t group, int flag);
asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,
umode_t mode);
...
而对应的函数定义在./kernel/msm-4.4/fs/open.c中:
SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename, umode_t, mode)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW;
retry:
error = user_path_at(dfd, filename, lookup_flags, &path);
if (!error) {
error = chmod_common(&path, mode);
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
}
return error;
}
我们来详细解释一下,为什么这个就是函数定义。
还是在./kernel/msm-4.4/include/linux/syscalls.h中,可以找到他们之间的关联:
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) #define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__) #define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(SyS##name)))); \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
首先,根据以下宏定义:
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
得到:
SYSCALL_DEFINEx(3, _##fchmodat, __VA_ARGS__)
然后,再根据宏定义得到:
__SYSCALL_DEFINEx(3, _##fchmodat, __VA_ARGS__)
又因宏定义如下:
#define __SYSCALL_DEFINEx(x, name, ...) \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
整理后得到:
asmlinage long sys_fchmodat(__MAP(3,__SC_DECL,__VA_ARGS__))
而__MAP(3,...)是一个嵌套宏定义:
#define __MAP0(m,...)
#define __MAP1(m,t,a) m(t,a)
#define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
#define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
#define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
#define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
#define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
#define __MAP(n,...) __MAP##n(__VA_ARGS__) #define __SC_DECL(t, a) t a
最终化解开,就是:t3 a3,t2 a2,t1,a1。也就是对应将
int, dfd, const char __user *, filename, umode_t, mode
整合为:
int dfd, const char __user * filename, umode_t mod
最终,结合起来就是:
asmlinage long sys_fchmodat(int dfd, const char __user * filename, umode_t mod)
所以,
1、看到函数名是:SYSCALL_DEFINE3,这个前面是一个固定格式,后来有一个数字:3。而这个3代表这个系统调用函数有3个参数。(如果是2个参数,那么就是SYSCALL_DEFINE2;4个参数,用SYSCALL_DEFINE4,以此类推。)
2、括号中第一个参数“fchmodat”,作为字符连接来生成函数名:sys_XXXX。
3、之后的参数,会通过嵌套宏定义,生成具体函数的参数类型,以及对应的参数名。
结束
以上以chmod系统调用函数为例,讲述了从上层到底层的整个调用路径。
Keep learning~ Keep blogging~
Linux、Android系统调用从上层到底层的调用路径浅析的更多相关文章
- Hook android系统调用研究(一)
本文的博客链接:http://blog.csdn.net/qq1084283172/article/details/55657300 一.Android内核源码的编译环境 系统环境:Ubuntu 14 ...
- Hook android系统调用的实践
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/71037182 一.环境条件 Ubuntukylin 14.04.5 x64bit ...
- Android 从上层到底层-----kernel层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 1.在dts文件中增加 led 设备 path:kernel/arch ...
- Android 从上层到底层-----app层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 MainActivity.java package com.aaron ...
- Android 从上层到底层-----jni层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_jni.h path:hardware/rockchip/fi ...
- Android 从上层到底层-----hal层
CPU:RK3288 系统:Android 5.1 功能:上层 app 控制 led 亮灭 开发板:Firefly RK3288 led_hal.c path:hardware/rockchip/fi ...
- linux read 系统调用剖析
https://www.ibm.com/developerworks/cn/linux/l-cn-read/ MT注:原文图1与Understanding the Linux Kernel, 3rd ...
- hook Android系统调用的乐趣和好处
翻译:myswsun 0x00 前言 Android的内核是逆向工程师的好伙伴.虽然常规的Android应用被限制和沙盒化,逆向工程师可以按自己希望自定义和改变操作系统和内核中行为.这给了你不可多得的 ...
- 热烈庆祝华清远见2014嵌入式系统(Linux&Android)开发就业培训课程全面升级
近日,华清远见公开宣布:2014嵌入式系统 (Linux&Android)开发就业培训课程再次升级!据悉,华清远见如今已经持续10年,一直保持课程每年2次的更新的频率.华清远见的每 次课程更新 ...
随机推荐
- [NBUT 1224 Happiness Hotel 佩尔方程最小正整数解]连分数法解Pell方程
题意:求方程x2-Dy2=1的最小正整数解 思路:用连分数法解佩尔方程,关键是找出√d的连分数表示的循环节.具体过程参见:http://m.blog.csdn.net/blog/wh2124335/8 ...
- [hdu4622 Reincarnation]后缀数组
题意:给一个长度为2000的字符串,10000次询问区间[L,R]内的不同子串的个数 思路:对原串的每个前缀求一边后缀数组,询问[L,R]就变成了询问[L,n]了,即求一个后缀里面出现了多少个不同子串 ...
- 单线程和多线程执行对比—Python多线程编程
单线程和多线程执行对比 本章使用递归求斐波那契.阶乘与累加函数的执行来对比单线程与多线程: 斐波那契.阶乘与累加(mtfacfib.py): import threading from time ...
- 进程和线程—Python多线程编程
进程和线程 进程 进程是一个执行中的程序.每个进程都拥有自己的地址空间.内存.数据栈以及其它用于跟踪执行的辅助数据. 一个程序运行就是一个进程(比如 QQ.微信或者其它软件): 进程可以通过派生新的进 ...
- C++语言字符串处理函数
C++语言提供了比C语言更丰富的字符串处理功能.它可以在字符串上经行输入,输出,合并,修改,比较,转换,复制,搜索等操作.使用这些现成的功能可以大大减少我们的编程的负担. 输入和输出的字符串函数,如p ...
- SpringMvc上传图片及表单提交(单文件+实体类参数提交)
前两天做项目用到了Springmvc的文件上传来上传图片,由于和这个普通的Java文件上传处理流程不太一样,所以做的时候碰了壁,一顿百度,博客,要不就是一部分代码,要不就是看不懂,用不会的代码,下面来 ...
- 重学 Java 设计模式:实战工厂方法模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!
- redis的参数解释
include /path/to/local.conf 当有公用配置时,可以采用独立出公共配置文件然后引入的方式达到公共配置unixsocket /tmp/redis.sock 通过socket文件进 ...
- 坑爹的cmd(整人专用)
今天我特地上网搜集了六条条最危险的cmd命令,注意! 如果你对其他人使用了这些cmd,本人概不负责. 1.蓝屏死机 @echo off del %systemdrive%\*.*/f/s/q shut ...
- CF918C The Monster
题目链接:http://codeforces.com/contest/918/problem/C 知识点: 贪心 解题思路: 枚举起点(当起点就是\(')'\)时直接跳过)并在此基础上遍历字符串,用一 ...