理解系统调用的关键在于洞悉系统调用号是联系用户模式与内核模式的纽带。而在Solaris x64平台上,系统调用号被保存在寄存器RAX中,从用户模式传递到内核模式。一旦进入内核模式,内核的sys_syscall入口程序就根据保存在RAX中的系统调用号,从内核维护的系统调用表(sysent)中查询出对应的系统调用处理程序,从而进行系统调用。系统调用最多支持6个参数,参数被顺序保存在寄存器RDI, RSI, RDX, RCX, R8, R9中完成传递。另外,从用户模式陷入内核模式,通过汇编指令syscall实现切换,而从内核模式返回到用户模式,则通过汇编指令sysret完成切换。

1 系统调用概述

1.1 什么是系统调用
在现代操作系统中,用户的应用程序访问并使用内核所提供的各种服务的途径被称之为系统调用(syscall)。

1.2 为什么需要系统调用
第一,系统调用可以为用户空间提供访问硬件资源的统一接口,以至于用户程序不必去关注具体的硬件操作。比如,读写文件时,用户完全没有必要关心文件存放在何种磁盘上,也不用关心文件在何种文件系统上。
第二,系统调用可以对操作系统进行保护,保证系统的稳定和安全。系统调用的存在规定了用户进程进入操作系统内核的具体方式。换言之,用户进程访问内核的路径是事先规定好了的,只能从规定的位置进入内核,而不允许随便跳入内核。有了这样的进入内核的统一访问路径上的限制,才能充分保证内核的安全。

1.3 系统调用与C库函数的关系
内核提供的系统调用在C库中都有相应的封装函数。系统调用与其封装的C库函数名称常常相同。例如: modctl系统调用在C库中的封装函数即为modctl函数,其实现位于modctl.s汇编文件中。

1.4 系统调用与系统命令的关系
系统命令位于C库函数的上一层,是利用C库函数实现的可执行程序。例如: 命令modinfo调用C库函数modctl()查询内核模块的信息。而C库函数封装了进入内核的系统调用,modctl()使用syscall指令(有别于int 0x80, 是一种快速系统调用指令)进入内核。

1.5 系统调用与系统函数的关系
内核函数与C库函数的区别仅仅是内核函数在内核中实现,因此必须遵循内核编程的规则。系统调用最终必须具有明确的操作。用户应用程序通过系统调用进入内核后,会执行系统调用对应的内核函数,也就是系统调用服务例程。例如:modctl系统调用的服务例程是内核函数modctl()。

系统调用过程如下图所示:

2 Solaris x64系统调用实现原理

Solaris 支持x64和sparc两种平台,目前内核都是64位,但是支持32位和64位的应用程序,因此,32位和64位的系统调用都是支持的。为简单起见,接下来的讨论只阐述x64平台上的64位系统调用。

2.1 AMD64 ABI基础

理解Solaris X64系统调用,不可避免地需要了解一下基本的AMD64 ABI。Solaris x64实现遵循的ABI文档是:
System V Application Binary Interface, AMD64 Architecture Processor Supplement
这里使用简化的ABI文档: http://www.x86-64.org/documentation/abi.pdf

A. AMD64 Linux Kernel Conventions
...
A.2.1 Calling Conventions The Linux AMD64 kernel uses internally the same calling conventions as user-level applications (see section 3.2. for details). User-level applications that like to call system calls should use the functions from the C library. The interface between the C library and the Linux kernel is the same as for the user-level applications with the following differences: . User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9. . A system-call is done via the syscall instruction. The kernel destroys registers %rcx and %r11. . The number of the syscall has to be passed in register %rax. . System-calls are limited to six arguments, no argument is passed directly on the stack. . Returning from the syscall, register %rax contains the result of the system-call. A value in the range between - and - indicates an error, it is -errno. . Only values of class INTEGER or class MEMORY are passed to the kernel.

另外,来自d3s.mff.cuni.cz/teaching/crash_dump_analysis的slides可以作为参考。 【贴两张主要的截图】

下面给出一个内核函数反汇编后的例子帮助理解ABI。

