------------------------------------------

本文系本站原创,欢迎转载!

转载请注明出处:http://ericxiao.cublog.cn/

------------------------------------------

一: 前言

Syscall tracer是用来跟踪系统调用的,它会检测所有系统调用的入口和出口,再将相关的信息保存到ring buffer.以下是syscall tracer的输出的一个例子:

# echo syscall > current_tracer

# cat trace | tail

-13607 [000] 29097.902910: sys_close(fd: 3)

-13607 [000] 29097.902912: sys_close -> 0x0

-13607 [000] 29097.902962: sys_fstat64(fd: 1, statbuf: bfaac95c)

-13607 [000] 29097.902963: sys_fstat64 -> 0x0

-13607 [000] 29097.902965: sys_open(filename: bfaad8f4, flags: 8000, mode: 0)

从上面的信息号可以看出,有一个sys_close的系统调用,关闭的文件描述符是3(sys_close(fd: 3)

), 这个系统调用返回的是0(sys_close -> 0x0).

下面就从linux kernel源代码的角度来分析syscall 的相关操作. 本文分析的源代码版本基于v2.6.31-rc1,代码基本上位于kernel/trace/trace_syscalls.c中.

二: syscall的初始化

Syscall的初始化入口为:

device_initcall(register_ftrace_syscalls);

它的初始化函数为register_ftrace_syscalls(),代码如下:

__init int register_ftrace_syscalls(void)

{

int ret;

ret = register_ftrace_event(&syscall_enter_event);

if (!ret) {

printk(KERN_WARNING "event %d failed to register\n",

syscall_enter_event.type);

WARN_ON_ONCE(1);

}

ret = register_ftrace_event(&syscall_exit_event);

if (!ret) {

printk(KERN_WARNING "event %d failed to register\n",

syscall_exit_event.type);

WARN_ON_ONCE(1);

}

return register_tracer(&syscall_tracer);

}

从刚开始的例子可以看出,syscall entry和syscall exit的显示方式是不相同的,这也是这个初始化函数中注册两个trace_event的原因.

Trace_event的相关操作在之前分析trace框架的时候已经分析过了,这里不再赘述.具体这两个trace_event是如何显示信息的,在之后联合syscall数据的保存再做分析.

此外,我们在初始化函数中还注册了syscall tracer,它就是今天分析的重点.

三: syscall tracer

Syscall tracer定义如下:

static struct tracer syscall_tracer __read_mostly = {

.name            = "syscall",

.init       = init_syscall_tracer,

.reset      = reset_syscall_tracer,

.flags      = &syscalls_flags,

};

结合trace框架的分析,在register_tracer()的时候,会进行self test,但syscall 中并没有selftest接口,说明syscall tracer在注册的时候不会有self test操作. 这是因为syscall是依赖于用户空间的系统调用,在系统初始化的时候不可能发生用户空间系统调用事件,因此,syscall在系统初始化时间是没有实际操作的.

如果我们在用户空间当syscall设置成当前的tracer:

# echo syscall > current_tracer

就会触发tracing_set_tracer(),结合之前的分析,在”安装”tracer的时候会调用:

tracer->init().并且会创建option文件.

在移除tracer的时候会调用tracer->reset().

从上面的结构中可以看出,syscall没有自己的set_flag()操作,也即采用默认操作,在默认操作下,不管在任何情况下,设置或者清除任何标志都是允许的(直接返回0).

Syscall的相关flags定义如下:

static struct tracer_opt syscalls_opts[] = {

{ TRACER_OPT(syscall_arg_type, TRACE_SYSCALLS_OPT_TYPES) },

{ }

};

static struct tracer_flags syscalls_flags = {

.val = 0, /* By default: no parameters types */

.opts = syscalls_opts

};

TRACER_OPT定义如下:

#define TRACER_OPT(s, b)    .name = #s, .bit = b

由此可见它的flags默认为0,只有一个标志,名称为”syscall_arg_type”,它的标志为:

enum {

TRACE_SYSCALLS_OPT_TYPES = 0x1,

};

即占用第一位.

在用户空间验证一下:

