linux内核系统调用宏SYSCALL_DEFINE

注:源码来自内核版本3.13
在Linux的系统中,系统调用在内核的入口函数都是以 sys_xxx 命名的(如:sys_read) ,但是如果在内核源码去搜索相关函数时,很遗憾,搜索不到 sys_xxx 函数的定义。因为Linux的系统调用对应的内核入口函数全部都是由一组 SYSCALL_DEFINE 的宏来定义的。
接下来,我们以系统调用socket()来讲述如何进行宏展开,得到以 sys_xxx 命名入口函数。
先看socket函数原型:

//file: man 2 socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

对应内核中的socket宏函数

//file: net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
	int retval;
	struct socket *sock;
	int flags;

	/* Check the SOCK_* constants for consistency.  */
	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;

	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
	if (retval < 0)
		goto out_release;

out:
	/* It may be already another descriptor 8) Not kernel problem. */
	return retval;

out_release:
	sock_release(sock);
	return retval;
}

关注第一行

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

这里,3 表示此系统调用有3个参数,第一个参数是用户层调用的函数名,接下来分别是socket()函数的第一个参数的类型和名称,第二个参数的类型和名称,以此类推。

//file: include/linux/syscalls.h
#define SYSCALL_DEFINE0(sname)					\
	SYSCALL_METADATA(_##sname, 0);				\
	asmlinkage long sys_##sname(void)

#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__)
  1. ## 表示将左右的符号拼接在一起。
  2. __VA_ARGS__ 表示前面 ... 里面的可变参数。
  3. SYSCALL_METADATA 是调试时跟踪系统调用的,与主线无关,跳过。
  4. asmlinkage 表示将函数参数存放在局部栈中(x86常用),FASTCALL 表示将函数参数存放在R0~R4通用寄存器中(arm常用)。通常是系统调用的函数需要加 armlinkage

展开1

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 
---->   SYSCALL_DEFINEx(3, _socket, int, family, int, type, int, protocol)
---->  SYSCALL_METADATA(socket, 3, int, family, int, type, int, protocol)  \  //这个宏非主线,不展开
		__SYSCALL_DEFINEx(3, socket, int, family, int, type, int, protocol)

__SYSCALL_DEFINEx 相关宏定义:

#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 __stringify_1(x...)	#x
#define __stringify(x...)	__stringify_1(x)
  1. __attribute__ 语法为 GNU C 的特性,是个编译器指令,告诉编译器声明的特性,或者让编译器进行更多的错误检查和高级优化。可以用来设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。 __attribute__ 语法格式为:__attribute__ ((attribute)) 使用__attribute__的时候,只能函数/变量/类型的声明处使用__attribute__,并且在 ; 前。
  2. 属性 alias 其实就是给函数起个别名。语法格式为:__attribute__ ((alias("xxx")))

__SYSCALL_DEFINEx为主要的实现过程,包含三个函数,sys_socket,SyS_socket,SYSC_socket。其中,sys_socket 为 SyS_socket 的别名,SyS_socket 调用了 SYSC_socket,SYSC_socket 后面没有分号,其定义在调用SYSCALL_DEFINE3时给出。因此,sys_socket / SyS_socket -> SYSC_socket,前面的封装并不是调用过程。

展开2

__SYSCALL_DEFINEx(3, socket, int, family, int, type, int, protocol)
---->
	asmlinkage long sys_socket(__MAP(3,__SC_DECL,int,family,int,type,int,protocol)) \
		__attribute__((alias("SyS_socket")));		\
	static inline long SYSC_socket(__MAP(3,__SC_DECL,int,family,int,type,int,protocol));	\
	asmlinkage long SyS_socket(__MAP(3,__SC_LONG,int,family,int,type,int,protocol));	\
	asmlinkage long SyS_socket(__MAP(3,__SC_LONG,int,family,int,type,int,protocol))	\
	{								\
		long ret = SYSC_socket(__MAP(3,__SC_CAST,int,family,int,type,int,protocol));	\
		__MAP(3,__SC_TEST,int,family,int,type,int,protocol);				\
		__PROTECT(3, ret,__MAP(3,__SC_ARGS,int,family,int,type,int,protocol));	\
		return ret;						\
	}								\
	static inline long SYSC_socket(__MAP(3,__SC_DECL,int,family,int,type,int,protocol))

其它一些宏定义

/*
 * __MAP - apply a macro to syscall arguments
 * __MAP(n, m, t1, a1, t2, a2, ..., tn, an) will expand to
 *    m(t1, a1), m(t2, a2), ..., m(tn, an)
 * The first argument must be equal to the amount of type/name
 * pairs given.  Note that this list of pairs (i.e. the arguments
 * of __MAP starting at the third one) is in the same format as
 * for SYSCALL_DEFINE<n>/COMPAT_SYSCALL_DEFINE<n>
 */
#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
#define __TYPE_IS_LL(t) (__same_type((t)0, 0LL) || __same_type((t)0, 0ULL))
#define __SC_LONG(t, a) __typeof(__builtin_choose_expr(__TYPE_IS_LL(t), 0LL, 0L)) a
#define __SC_CAST(t, a)	(t) a
#define __SC_ARGS(t, a)	a
#define __SC_TEST(t, a) (void)BUILD_BUG_ON_ZERO(!__TYPE_IS_LL(t) && sizeof(t) > sizeof(long))

#ifdef CONFIG_FTRACE_SYSCALLS
#define __SC_STR_ADECL(t, a)	#a
#define __SC_STR_TDECL(t, a)	#t

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)

  1. 借用 gcc 内置函数 __builtin_choose_expr__builtin_types_compatible_p 可以帮助c来实现函数重载。
    首先对这两个函数功能做下介绍:
    __builtin_choose_expr(expr,expr1,expr2) 与c语言:?运算符有些类似,如果expr表达式为真,那么返回expr1,否则返回expr2。
    __builtin_types_compatible_p(type1,type2) 测试type1与type2是否相同,如果相同返回真,否则返回假。