函数原型:
ibt_status_t ibt_suggest_alt_path(ibt_channel_hdl_t channel,
ibt_execution_mode_t mode,
ibt_suggest_alt_path_info_t *alt_path,
void *priv_data,
ibt_priv_data_len_t priv_data_len,
ibt_spr_returns_t *ret_args); 用mdb -k进入内核反汇编
root# mdb -k
> ibt_suggest_alt_path::dis
ibt_suggest_alt_path: pushq %rbp ; save rbp
ibt_suggest_alt_path+1: movq %rsp,%rbp ;
ibt_suggest_alt_path+4: subq $0x30,%rsp ;
ibt_suggest_alt_path+8: movq %rdi,-0x8(%rbp) ; arg1 : rdi
ibt_suggest_alt_path+0xc: movq %rsi,-0x10(%rbp) ; arg2 : rsi
ibt_suggest_alt_path+0x10: movq %rdx,-0x18(%rbp) ; arg3 : rdx
ibt_suggest_alt_path+0x14: movq %rcx,-0x20(%rbp) ; arg4 : rcx
ibt_suggest_alt_path+0x18: movq %r8,-0x28(%rbp) ; arg5 : r8
ibt_suggest_alt_path+0x1c: movq %r9,-0x30(%rbp) ; arg6 : r9
ibt_suggest_alt_path+0x20: pushq %rbx ; save rbx
ibt_suggest_alt_path+0x21: pushq %r12 ; save r12
ibt_suggest_alt_path+0x23: pushq %r13 ; save r13
ibt_suggest_alt_path+0x25: pushq %r14 ; save r14
ibt_suggest_alt_path+0x27: pushq %r15 ; save r15
...
ibt_suggest_alt_path+0xa11: popq %r15 ; restore r15
ibt_suggest_alt_path+0xa13: popq %r14 ; restore r14
ibt_suggest_alt_path+0xa15: popq %r13 ; restore r13
ibt_suggest_alt_path+0xa17: popq %r12 ; restore r12
ibt_suggest_alt_path+0xa19: popq %rbx ; restore rbx
ibt_suggest_alt_path+0xa1a: leave ; restore rsp, rbp
ibt_suggest_alt_path+0xa1b: ret ;
> $q // leave == movq %rbp, %rsp + popq %rbp

2.2 系统调用号

每一个系统调用都有一个独一无二的系统调用号。操作系统最多支持512个系统调用。如果一个系统调用被废弃,那么它对应的系统调用号将被保留,而不能分配给新的系统调用使用。所有系统调用号位于文件/etc/name_to_sysnum中。ABI规定了系统调用号是由寄存器rax传递给内核的,例如: modctl的系统调用号为152 (=0x98), 从modctl::dis的输出中我们可以看出,在执行syscall指令之前,%eax == 0x98。

root# egrep "modctl" /etc/name_to_sysnum
modctl 152 root# echo "modctl::dis" | mdb /lib/64/libc.so.1
modctl: movq %rcx,%r10
modctl+3: movl $0x98,%eax
modctl+8: syscall
modctl+0xa: jb -0x126d30 <__cerror>
modctl+0x10: xorq %rax,%rax
modctl+0x13: ret

- modctl()的实现可参见usr/src/lib/libc/common/sys/modctl.s

有关系统调用号的定义,见源文件usr/src/uts/common/sys/syscall.h,
例如:
#define     SYS_modctl      152
内核在进入sys_syscall()后,根据寄存器rax中存储的系统调用号查找相应的系统调用内核函数。

2.3 系统调用表
      Solaris内核维护了一张系统调用表,表中的每一个元素是一个struct sysent。

2.3.1 结构体struct sysent

 /*
322 * Structure of the system-entry table.
323 *
324 * Changes to struct sysent should maintain binary compatibility with
325 * loadable system calls, although the interface is currently private.
326 *
327 * This means it should only be expanded on the end, and flag values
328 * should not be reused.
329 *
330 * It is desirable to keep the size of this struct a power of 2 for quick
331 * indexing.
332 */
struct sysent {
char sy_narg; /* total number of arguments */
#ifdef _LP64
unsigned short sy_flags; /* various flags as defined below */
#else
unsigned char sy_flags; /* various flags as defined below */
#endif
int (*sy_call)(); /* argp, rvalp-style handler */
krwlock_t *sy_lock; /* lock for loadable system calls */
int64_t (*sy_callc)(); /* C-style call hander or wrapper */
};
root# mdb -k
> ::sizeof struct sysent
sizeof (struct sysent) = 0x20
> ::offsetof sysent sy_callc
offsetof (sysent, sy_callc) = 0x18, sizeof (...->sy_callc) = 8

注意:结构体sysent的大小为0x20(=32), 系统调用服务例程sy_callc在结构体sysent中的偏移为0x18。后面我们分析sys_syscall()汇编代码的时候会用到0x20, 0x18这两个数字。

2.3.2 系统调用表struct sysent sysent[NSYSCALL]

o 宏NSYSCALL定义于头文件usr/src/uts/common/sys/systm.h中,

#define NSYSCALL 256 /* number of system calls */

o sysent[NSYSCALL]定义于源文件usr/src/uts/common/os/sysent.c

/*
* Native sysent table.
*/
struct sysent sysent[NSYSCALL] =
{
/* 0 */ IF_LP64(
SYSENT_NOSYS(),
SYSENT_C("indir", indir, )),
/* 1 */ SYSENT_CI("exit", rexit, ),
...
/* 152 */ SYSENT_CI("modctl", modctl, ),
...
/* 255 */ SYSENT_CI("umount2", umount2, )
...
};