# echo syscall > current_tracer

# ls options/syscall_arg_type

options/syscall_arg_type

# cat options/syscall_arg_type

0

说明已经创建了一个名为”syscall_arg_type”的文件,且初始值为0.

Syscall的reset()接口为reset_syscall_tracer(),代码如下:

static void reset_syscall_tracer(struct trace_array *tr)

{

stop_ftrace_syscalls();

tracing_reset_online_cpus(tr);

}

先是调用stop_ftrace_syscalls()来停止syscall的跟踪,因为这时syscall tracer已经被别的tracer替换了. 然后再是调用traing_reset_online_cpus()来清空ring buffer.以免在别的tracer没有init接口污染ring buffer(在tracing_set_tracer()中,只有tracer->init有定义的时候才会清空ring buffer).

Stop_ftrace_syscalls()是用来停止syscall的跟踪操作,它的代码如下:

void stop_ftrace_syscalls(void)

{

unsigned long flags;

struct task_struct *g, *t;

mutex_lock(&syscall_trace_lock);

/* There are perhaps still some users */

if (--refcount)

goto unlock;

read_lock_irqsave(&tasklist_lock, flags);

do_each_thread(g, t) {

clear_tsk_thread_flag(t, TIF_SYSCALL_FTRACE);

} while_each_thread(g, t);

read_unlock_irqrestore(&tasklist_lock, flags);

unlock:

mutex_unlock(&syscall_trace_lock);

}

syscall_trace_lock锁用来保护设置进程flag,以及操作计数,以保证其串行化.

在这里设置refcount是为了避免多次重复的操作,比如说,syscall已经是stop状态了,但又有一个stop操作过来了,这时就没必须再次stop syscall.

然后持有进程的保护读写自旋锁,清除掉所有进程的TIF_SYSCALL_FTRACE标志.

Syscall的init接口为init_syscall_tracer(),代码如下:

static int init_syscall_tracer(struct trace_array *tr)

{

start_ftrace_syscalls();

return 0;

}

Start_ftrace_syscalls代码如下:

void start_ftrace_syscalls(void)

{

unsigned long flags;

struct task_struct *g, *t;

mutex_lock(&syscall_trace_lock);

/* Don't enable the flag on the tasks twice */

if (++refcount != 1)

goto unlock;

arch_init_ftrace_syscalls();

read_lock_irqsave(&tasklist_lock, flags);

do_each_thread(g, t) {

set_tsk_thread_flag(t, TIF_SYSCALL_FTRACE);

} while_each_thread(g, t);

read_unlock_irqrestore(&tasklist_lock, flags);

unlock:

mutex_unlock(&syscall_trace_lock);

}

代码很简单,start_ftrace_syscalls()和stop_ftrace_syscall()做的是相反的事情,即为每个进程设置TIF_SYSCALL_FTRACE标志.

注意到,这里还有一个新的操作,即arch_init_ftrace_syscalls(),这个函数用来初始化平台的的syscalls,在x86平台,该函数如下:

void arch_init_ftrace_syscalls(void)

{

int i;

struct syscall_metadata *meta;

unsigned long **psys_syscall_table = &sys_call_table;

static atomic_t refs;

if (atomic_inc_return(&refs) != 1)

goto end;

syscalls_metadata = kzalloc(sizeof(*syscalls_metadata) *

FTRACE_SYSCALL_MAX, GFP_KERNEL);

if (!syscalls_metadata) {

WARN_ON(1);

return;

}

for (i = 0; i < FTRACE_SYSCALL_MAX; i++) {

meta = find_syscall_meta(psys_syscall_table[i]);

syscalls_metadata[i] = meta;

}

return;

/* Paranoid: avoid overflow */

end:

atomic_dec(&refs);

}

首先,refs是局部静态变量,用来防止过多的初始化,从上面的代码可以看出,进入函数的时候,该计数+1,如果失败,才会减计数.

那是否在有些情况下,该函数会初始化失败? 所以需要多次调用,直到它成功为止?

先来看struct syscall_metadata的定义,它保存的是系统调用的元数据,如下:

struct syscall_metadata {

const char  *name;

int     nb_args;

const char  **types;

const char  **args;

};

这些保存的元数包括: 系统调用的名字(name),参数个数(nb_args),系统调用的参数类型(types),以及系统调用的参数名字(args).

从上面的代码可以看到,syscall tracer所能支持的最大系统调用数是FTRACE_SYSCALL_MAX.

首先为syscalls_metadata分配空间,然后调用find_syscall_meta()找到该系统调用对应的元数据.

find_syscall_meta()接受的参数是系统调用表中对应的处理函数,代码如下:

static struct syscall_metadata *find_syscall_meta(unsigned long *syscall)

{

struct syscall_metadata *start;

struct syscall_metadata *stop;

char str[KSYM_SYMBOL_LEN];

start = (struct syscall_metadata *)__start_syscalls_metadata;

stop = (struct syscall_metadata *)__stop_syscalls_metadata;

kallsyms_lookup((unsigned long) syscall, NULL, NULL, NULL, str);

for ( ; start < stop; start++) {

if (start->name && !strcmp(start->name, str))

return start;

}

return NULL;

}

从此可见,所有系统调用的元数据都会保存在从__start_syscalls_metadata到__stop_syscalls_metadata的区域.这个区域到底是怎么形成的呢?

从vmlinux.lds.h中可以看到,有它的相关信息:

#define TRACE_SYSCALLS() VMLINUX_SYMBOL(__start_syscalls_metadata) = .; \

*(__syscalls_metadata)             \

VMLINUX_SYMBOL(__stop_syscalls_metadata) = .;

那就是说,他们表示的是__syscalls_metadata链接段的部份,所以只需要找到链接到这段的数据即可.

我们还是从系统调用的定义开始,有两种情况,(下面的分析都是假设已经配置了syscall tracer的编译宏:CONFIG_FTRACE_SYSCALLS):

1: 系统调用不带参数

这种情况下,是以SYSCALL_DEFINE0()定义的,这类系统调用有getpid()之类,它的定义如下:

#define SYSCALL_DEFINE0(sname)                  \

static const struct syscall_metadata __used     \

__attribute__((__aligned__(4)))           \

__attribute__((section("__syscalls_metadata")))   \

__syscall_meta_##sname = {            \

.name       = "sys_"#sname,         \

.nb_args    = 0,                \

};                          \

asmlinkage long sys_##sname(void)

从上面可以看出,这类系统调用的syscall_metadata数据中只有系统调用的名称和参数个数(0).例如,如果是getpid系统调用,上面的数据为:

__syscall_meta_get_pid = {

.name = “sys_getpid”,

.nb_args = 0,

}

2: 如果系统调用带有参数

这种情况下,通常是由SYSCALL_DEFINE1, SYSCALL_DEFINE2,……所定义,但归根到底,它们都是由SYSCALL_DEFINEx扩展来的,如下示:

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

……

……

来看一下SYSCALL_DEFINEx的定义:

#define SYSCALL_DEFINEx(x, sname, ...)              \

static const char *types_##sname[] = {          \

__SC_STR_TDECL##x(__VA_ARGS__)          \

};                          \

static const char *args_##sname[] = {           \

__SC_STR_ADECL##x(__VA_ARGS__)          \

};                          \

SYSCALL_METADATA(sname, x);             \

__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

上面的type_###sname就是类型数组,args###sname是参数名称数组,这些都是在struct syscall_metadata的相关部份.

SYSCALL_METADATA()定义如下:

#define SYSCALL_METADATA(sname, nb)             \

static const struct syscall_metadata __used     \

__attribute__((__aligned__(4)))           \

__attribute__((section("__syscalls_metadata")))   \

__syscall_meta_##sname = {            \

.name       = "sys"#sname,          \

.nb_args    = nb,               \

.types      = types_##sname,        \

.args       = args_##sname,         \

}

这个赋值了它的调用名称,参数个数,它的参数类型和参数名称分别指向了types_###sname, args###sname.