展开3

__MAP(3,__SC_DECL,int,family,int,type,int,protocol)
---> __MAP3(__SC_DECL,int,family,int,type,int,protocol)
---> __SC_DECL(int,family), __MAP2(__SC_DECL,int,type,int,protocol)
---> __SC_DECL(int,family),__SC_DECL(int,type),__MAP1(__SC_DECL,int,protocol)
---> __SC_DECL(int,family),__SC_DECL(int,type),__SC_DECL(int,protocol)
---> int family, int type, int protocol

__MAP(3,__SC_LONG,int,family,int,type,int,protocol)
---> __MAP3(__SC_LONG,int,family,int,type,int,protocol)
---> __SC_LONG(int,family), __MAP2(__SC_LONG,int,type,int,protocol)
---> __SC_LONG(int,family),__SC_LONG(int,type),__MAP1(__SC_LONG,int,protocol)
---> __SC_LONG(int,family),__SC_LONG(int,type),__SC_DECL(int,protocol)
---> long family, long type, long protocol

__MAP(3,__SC_CAST,int,family,int,type,int,protocol)
---> __SC_CAST(int,family),__SC_CAST(int,type),__SC_CAST(int,protocol)
---> (int)family, (int)type, (int)protocol

__MAP(3,__SC_ARGS,int,family,int,type,int,protocol)
---> __SC_ARGS(int,family),__SC_ARGS(int,type),__SC_ARGS(int,protocol)
---> family,type,protocol

__MAP(3,__SC_TEST,int,family,int,type,int,protocol)
---> (void)BUILD_BUG_ON_ZERO(0), (void)BUILD_BUG_ON_ZERO(0), (void)BUILD_BUG_ON_ZERO(0)

__PROTECT(x, ret,__MAP(x,__SC_ARGS,int, family, int, type, int, protocol))
---> asmlinkage_protect(x, ret, family, type, protocol)

将展开3带入展开2得展开4

__SYSCALL_DEFINEx(3, socket, int, family, int, type, int, protocol)
---->
	asmlinkage long sys_socket(int family, int type, int protocol) \
		__attribute__((alias("SyS_socket")));		\
	static inline long SYSC_socket(int family, int type, int protocol);	\
	asmlinkage long SyS_socket(long family, long type, long protocol);	\
	asmlinkage long SyS_socket(long family, long type, long protocol)	\
	{								\
		long ret = SYSC_socket((int)family, (int)type, (int)protocol);	\
		(void)BUILD_BUG_ON_ZERO(0),(void)BUILD_BUG_ON_ZERO(0),(void)BUILD_BUG_ON_ZERO(0);				\
		asmlinkage_protect(x, ret, family, type, protocol);	\
		return ret;						\
	}								\
	static inline long SYSC_socket(int family, int type, int protocol)

将展开4和展开1带入socket宏函数

//file: net/socket.c
SYSCALL_METADATA(socket, 3, int, family, int, type, int, protocol)  \ //这个宏非主线,不展开
asmlinkage long sys_socket(int family, int type, int protocol) \
	__attribute__((alias("SyS_socket")));		\
static inline long SYSC_socket(int family, int type, int protocol);	\
asmlinkage long SyS_socket(long family, long type, long protocol);	\
asmlinkage long SyS_socket(long family, long type, long protocol)	\
{								\
	long ret = SYSC_socket((int)family, (int)type, (int)protocol);	\
	(void)BUILD_BUG_ON_ZERO(0),(void)BUILD_BUG_ON_ZERO(0),(void)BUILD_BUG_ON_ZERO(0);				\
	asmlinkage_protect(x, ret, family, type, protocol);	\
	return ret;						\
}								\
static inline long SYSC_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;
	int flags;

	/* Check the SOCK_* constants for consistency.  */
	BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
	BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
	BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

	flags = type & ~SOCK_TYPE_MASK;
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;
	type &= SOCK_TYPE_MASK;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;

	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
	if (retval < 0)
		goto out_release;

out:
	/* It may be already another descriptor 8) Not kernel problem. */
	return retval;

out_release:
	sock_release(sock);
	return retval;
}

这时候我们熟悉的函数sys_socket函数就出现了。
内核进行多次宏替换,主要是将系统调用的参数统一为long类型来接收,然后再强转为本来参数类型。这样做的目的是解决Linux的CVE-2009-0029漏洞,该漏洞的大概意思是说:在Linux 2.6.28及以前版本的内核中,IBM/S390、PowerPC、Sparc64以及MIPS 架构64位平台的ABI要求在系统调用时,用户空间程序将系统调用中32位的参数存放在64位的寄存器中要做到正确的符号扩展,但是用户空间程序却不能保证做到这点,这样就会可以通过向有漏洞的系统调用传送特制参数便可以导致系统崩溃或获得权限提升。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值