o 宏SYSENT_CI定义于源文件 usr/src/uts/common/os/sysent.c中,

#define    SYSENT_CI(name, call, narg)    \
{ (narg), SE_32RVAL1, NULL, NULL, (llfcn_t)(call) }

o 宏SE_32RVAL1定义于头文件 usr/src/uts/common/sys/systm.h中,

#define SE_32RVAL1 0x0 /* handler returns int32_t in rval1 */

o 以modctl为例,其在sysent表中被展开后就是:

{, 0x0, NULL, NULL, (llfcn_t)modctl}

o 用mdb查看一下,

> (sysent + 0x20 * 0t152)::print -Ta struct sysent
fffffffffc243480 struct sysent {
fffffffffc243480 char sy_narg = '\006'
fffffffffc243482 unsigned short sy_flags =
fffffffffc243488 int (*)() sy_call =
fffffffffc243490 krwlock_t *sy_lock =
fffffffffc243498 int64_t (*)() sy_callc = modctl
}
>

果然,sys_narg = 6, sy_callc = modctl; 也就是说,modctl系统函数中会接收6个参数。

o modctl在usr/src/uts/common/os/sysent.c的申明如下,

int modctl(int, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);

2.4 系统调用入口sys_syscall

o 用户态的C库函数调用syscall指令后进入内核,内核从sys_syscall()开始执行。注意sys_syscall()是通过汇编代码实现的,源文件为:
usr/src/uts/i86pc/ml/syscall_asm_amd64.s

 _syscall_invoke:
movq REGOFF_RDI(%rbp), %rdi
movq REGOFF_RSI(%rbp), %rsi
movq REGOFF_RDX(%rbp), %rdx
movq REGOFF_RCX(%rbp), %rcx
movq REGOFF_R8(%rbp), %r8
movq REGOFF_R9(%rbp), %r9 cmpl $NSYSCALL, %eax
jae _syscall_ill
shll $SYSENT_SIZE_SHIFT, %eax
leaq sysent(%rax), %rbx call *SY_CALLC(%rbx) movq %rax, %r12
movq %rdx, %r13

o 对sys_syscall()用mdb查看

 > sys_syscall::dis
sys_syscall: swapgs
...
sys_syscall+0x21d: movq 0x10(%rbp),%rdi
sys_syscall+0x221: movq 0x18(%rbp),%rsi
sys_syscall+0x225: movq 0x20(%rbp),%rdx
sys_syscall+0x229: movq 0x28(%rbp),%rcx
sys_syscall+0x22d: movq 0x30(%rbp),%r8
sys_syscall+0x231: movq 0x38(%rbp),%r9
sys_syscall+0x235: cmpl $0x100,%eax
sys_syscall+0x23a: jae +0x11a <0xfffffffffb8014bb>
sys_syscall+0x240: shll $0x5,%eax
sys_syscall+0x243: leaq 0xfffffffffc242180(%rax),%rbx <sysent>
sys_syscall+0x24a: call *0x18(%rbx)
sys_syscall+0x24d: movq %rax,%r12
sys_syscall+0x250: movq %rdx,%r13
...
nopop_sys_syscall_swapgs_sysretq: swapgs
nopop_sys_syscall_swapgs_sysretq+: sysret
...

o sys_syscall.s中的这3行,

    shll    $SYSENT_SIZE_SHIFT, %eax
leaq sysent(%rax), %rbx
call *SY_CALLC(%rbx)

对应于将sys_syscall反汇编后这3行

 sys_syscall+0x240: shll  $0x5,%eax
sys_syscall+0x243: leaq 0xfffffffffc242180(%rax),%rbx <sysent>
sys_syscall+0x24a: call *0x18(%rbx)

12: 将eax的值也就是系统调用号左移5位,eax = eax << 5 = eax * 32;
13: 将rax的值加上系统调用表sysent的首地址,存入rbx中;
14: 将rbx的值加上0x18, 该内存地址中的值就是系统调用服务例程的收地址,call [rbx+0x18], 就是调用对应的系统调用服务例程。

o 例如: (以modctl为例)

> ::sizeof struct sysent
sizeof (struct sysent) = 0x20 > ::offsetof struct sysent sy_callc
offsetof (struct sysent, sy_callc) = 0x18, sizeof (...->sy_callc) = > sysent + 0x20 * 0t152 = J
fffffffffc243480
> fffffffffc243480 + 0x18 = J
fffffffffc243498
> fffffffffc243498/J
sysent+0x1318: fffffffffbcad4e0 > fffffffffbcad4e0::whatis
fffffffffbcad4e0 is modctl, in genunix's text segment
> fffffffffbcad4e0/i
modctl:
modctl: pushq %rbp > sysent + 0x20 * 0t152 ::print -Ta struct sysent
fffffffffc243480 struct sysent {
fffffffffc243480 char sy_narg = '\'
fffffffffc243482 unsigned short sy_flags = 0
fffffffffc243488 int (*)() sy_call = 0
fffffffffc243490 krwlock_t *sy_lock = 0
fffffffffc243498 int64_t (*)() sy_callc = modctl
}
>