这两个数组中的数据是怎么样形成的呢? 问题就回到了__SC_STR_TDECL##x(__VA_ARGS__)和__SC_STR_ADECL##x(__VA_ARGS__)是怎么样实现的.

对于__SC_STR_TDECL##x(__VA_ARGS__),如下示:

#define __SC_STR_TDECL1(t, a)       #t

#define __SC_STR_TDECL2(t, a, ...)  #t, __SC_STR_TDECL1(__VA_ARGS__)

#define __SC_STR_TDECL3(t, a, ...)  #t, __SC_STR_TDECL2(__VA_ARGS__)

#define __SC_STR_TDECL4(t, a, ...)  #t, __SC_STR_TDECL3(__VA_ARGS__)

#define __SC_STR_TDECL5(t, a, ...)  #t, __SC_STR_TDECL4(__VA_ARGS__)

#define __SC_STR_TDECL6(t, a, ...)  #t, __SC_STR_TDECL5(__VA_ARGS__)

该宏定义是一个递归定义,也就是说,它是取参数列表的第一个参数,然后跳过一个参数,再取......

我们以sendto系统调用为例进行分析:

它的定义为:

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,

unsigned, flags, struct sockaddr __user *, addr,

int, addr_len)

因为__SC_STR_TDECL##x()是先取第一个参数,然后隔一个参数再取一个参数,因此,上面的例子就成了:

__SC_STR_TDECL6 = int, void __user*, size_t, unsigned, struct sockaddr, int

__SC_STR_ADECL##x的定义如下:

#define __SC_STR_ADECL1(t, a)       #a

#define __SC_STR_ADECL2(t, a, ...)  #a, __SC_STR_ADECL1(__VA_ARGS__)

#define __SC_STR_ADECL3(t, a, ...)  #a, __SC_STR_ADECL2(__VA_ARGS__)

#define __SC_STR_ADECL4(t, a, ...)  #a, __SC_STR_ADECL3(__VA_ARGS__)

#define __SC_STR_ADECL5(t, a, ...)  #a, __SC_STR_ADECL4(__VA_ARGS__)

#define __SC_STR_ADECL6(t, a, ...)  #a, __SC_STR_ADECL5(__VA_ARGS__)

它跟__SC_STR_TDECL##x相反,它是先取第二个参数,然后隔一参数再取.对于sendto来说,就是这样子的:

__SC_STR_ ADECL6 = fd, buff, size_t, len, flags, addr, addr_len

到这里,终于水落石出了,我们对struct syscall_metadata的数据组织应该很清楚了.

Syscall的相关操作接口,到这就分析完了,下面我们来分析一下,syscall到底是怎样去跟踪的.

四: syscall的tracer原理

接下来看一下syscall的相关执行流,在arch/x86/kernel/entry_32.S中:

ENTRY(system_call)

RING0_INT_FRAME         # can't unwind into user space anyway

/*将系统调用号入栈*/

pushl %eax          # save orig_eax

CFI_ADJUST_CFA_OFFSET 4

/*保存寄存器环境*/

SAVE_ALL

/*取得当前进程的thread_info并将其存放到ebp中*/

GET_THREAD_INFO(%ebp)

# system call tracing in operation / emulation

/*检查thread_info标志中是否包含_TIF_WORK_SYSCALL_ENTRY

*中的标志,如有包含,此跳转到syscall_trace_entry

*/

testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)

jnz syscall_trace_entry

/*如果系统调用号比最大的允许调用号还要大,非法情况,跳转到syscall_badsys*/

cmpl $(nr_syscalls), %eax

jae syscall_badsys

syscall_call:

/*调用对应的系统调用函数*/

call *sys_call_table(,%eax,4)

/*将返回值存放到eax*/

movl %eax,PT_EAX(%esp)      # store the return value

syscall_exit:

LOCKDEP_SYS_EXIT

DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt

# setting need_resched or sigpending

# between sampling and the iret

TRACE_IRQS_OFF

/*将thread_info的标志位存放到ecx*/

movl TI_flags(%ebp), %ecx

/*判断标志位中是否含有_TIF_ALLWORK_MASK中的标志,如果有,

*跳转到syscall_exit_work

*/

testl $_TIF_ALLWORK_MASK, %ecx  # current->work

jne syscall_exit_work

......

.......

_TIF_WORK_SYSCALL_ENTRY 的定义如下:

#define _TIF_WORK_SYSCALL_ENTRY \

(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_EMU | _TIF_SYSCALL_FTRACE |  \

_TIF_SYSCALL_AUDIT | _TIF_SECCOMP | _TIF_SINGLESTEP)

应该会注意到,里面的标志中就有我们在前面分析中涉及到的

TIF_SYSCALL_FTRACE(_TIF_SYSCALL_FTRACE  (1 << TIF_SYSCALL_FTRACE))

_TIF_ALLWORK_MASK定义如下:

#define _TIF_ALLWORK_MASK ((0x0000FFFF & ~_TIF_SECCOMP) | _TIF_SYSCALL_FTRACE)

如果也有_TIF_SYSCALL_FTRACE标志.

那也就是说,syscall tracer如果被启动,在进入到syscall的时候,会跳转至syscall_trace_entry().在退出syscall的时候会跳转到syscall_exit_work().

先来看syscall_trace_entry,如下:

syscall_trace_entry:

/*默认将返回值置为-ENOSYS */

movl $-ENOSYS,PT_EAX(%esp)

/*将esp copy到eax.这是因为syscall_trace_entry是前三个参数用寄存器传递的

*它的第一个参数放置在eax中,也就是当前的esp

*/

movl %esp, %eax

/*调用sycall_trace_enter*/

call syscall_trace_enter

/* What it returned is what we'll actually use.  */

/* syscall_trace_enter()会返回实际所用的系统调用号,出错返回负值*/

cmpl $(nr_syscalls), %eax

jnae syscall_call

jmp syscall_exit

END(syscall_trace_entry)

也就是说,在进行实际的系统调用前,流程会先转入到syscall_trace_enter()进行判断.

syscall_exit_work定义如下:

syscall_exit_work:

/*如果不包含_TIF_WORK_SYSCALL_EXIT 中的标志,会跳转到work_pending*/

testl $_TIF_WORK_SYSCALL_EXIT, %ecx

jz work_pending

TRACE_IRQS_ON

ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call

# schedule() instead

/*将第一个参数放入到eax中,再调用syscall_trace_leave()*/

movl %esp, %eax

call syscall_trace_leave

jmp resume_userspace

END(syscall_exit_work)

又看到了一个标志集: _TIF_WORK_SYSCALL_EXIT, 定义如下:

#define _TIF_WORK_SYSCALL_EXIT  \

(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | _TIF_SINGLESTEP |    \

_TIF_SYSCALL_FTRACE)

注意到了,里面也会包含_TIF_SYSCALL_FTRACE, 也就是说,在退出系统调用前,如果syscall tracer被打开,会先转入到syscall_trace_leave()中.

下面分两个部份来分析,一个部份是syscall entry操作,一个是syscall exit操作.

4.1: syscall entry分析:

从上面的分析可得到,在启用syscall tracer的时候,进行实际的系统调用之前,会先调用syscall_trace_enter(), 代码片段如下:

asmregparm long syscall_trace_enter(struct pt_regs *regs)

{

......

......

if (unlikely(test_thread_flag(TIF_SYSCALL_FTRACE)))

ftrace_syscall_enter(regs);

......

......

}

也就是说,流程会转入到ftrace_syscall_enter(),该函数代码如下:

void ftrace_syscall_enter(struct pt_regs *regs)

{

struct syscall_trace_enter *entry;

struct syscall_metadata *sys_data;

struct ring_buffer_event *event;

int size;

int syscall_nr;

syscall_nr = syscall_get_nr(current, regs);

sys_data = syscall_nr_to_meta(syscall_nr);

if (!sys_data)

return;

size = sizeof(*entry) + sizeof(unsigned long) * sys_data->nb_args;

event = trace_current_buffer_lock_reserve(TRACE_SYSCALL_ENTER, size,

0, 0);

if (!event)

return;

entry = ring_buffer_event_data(event);

entry->nr = syscall_nr;

syscall_get_arguments(current, regs, 0, sys_data->nb_args, entry->args);

trace_current_buffer_unlock_commit(event, 0, 0);

trace_wake_up();

}