一旦sys_syscall()找到了系统调用的服务例程(当然是根据系统调用号计算出来的),就进入那个服务例程执行。而系统调用的参数准备在寄存器rdi, rsi, rdx, rcx, r8, r9中。

L4-9正是把储存在stack上的参数值取出来,装入对应的寄存器。系统调用服务例程将从寄存器中取得参数,例如:

  sys_syscall+0x21d: movq  0x10(%rbp),%rdi
sys_syscall+0x221: movq 0x18(%rbp),%rsi
sys_syscall+0x225: movq 0x20(%rbp),%rdx
sys_syscall+0x229: movq 0x28(%rbp),%rcx
sys_syscall+0x22d: movq 0x30(%rbp),%r8
sys_syscall+0x231: movq 0x38(%rbp),%r9
sys_syscall+0x235: cmpl $0x100,%eax
sys_syscall+0x23a: jae +0x11a <0xfffffffffb8014bb>
sys_syscall+0x240: shll $0x5,%eax
sys_syscall+0x243: leaq 0xfffffffffc242180(%rax),%rbx <sysent>
sys_syscall+0x24a: call *0x18(%rbx)

系统调用服务例程将从寄存器中取得参数,例如:

> fffffffffbcad4e0::dis
modctl: pushq %rbp
modctl+: movq %rsp,%rbp
modctl+: subq $0x30,%rsp
modctl+: movq %rdi,-0x8(%rbp)
modctl+0xc: movq %rsi,-0x10(%rbp)
modctl+0x10: movq %rdx,-0x18(%rbp)
modctl+0x14: movq %rcx,-0x20(%rbp)
modctl+0x18: movq %r8,-0x28(%rbp)
modctl+0x1c: movq %r9,-0x30(%rbp)
...

到此为止,用户程序调用C库函数modctl()的参数已经从用户空间传入内核空间,等待内核空间的modctl()执行。当然,中间经过了sys_syscall()存入stack中又从stack中取出来的过程。一旦内核空间的modctl()执行完毕,sys_syscall()就通过sysret指令返回给用户空间的modctl().

3 系统调用过程观察实例

3.1 最简单直接的观察

o 在终端A上启动mdb, 调试命令modinfo