该函数就是把系统调用的相关信息保存下来罢了.

首先,调有syscall_get_nr()取得系统调用号,其实它就是取regs->orig_ax.因为在用户空间进行系统调用的时候,系统调用号是保存在eax寄存器中的.

然后调用syscall_nr_to_meta()取得从该系统调用号对应的syscall_metadata, 综合我们在上面的分析,其实它就是在syscalls_metadata[]数组中的对应项.

我们先来看一下syscall tracer entry的数据组织,它的数据是存放在struct syscall_trace_enter中的,该结构中下示:

struct syscall_trace_enter {

struct trace_entry  ent;

int         nr;

unsigned long       args[];

};

Nr就是系统调用号, argsargs就是参数值数组.

综合上面的分析,可得知,nr系统调用对应的参数个数是sys_data->nb_args,因此它需要分配的长度是:

Sizeof(struct syscall_trace_enter) + sys_data->nb_args*sizeof(unsigned long)

然后就是一个具体取参数的过程,它是调用syscall_get_arguments()来完成的,在x86 32位平台上,代码如下:

static inline void syscall_get_arguments(struct task_struct *task,

struct pt_regs *regs,

unsigned int i, unsigned int n,

unsigned long *args)

{

BUG_ON(i + n > 6);

memcpy(args, &regs->bx + i, n * sizeof(args[0]));

}

参数的含义为:

Task: 当前进程

Regs: 寄存器列表

i,n: 从第i个系统调用参数开始,连续取n项

上面的函数很好理解,因为系统调用时,参数是放在ebx, ecx,edx ……等寄存器中,在SAVE_ALL的时候把这些寄存器安排在了一次,也就是在regs->bx开始的部份.

然后再提交数据,并调用trace_wake_up()来唤醒pipe_read操作.

疑问,在trace_current_buffer_unlock_commit()也会有一次唤醒,这里的trace_wake_up()是否可以去掉?

此外,从上面的代码中可以看出:

1: syscall tracer entry没有去跟踪CPU flags和preempt_count等信息.

2: syscall tracer entry写入的消息type为TRACE_SYSCALL_ENTER

4.2: syscall exit分析

在上面的分析中,提到过,在系统调用退出之前会调用syscall_trace_leave(),该函数代码段如下:

asmregparm void syscall_trace_leave(struct pt_regs *regs)

{

......

......

if (unlikely(test_thread_flag(TIF_SYSCALL_FTRACE)))

ftrace_syscall_exit(regs);

......

......

}

由此可见,流程会转入到ftrace_syscall_exit(),代码如下:

void ftrace_syscall_exit(struct pt_regs *regs)

{

struct syscall_trace_exit *entry;

struct syscall_metadata *sys_data;

struct ring_buffer_event *event;

int syscall_nr;

syscall_nr = syscall_get_nr(current, regs);

sys_data = syscall_nr_to_meta(syscall_nr);

if (!sys_data)

return;

event = trace_current_buffer_lock_reserve(TRACE_SYSCALL_EXIT,

sizeof(*entry), 0, 0);

if (!event)

return;

entry = ring_buffer_event_data(event);

entry->nr = syscall_nr;

entry->ret = syscall_get_return_value(current, regs);

trace_current_buffer_unlock_commit(event, 0, 0);

trace_wake_up();

}

这个过程跟syscall tracer entry大部份都一样,不同的是,这里的数据组织是不一样的,这种情况下,数织组织是放在struct syscall_trace_exit中的:

struct syscall_trace_exit {

struct trace_entry  ent;

int         nr;

unsigned long       ret;

};

Nr是系统调用号,ret是系统调用的返回值.

系统调用的返回值很好取,它就是存放在reg->ax中.

另外,它的数据type为TRACE_SYSCALL_EXIT.

此外,其它操作都跟ftrace_syscall_enter()中是一样的,这里就不做重复分析.