root# mdb /usr/sbin/modinfo
> main:b
> :r -i
mdb: target stopped at:
ld.so.`rtld_db_postinit: pushq %rbp
> :c
mdb: target stopped at:
ld.so.`rtld_db_dlactivity: pushq %rbp
mdb: You've got symbols!
Loading modules: [ ld.so.1 libc.so.1 libuutil.so.1 ]
> modctl:b
> modctl::dis
libc.so.1`modctl: movq %rcx,%r10
libc.so.1`modctl+3: movl $0x98,%eax
libc.so.1`modctl+8: syscall
libc.so.1`modctl+0xa: jb -0x126d30 <libc.so.1`__cerror>
libc.so.1`modctl+0x10: xorq %rax,%rax
libc.so.1`modctl+0x13: ret
> :s
mdb: target stopped at:
ld.so.1`rtld_db_dlactivity+1: movq %rsp,%rbp
> :s
mdb: target stopped at:
libc.so.1`modctl+3: movl $0x98,%eax
> :s
mdb: target stopped at:
libc.so.1`modctl+8: syscall > // 在进入内核模式前,先看看寄存器
> $r
%rax = 0x0000000000000098 %r8 = 0x0000000000000000
%rbx = 0xffff80fdae44e2d0 %r9 = 0x0000000000000000
%rcx = 0x000000061bf989a0 %r10 = 0x000000061bf989a0
%rdx = 0xffff80fdae44e2d0 %r11 = 0x00007ff91dcbfe90
%rsi = 0x0000000000000010 %r12 = 0xffff80fdae44e2d8
%rdi = 0x0000000000000002 %r13 = 0x0000000000000000
...
> // rax = 0x98 = 152 // modctl的系统调用号
> // rdi = 0x2 // cmd MODINFO = 2
> // rsi = 0x10 = 16 // mod ID
> // rdx = 0xffff80fdae44e2d0 // struct modinfo *
> 0xffff80fdae44e2d0::print struct modinfo mi_id mi_name mi_msinfo[0]
mi_id = 0x10
mi_name = [ '\', ... ]
mi_msinfo[0] = {
mi_msinfo[0].msi_linkinfo = [ '\', ... ]
mi_msinfo[0].msi_p0 = 0xae44e438
}
> > :s // 现在不能按回车键, 一旦按下回车键,就执行syscall进入内核模式

o 在终端B(console)上启动mdb -K, 设置断点modctl:b; 这样,一旦在终端A >:s 后敲入回车,立即进入内核模式,可从终端B上观察到

root# mdb -K
kmdb: target stopped at:
kmdb_enter+0xb: movq %rax,%rdi
[]> modctl:b
[]> :c
root#

o 在终端A上键入回车

> :s
[光标在此闪烁];用户程序被暂停了!
与此同时,终端B上进入内核模式

root# mdb -K
kmdb: target stopped at:
kmdb_enter+0xb: movq %rax,%rdi
[]> modctl:b
[]> :c
root# kmdb: stop at modctl
kmdb: target stopped at:
modctl: pushq %rbp
[]>

o 在终端B上查看调用参数

[]> $r
%rax = 0x0000000000001300 %r9 = 0x0000000000000000
%rbx = 0xfffffffffc243480 sysent+0x1300 %r10 = 0xffff80fdae44e5d8
%rcx = 0x000000061bf989a0 %r11 = 0xffff80fdae44e268
%rdx = 0xffff80fdae44e2d0 %r12 = 0x0000000000000000
%rsi = 0x0000000000000010 %r13 = 0x0000000000000000
%rdi = 0x0000000000000002 %r14 = 0xffffa1c009e87000
%r8 = 0x0000000000000000 %r15 = 0xffffa1c00458a4c0 %rip = 0xfffffffffbcad4e0 modctl
... > // rdi = 0x2
> // rsi = 0x10
> // rdx = 0xffff80fdae44e2d0
[]> 0xffff80fdae44e2d0::print struct modinfo mi_id mi_name
mi_id = 0x10
mi_name = [ '\0', ...]

o 在终端B上输入 :z, :c返回

[]>
[]> :z
[]> :c
与此同时,终端A上的被暂停的:s被激活,
> :s
mdb: target stopped at:
libc.so.`modctl+0xa: jb -0x126d30 <libc.so.`__cerror>
> 说明内核调用已经结束,返回到用户模式。

o 在终端A上查看地址0xffff80fdae44e2d0处的内容,我们期望的数据应该已被内核调用填好

> 0xffff80fdae44e2d0::print struct modinfo
{
mi_info =
mi_state =
mi_id = 0x10
mi_nextid = 0x10
mi_base = 0xfffffffffbdbc2d8
mi_size = 0x5d198
mi_rev =
mi_loadcnt =
mi_name = [ "pcie" ]
mi_msinfo = [
{
msi_linkinfo = [ "PCI Express Framework Module" ]
msi_p0 = 0xffffffff
},
...

注意: mis_base, mi_size, mi_name, ms_msinfo[0]中的数据已如我们期望的被填充好。

ID  LOADADDR         SIZE   INFO REV NAMEDESC
fffffffffbdbc2d8 5d198 -- pcie (PCI Express Framework Module)
mdb: target has terminated
>

到此为止,我们观察到了从用户模式进入内核模式,再从内核模式返回到用户模式的全过程。系统调用的神秘面纱已经被揭开。接下来将用DTrace深入观察内核服务例程的行为。

3.2 使用DTrace观察内核行为

o 在终端A启动mdb, 调试命令modinfo

root# mdb /usr/sbin/modinfo
> main:b
> :r -i
mdb: target stopped at:
ld.so.`rtld_db_postinit: pushq %rbp
> :c
mdb: target stopped at:
ld.so.`rtld_db_dlactivity: pushq %rbp
mdb: You've got symbols!
Loading modules: [ ld.so.1 libc.so.1 libuutil.so.1 ]
> modctl:b
> :c
mdb: stop at libc.so.1`modctl
mdb: target stopped at:
libc.so.1`modctl: movq %rcx,%r10
> :s
mdb: target stopped at:
libc.so.1`modctl+3: movl $0x98,%eax
> :s
mdb: target stopped at:
libc.so.1`modctl+8: syscall
>
> $r
%rax = 0x0000000000000098 %r8 = 0x0000000000000000
%rbx = 0xffff80dbdcb43120 %r9 = 0x0000000000000000
%rcx = 0x0000000f25c48f90 %r10 = 0x0000000f25c48f90
%rdx = 0xffff80dbdcb43120 %r11 = 0x00007ffdc84bfe90
%rsi = 0x0000000000000010 %r12 = 0xffff80dbdcb43128
%rdi = 0x0000000000000002 %r13 = 0x0000000000000000
...
> 0xffff80dbdcb43120::print struct modinfo mi_id mi_name
mi_id = 0x10
mi_name = [ '\', ...]

o 在终端B启动dtrace脚本

root# ./fook.d

fook.d的代码如下:

 #!/usr/sbin/dtrace -qs

 syscall::modctl:entry
/execname == "modinfo"/
{
printf("%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename);
printf("args: 0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X\n",
arg0, arg1, arg2, arg3, arg4, arg5);
stack();
printf("\n---------------------------------------------------------\n");
self->n = ;
} syscall::modctl:return
/execname == "modinfo"/
{
self->n = ;
} fbt::modctl:entry
/self->n == /
{
printf("%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename);
printf("args: 0x%X, 0x%X, 0x%X, 0x%X, 0x%X, 0x%X\n",
arg0, arg1, arg2, arg3, arg4, arg5);
stack();
printf("\n---------------------------------------------------------\n");
} fbt::modctl_modinfo:entry
/self->n == /
{
printf("%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename);
printf("args: 0x%X, 0x%X\n", arg0, arg1);
stack(); self->mip = (struct modinfo *)arg1; /*
* usr/src/uts/common/sys/modctl.h#421
* struct modinfo {
* int mi_info; // Flags for info wanted
* int mi_state; // Flags for module state
* int mi_id; // id of this loaded module
* int mi_nextid; // id of next module or -1
* caddr_t mi_base; // virtual addr of text
* size_t mi_size; // size of module in bytes
* int mi_rev; // loadable modules rev
* int mi_loadcnt; // # of times loaded
* char mi_name[MODMAXNAMELEN]; // name of module
* struct modspecific_info mi_msinfo[MODMAXLINK];
* // mod specific info
* };
*
* struct modspecific_info {
* char msi_linkinfo[MODMAXLINKINFOLEN]; // name in linkage struct
* int msi_p0; // module specific information
* };
*
* usr/src/cmd/modload/modinfo.c#248
* static boolean_t
* print_mod_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
*
* XXX: Here we cannot use self->mip->mi_id,... directly, so copyin !
*/
self->mi = (struct modinfo *)(copyin((uintptr_t)(self->mip),
sizeof(struct modinfo)));
printf("\n");
printf("ENT: ID mi->mi_id = %d\n",
self->mi->mi_id);
printf("ENT: LOADADDR mi->mi_base = %p\n",
self->mi->mi_base);
printf("ENT: SIZE mi->mi_size = %x\n",
self->mi->mi_size);
printf("ENT: INFO mi->mi_mi_msinfo[0].msi_p0 = %d\n",
self->mi->mi_msinfo[].msi_p0);
printf("ENT: REV mi->mi_rev = %x\n",
self->mi->mi_rev);
printf("ENT: NAME mi->mi_name = %s\n",
stringof(self->mi->mi_name));
printf("ENT: DESC mi->mi_msinfo[0].msi_linkinfo = %s\n",
stringof(self->mi->mi_msinfo[].msi_linkinfo));
printf("\n---------------------------------------------------------\n");
} fbt::modctl_modinfo:return
/self->n == /
{
printf("%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename); stack(); /*
* XXX: Here we cannot use self->mip->mi_id,... directly, so copyin !
*/
self->mi = (struct modinfo *)(copyin((uintptr_t)(self->mip),
sizeof(struct modinfo)));
printf("\n");
printf("RET: ID mi->mi_id = %d\n",
self->mi->mi_id);
printf("RET: LOADADDR mi->mi_base = %p\n",
self->mi->mi_base);
printf("RET: SIZE mi->mi_size = %x\n",
self->mi->mi_size);
printf("RET: INFO mi->mi_mi_msinfo[0].msi_p0 = %d\n",
self->mi->mi_msinfo[].msi_p0);
printf("RET: REV mi->mi_rev = %x\n",
self->mi->mi_rev);
printf("RET: NAME mi->mi_name = %s\n",
stringof(self->mi->mi_name));
printf("RET: DESC mi->mi_msinfo[0].msi_linkinfo = %s\n",
stringof(self->mi->mi_msinfo[].msi_linkinfo));
printf("\n---------------------------------------------------------\n"); self->mip = ;
} fbt::copyin:entry,
fbt::copyout:entry
/self->n == /
{
printf("%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename);
printf("args: 0x%X, 0x%X, 0x%X\n", arg0, arg1, arg2);
stack();
printf("\n---------------------------------------------------------\n");
}

注意:内核内存和用户内存是严格隔离的,当内核需要访问用户内存时,必须使用copyin();反之,如内核需要把数据传递会用户空间,必须使用copyout()。

o 在终端A中执行:s (执行汇编指令syscall)

> :s
mdb: target stopped at:
libc.so.`modctl+0xa: jb -0x126d30 <libc.so.`__cerror>
>

与此同时,终端B的输出如下

root# ./fook.d
syscall::modctl :entry
args: 0x2, 0x10, 0xFFFF80DBDCB43120, 0xF25C48F90, 0x0, 0x0 unix`sys_syscall+0x24d ---------------------------------------------------------
fbt:genunix:modctl :entry
args: 0x2, 0x10, 0xFFFF80DBDCB43120, 0xF25C48F90, 0x0, 0x0 genunix`dtrace_systrace_syscall+0x14d
unix`sys_syscall+0x24d ---------------------------------------------------------
fbt:genunix:modctl_modinfo :entry
args: 0x10, 0xFFFF80DBDCB43120 genunix`modctl+0x4e7
genunix`dtrace_systrace_syscall+0x14d
unix`sys_syscall+0x24d ENT: ID mi->mi_id =
ENT: LOADADDR mi->mi_base =
ENT: SIZE mi->mi_size =
ENT: INFO mi->mi_mi_msinfo[].msi_p0 = -
ENT: REV mi->mi_rev =
ENT: NAME mi->mi_name =
ENT: DESC mi->mi_msinfo[].msi_linkinfo = ## ---------------------------------------------------------
fbt:unix:copyin :entry
args: 0xFFFF80DBDCB43120, 0xFFFFFFFC81AEDA30, 0x1B0 genunix`modctl_modinfo+0xa0
genunix`modctl+0x4e7
genunix`dtrace_systrace_syscall+0x14d
unix`sys_syscall+0x24d ---------------------------------------------------------
fbt:unix:copyout :entry
args: 0xFFFFFFFC81AEDA30, 0xFFFF80DBDCB43120, 0x1B0 genunix`modctl_modinfo+0x1e6
genunix`modctl+0x4e7
genunix`dtrace_systrace_syscall+0x14d
unix`sys_syscall+0x24d ---------------------------------------------------------
fbt:genunix:modctl_modinfo :return genunix`modctl+0x4e7
genunix`dtrace_systrace_syscall+0x14d
unix`sys_syscall+0x24d RET: ID mi->mi_id =
RET: LOADADDR mi->mi_base = fffffffffbdbc2d8
RET: SIZE mi->mi_size = 5d198
RET: INFO mi->mi_mi_msinfo[].msi_p0 = -
RET: REV mi->mi_rev =
RET: NAME mi->mi_name = pcie
RET: DESC mi->mi_msinfo[].msi_linkinfo = PCI Express Framework Module ---------------------------------------------------------

o 在终端A中查看地址0xffff80dbdcb43120的内容

> 0xffff80dbdcb43120::print struct modinfo
{
mi_info =
mi_state =
mi_id = 0x10
mi_nextid = 0x10
mi_base = 0xfffffffffbdbc2d8
mi_size = 0x5d198
mi_rev =
mi_loadcnt =
mi_name = [ "pcie" ]
mi_msinfo = [
{
msi_linkinfo = [ "PCI Express Framework Module" ]
msi_p0 = 0xffffffff
},
  ...

该输出跟DTrace中观测到的数据一致。

o 在终端A上执行dtrace脚本观察用户模式下的调用栈

root# ./foou.d -c "modinfo -i 16"
ID LOADADDR SIZE INFO REV NAMEDESC
fffffffffbdbc2d8 5d198 -- pcie (PCI Express Framework Module) pid22782:libc.so.:modctl :entry
args: 0x2, 0x10, 0xFFFF80F02D9A1730, 0x927D90DF0, 0x0 libc.so.`modctl
modinfo`main+0x3b6
modinfo`0x7ffe48701b34

foou.d的代码如下:

 #!/usr/sbin/dtrace -qs

 pid$target::modctl:entry
/execname == "modinfo"/
{
printf("\n%s:%s:%s :%s\n", probeprov, probemod, probefunc, probename);
printf("args: 0x%X, 0x%X, 0x%X, 0x%X, 0x%X\n",
arg0, arg1, arg2, arg3, arg4);
ustack();
}

o 在终端B上观察内核模式下的调用栈

(1) 在终端B上启动DTrace,

root# dtrace -n "fbt::mod_infonull:entry {stack();}"
dtrace: description 'fbt::mod_infonull:entry ' matched probe

(2) 在终端A上执行命令 modinfo -i 16

root# modinfo -i
ID LOADADDR SIZE INFO REV NAMEDESC
fffffffffbdbc2d8 5d198 -- pcie (PCI Express Framework Module)

与此同时,终端B上的输出为:

root# dtrace -n "fbt::mod_infonull:entry {stack();}"
dtrace: description 'fbt::mod_infonull:entry ' matched probe
CPU ID FUNCTION:NAME
mod_infonull:entry
genunix`mod_info+0x66
pcie`_info+0x1f
genunix`mod_getinfo+0x5a
genunix`modinfo+0x125
genunix`modctl_modinfo+0xd5
genunix`modctl+0x4e7
unix`sys_syscall+0x24d ^C

4. 直接使用系统调用编程

下面给出一个简单的例子,说明只需要准备好系统调用号和相应的参数,直接使用汇编指令syscall就可以完成系统调用。

 BITS 

 SECTION .data

 Hello:          db "Hello world!",
len_Hello: equ $-Hello SECTION .text global _start _start:
mov rdi, ; fd = stdout
mov rsi, Hello ; *buf = Hello
mov rdx, len_Hello ; count = len_Hello
mov rax, ; write syscall (x86_64)
syscall mov rdi, ; status = 0 (exit normally)
mov rax, ; exit syscall (x86_64)
syscall

编译,执行如下所示:

root# yasm -f elf64 foo.asm
root# ld -o foo foo.o
root# ./foo
Hello world!
root# echo $?

最后,关于如何给Solaris添加一个系统调用,请参考《Solaris内核结构》(第2版)一书的附录B:Adding a System Call to Solaris。

推荐阅读: The Definitive Guide to Linux System Calls

深入理解Solaris X64系统调用的更多相关文章

  1. 深入理解Windows X64调试

    随着64位操作系统的普及,都开始大力进军x64,X64下的调试机制也发生了改变,与x86相比,添加了许多自己的新特性,之前学习了Windows x64的调试机制,这里本着“拿来主义”的原则与大家分享. ...

  2. [Kernel]理解System call系统调用

    转自:http://os.51cto.com/art/200512/13510.htm 现在,您或许正在查看设备驱动程序,并感到奇怪:“函数 foo_read() 是如何被调用的?”或者可能疑惑: “ ...

  3. 深入理解Linux的系统调用【转】

    一. 什么是系统调用 在Linux的世界里,我们经常会遇到系统调用这一术语,所谓系统调用,就是内核提供的.功能十分强大的一系列的函数.这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一 ...

  4. 深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理

    在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz).每次被年轻的有才华的 ...

  5. linux内核分析——扒开系统调用的三层皮

    万子惠 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验部分 选择2 ...

  6. Linux 编程中的API函数和系统调用的关系【转】

    转自:http://blog.chinaunix.net/uid-25968088-id-3426027.html 原文地址:Linux 编程中的API函数和系统调用的关系 作者:up哥小号 API: ...

  7. Linux 库函数与系统调用的关系与区别

    上周总结了<C 标准库的基础 IO>,其实这些功能函数通过「系统调用」也能实现相应功能.这次文章并不是要详细介绍各系统调用接口的使用方法,而是要深入理解「库函数」与「系统」调用之间的关系和 ...

  8. 对于Linux内核执行过程的理解(基于fork、execve、schedule等函数)

    382 + 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 一.实验环境 win10 -> VMware -> Ubuntu1 ...

  9. Linux内核分析第五周 扒开系统调用的三层皮(下) (20135304 刘世鹏)

    作者:刘世鹏20135304  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给MenuOS增加t ...

随机推荐

  1. Luckily general gradient for spherical harmonics is defined

    http://web4.cs.ucl.ac.uk/staff/j.kautz/publications/gradientSH_RS04.pdf

  2. 【Android 多媒体应用】使用MediaCodec解码使用AudioTrack播放音频数据

    1.MainActivity.java import android.app.Activity; import android.os.Bundle; import android.os.Environ ...

  3. [arc076F]Exhausted? 贪心+堆

    Description ​ 有m个椅子,第i个在位置i,每个椅子只能坐一个人.  有n个人,第i个人能坐的椅子的位置j需满足j≤Li或j≥Ri.  现在你可以添加若干个椅子,可以放在任意实数位置.问最 ...

  4. 洛谷P5264 【模板】多项式三角函数

    题面 传送门 题解 据说有一个叫做欧拉公式的东西 \[e^{ix}=\cos(x)+i\sin(x)\] 别问我为啥我今天第一次看到它 那么显然也有 \[e^{-ix}=\cos(x)-i\sin(x ...

  5. json与xml简介

    <1>. json : JavaScript 对象表示法(JavaScript Object Notation) 语法: 数据在名称/值对中 数据由逗号分隔 花括号保存对象 方括号保存数组 ...

  6. js实现小球碰撞游戏

    效果图:  效果图消失只是截的gif图的问题 源码: <!DOCTYPE html> <html lang="en"> <head> <m ...

  7. KVC 原理及自定义实现

    一.  setValue: forKey: 赋值过程 1.首先寻找setter方法(两个) - setName: -setIsName: 2.然后再寻找成员变量 默认 + (BOOL)accessIn ...

  8. 基础篇:3.2)规范化:3d零件建模

    本章目的:规范化零件建模,这是机械的基本功夫. 1.建模的总体原则和总体要求 1.1 建模总体原则 a)零件模型应能准确表达零件的设计信息:b)零件模型包含零件的几何要素.约束要素和工程要素:c)零件 ...

  9. 2019 CCPC-Wannafly Winter Camp Day3(Div2, onsite)

    solve 4/11 补题:5/11 A 二十四点* Code:pai爷  zz Thinking :pai爷 打表找规律,1张牌 10个不可能的 2张牌有 43 种不可能的 3张牌 有74 种不可能 ...

  10. 基于vue-cli li列表的显示隐藏

    效果:点击“公告标题”,显示公告内容,点击同一个“公告标题”多次,显示与隐藏切换 方法一: html部分代码: <ul class="clist"> <li v- ...