五: syscall tracer的数据显示

在实始化的时候,我们看到它注册了两种trace_event,现在是到分析它们的时候了.他们的定义如下:

static struct trace_event syscall_enter_event = {

.type      = TRACE_SYSCALL_ENTER,

.trace      = print_syscall_enter,

};

static struct trace_event syscall_exit_event = {

.type      = TRACE_SYSCALL_EXIT,

.trace      = print_syscall_exit,

};

一个是用来输出syscall entry信息的,另一个是用来输出syscall exit 信息的.

先来看syscall entry信息的输出.

5.1: sycall entry信息的输出

它的输了操作是在print_syscall_enter()中完成的,代码如下:

enum print_line_t

print_syscall_enter(struct trace_iterator *iter, int flags)

{

struct trace_seq *s = &iter->seq;

struct trace_entry *ent = iter->ent;

struct syscall_trace_enter *trace;

struct syscall_metadata *entry;

int i, ret, syscall;

/*将ent转换成 struct trace_entry*/

trace_assign_type(trace, ent);

/*取得系统调用号*/

syscall = trace->nr;

/*取得该系统调用号对应的syscall_metadata*/

entry = syscall_nr_to_meta(syscall);

if (!entry)

goto end;

/*显示”系统调用名称(“*/

ret = trace_seq_printf(s, "%s(", entry->name);

if (!ret)

return TRACE_TYPE_PARTIAL_LINE;

/*循环输出每个参数的信息*/

for (i = 0; i < entry->nb_args; i++) {

/* parameter types */

/*如果设置了TRACE_SYSCALLS_OPT_TYPES 标志,就需要输出系统

*调用参数的类型,这些信息都是保存在syscall_metadata 中的

*/

if (syscalls_flags.val & TRACE_SYSCALLS_OPT_TYPES) {

ret = trace_seq_printf(s, "%s ", entry->types[i]);

if (!ret)

return TRACE_TYPE_PARTIAL_LINE;

}

/* parameter values */

/*输出参数的名称和参数的值,如果是最后一个参数,附加”)”,否则

*附加”,”*/

ret = trace_seq_printf(s, "%s: %lx%s ", entry->args[i],

trace->args[i],

i == entry->nb_args - 1 ? ")" : ",");

if (!ret)

return TRACE_TYPE_PARTIAL_LINE;

}

/*末尾输出”/n”*/

end:

trace_seq_printf(s, "\n");

return TRACE_TYPE_HANDLED;

}

这个函数比较简单,对照代码中的注释应该很容易看懂,这里就不加详细分析了.

5.2: syscall exit信息的输出

对应的接口为print_syscall_exit().代码如下:

enum print_line_t

print_syscall_exit(struct trace_iterator *iter, int flags)

{

struct trace_seq *s = &iter->seq;

struct trace_entry *ent = iter->ent;

struct syscall_trace_exit *trace;

int syscall;

struct syscall_metadata *entry;

int ret;

trace_assign_type(trace, ent);

syscall = trace->nr;

entry = syscall_nr_to_meta(syscall);

if (!entry) {

trace_seq_printf(s, "\n");

return TRACE_TYPE_HANDLED;

}

ret = trace_seq_printf(s, "%s -> 0x%lx\n", entry->name,

trace->ret);

if (!ret)

return TRACE_TYPE_PARTIAL_LINE;

return TRACE_TYPE_HANDLED;

}

这个函数也很简单,它就是输出”系统调用名称 -> 返回值”.

六: 小结

总的来说,syscall tracer代码比较清晰, 是一个极容易理解的tracer, 以它为起点分析tracer, 对于理顺前面的框架分析是很有帮助的.

Linux的syscall源码,Linux内核跟踪之syscall tracer相关推荐

  1. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  2. linux c free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  3. linux usb摄像头 源码,Linux USB摄像头驱动实现源码分析

    Spac5xx的实现是按照标准的USB VIDEO设备的驱动框架编写(其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.c文件),整个源程序由四个主体部分组 ...

  4. linux 循环缓冲区 源码,Linux中的循环缓冲区

    在学习到 并发和竞态 时,其中的提到了缓冲区,用于实现免锁算法,这里转载的是大神有关循环缓冲区做的一些操作. 其中源代码在最下面的附件中,有关作者的讲解感觉很清晰,很好,不过这里说一下自己的见解: 点 ...

  5. linux 虚拟网卡 源码,Linux的虚拟网卡TUN和TAP

    TUN/TAP 提供了给用户空间程序的包的接收和传输,它可以看成是简单的点对点设备或是 以太网设备.它不是从物理设备接收包,而是从用户空间程序接收包.它发送包不是通过物 理设备来发送包,而是将这些包写 ...

  6. 传奇游戏源码 Linux版本 传奇源码 Linux版 三端源码和搭建, 然后打包生成APP

    此源码牛逼拉萨, 因为鄙人玩了好一段时间, 故此搞篇文章记录下几个技术关键点 Linux架设教程 先决条件: CentOS 7 Nginx 1.8 mysql 5.6 php 5.6 建议使用 IP: ...

  7. linux声卡驱动源码,Linux声卡驱动移植和測试(示例代码)

    一.分析驱动程序,依据开发板改动代码 代码太长,就不贴了,几个注意点: 1. 查看开发板原理图和S3C2410的datasheet,UDA1341的L3MODE.L3DATA.L3CLOCK分别与S3 ...

  8. linux 虚拟文件系统 源码,Linux内核源代码情状分析-虚拟文件系统

    Linux内核源代码情景分析-虚拟文件系统 我们先来看两张图: 第一张是VFS与具体文件系统的关系示意图: 第二张是Linux文件系统的层次结构: 特殊文件:用来实现"管道"的文件 ...

  9. LINUX进程调度分析源码,Linux 实时调度(源码分析)

    为了弄清楚在多cpu系统中是如何实现实时调度的,先引入以下几个概念: cpu的状态: 我们知道,在linux系统中,任务的优先级为0~140. INVALID:(-1)该cpu不可用 IDLE(0): ...

  10. linux运行geoserver源码,Linux 下Geoserver 的部署

    之前做的是在windows下的Geoserver openlayers 的部署开发 现在需求是将这套系统移植到Linux下,首先先介绍如何在 Linux下部署Geoserver 关于Geoserver ...

最新文章

  1. 贵州二本好的计算机专业,官方支持贵州大学创建双一流大学,贵州唯一的211,二本也有机会...
  2. 树莓派保卫战--防止SSH暴力破解
  3. hdu2152(普通母函数)
  4. oracle 月份期差,Oracle Database 日期算术-日期之间的月份或年份之间的差异
  5. strace调试(Linux Device Driver)
  6. 手机系统安装打印机服务器错误代码,OKI打印机报错?各型号代码故障解决方法...
  7. MongoDB介绍与安装
  8. VMWare 修改虚拟机的swap文件
  9. js怎么识别图片中的文字,js图片文字识别代码
  10. 2010国家节假日安排
  11. 试验Windows Embedded Standard 7 Service Pack 1 Evaluation Edition
  12. 问卷调查的数据如何分析?
  13. 10.原码、反码、补码
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  15. java中构造方法的理解,super()与构造方法,无参,有参构造方法,this()与构造方法
  16. BPF入门1:BPF技术简介
  17. HOperatorSet.GrabImageAsync(out ho_Image, hv_AcqHandle, -1);出现异常
  18. 董明珠再砸150亿,欲建立自主创新智能制造产业基地
  19. Java字节流读取shp_SHN系列语音卡调试文档.doc
  20. python b站 排行_用python爬虫追踪知乎/B站大V排行

热门文章

  1. rocketmq消费
  2. HTTP 304状态码
  3. WPF的5种绑定模式(mode)
  4. WordPress更新提示无法创建目录的解决方案
  5. javascript常见的数组方法
  6. Linux平台搭建Discuz
  7. linux中如何记录时间
  8. 捕获浏览器关闭、刷新事件,在窗体关闭时从全局对象里移除当前用户
  9. PHP 删除文件,文件下的目录
  10. PHPeclipse操作svn