linux环境下的shellcode为什么不调用libc中的 调用库函数节点,而是利用系统调用

【转】&ARM&Linux系统调用的原理
ARM Linux系统调用的原理
http://blog.chinaunix.net/space.php?uid=&do=blog&id=160981
作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序和硬件之间设置一个额外层具有很多优点。首先,这使得编程更加容易,把用户从学
习硬件设备的低级编程特性中解放出来。其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。最后,更
重要的是这些接口使得程序具有可移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。
Unix系统通过向内核发出系统调用(system
call)实现了用户态进程和硬件设备之间的大部分接口。系统调用是操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。
应用编程接口(API)与系统调用的不同在于,前者只是一个函数定义,说明了如何获得一个给定的服务,而后者是通过软件中断向内核发出的一个明确的请求。POSIX标准针对API,而不针对系统调用。Unix系统给程序员提供了很多API库函数。libc的标准c库所定义的一些API引用了封装例程(wrapper routine)(其唯一目的就是发布系统调用)。通常情况下,每个系统调用对应一个封装例程,而封装例程定义了应用程序使用的API。反之则不然,一个API没必要对应一个特定的系统调用。从编程者的观点看,API和系统调用之间的差别是没有关系的:唯一相关的事情就是函数名、参数类型及返回代码的含义。然而,从内核设计者的观点看,这种差别确实有关系,因为系统调用属于内核,而用户态的库函数不属于内核。
大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用。返回-1通常表示内核不能满足进程的请求。系统调用处理程序的失败可能是由无效参数引起的,也可能是因为缺乏可用资源,或硬件出了问题等等。在libc库中定义的errno变量包含特定的出错码,每个出错码定义为一个常量宏。
当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统调用号(system call
number)的参数来识别所需的系统调用。所有的系统调用核都返回一个整数值。这些返回值与封装例程返回值的约定是不同的。在内中,整数或0表示系统调用成功结束,而负数表示一个出错条件。在后一种情况下,这个值就是存放在errno变量中必须返回给应用程序的负出错码。
ARM Linux系统利用SWI指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而实现从用户模式到管理模式的变换,CPSR保存到管理模式的SPSR,执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。指令格式如下:
SWI{cond} immed_24
24位立即数,值为从0——之间的整数。
使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。
1)、指令中24位的立即数指定了用户请求的服务类型,参数通过通用寄存器传递。SWI异常处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。如:
MOV R0,#34
2)、指令中的24位立即数被忽略,用户请求的服务类型由寄存器R0的值决定,参数通过其他的通用寄存器传递。如:
MOV R0, #12
MOV R1, #34
在SWI异常处理程序中,取出SWI立即数的步骤为:首先确定引起软件中断的SWI指令是ARM指令还是Thumb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数(低24位)。
由用户空间进入系统调用
通常情况下,我们写的用户空间应用程序都是通过封装的C lib来调用系统调用的。以0.9.30版uClibc中的open为例,来追踪一下这个封装的函数是如何一步一步的调用系统调用的。在include/fcntl.h中有定义:
#& define open
open实际上只是open64的一个别名而已。
在libc/sysdeps/linux/common/open64.c中可以看到:
extern __typeof(open64) __libc_open64;
extern __typeof(open) __libc_
可见open64也只不过是__libc_open64的别名,而__libc_open64函数在同一个文件中定义:
libc_hidden_proto(__libc_open64)
int __libc_open64 (const char *file, int oflag,
mode_t mode = 0;
(oflag & O_CREAT)
va_start (arg, oflag);
mode = va_arg (arg, mode_t);
va_end (arg);
return __libc_open(file, oflag | O_LARGEFILE, mode);
libc_hidden_def(__libc_open64)
最终__libc_open64又调用了__libc_open函数,这个函数在文件libc/sysdeps/linux/common/open.c中定义:
libc_hidden_proto(__libc_open)
int __libc_open(const char *file, int oflag,
mode_t mode = 0;
if (oflag & O_CREAT) {
va_start (arg, oflag);
mode = va_arg (arg, mode_t);
return __syscall_open(file, oflag, mode);
libc_hidden_def(__libc_open)
这个函数,也是仅仅根据打开标志oflag的值,来判断是否有第三个参数,若由,则获得其值。之后,便用获得的参数来调用__syscall_open(file, oflag,
__syscall_open在同一个文件中定义:
static __inline__ _syscall3(int, __syscall_open, const char
int, flags, __kernel_mode_t, mode)
在文件libc/sysdeps/linux/arm/bits/syscalls.h文件中可以看到:
#undef _syscall3
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
type name(type1 arg1,type2 arg2,type3 arg3)
return (type) (INLINE_SYSCALL(name, 3, arg1, arg2, arg3));
这个宏实际上完成定义一个函数的工作,宏的第一个参数是函数的返回值类型,第二个参数是函数名,之后的参数就如同它们的参数名所表明的那样,分别是函数的参数类型及参数名。__syscall_open实际上为:
int __syscall_open (const char * file,int flags,
__kernel_mode_t mode)
return (int) (INLINE_SYSCALL(__syscall_open, 3, file,
flags, mode));
INLINE_SYSCALL为同一个文件中定义的宏:
#undef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr, args...)&&&&&&&&&&&
& ({ unsigned int
_inline_sys_result = INTERNAL_SYSCALL (name, , nr,
if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_inline_sys_result,
{&&&&&&&&&&&&&&&&&&&&&&&
&__set_errno
(INTERNAL_SYSCALL_ERRNO (_inline_sys_result, ));&&&
&_inline_sys_result =
(unsigned int) -1;&&&&&&&&&
}&&&&&&&&&&&&&&&&&&&&&&&
(int) _inline_sys_ })
INLINE_SYSCALL宏中最值得注意的是INTERNAL_SYSCALL,其定义如下:
#undef INTERNAL_SYSCALL
#if !defined(__thumb__)
#if defined(__ARM_EABI__)
#define INTERNAL_SYSCALL(name, err, nr,
args...)&&&&&&&
& ({unsigned int
__sys_&&&&&&&&&&&&&&&&
{&&&&&&&&&&&&&&&&&&&&&&&&&
register int _a1 __asm__ ("r0"), _nr __asm__ ("r7");&&&
LOAD_ARGS_##nr (args)&&&&&&&&&&&&&&&
_nr = SYS_ify(name);&&&&&&&&&&&&&&&&
__asm__ __volatile__ ("swi&
syscall " #name&
: "=r" (_a1)&&&&&&&&&&&
: "r" (_nr) ASM_ARGS_##nr&&&&&&&
: "memory");&&&&&&&&&&&
__sys_result = _a1;&&&&&&&&
}&&&&&&&&&&&&&&&&&&&&&&&&&
(int) __sys_ })
#define INTERNAL_SYSCALL(name, err, nr,
args...)&&&&&&&
& ({ unsigned int
__sys_&&&&&&&&&&&&&&&
{&&&&&&&&&&&&&&&&&&&&&&&&&
register int _a1 __asm__ ("a1");&&&&&&&&&&&&&&
LOAD_ARGS_##nr (args)&&&&&&&&&&&&&&&
__asm__ __volatile__ ("swi&
%1 @ syscall " #name&
: "=r" (_a1)&&&&&&&&&&&&&&
: "i" (SYS_ify(name)) ASM_ARGS_##nr&&&
: "memory");&&&&&&&&&&&&&&
__sys_result = _a1;&&&&&&&&&&&&&&&&&
}&&&&&&&&&&&&&&&&&&&&&&&&&
(int) __sys_ })
这里也将同文件中的LOAD_ARGS宏的定义贴出来:
#define LOAD_ARGS_0()
#define ASM_ARGS_0
#define LOAD_ARGS_1(a1)&&&&&&&&&&
& _a1 = (int)
(a1);&&&&&&&&&&&
& LOAD_ARGS_0
#define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)
#define LOAD_ARGS_2(a1, a2)&&&&&&
& register int _a2
__asm__ ("a2") = (int) (a2);&& \
& LOAD_ARGS_1
#define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)
#define LOAD_ARGS_3(a1, a2, a3)&&&&&&&&
& register int _a3
__asm__ ("a3") = (int) (a3);&& \
& LOAD_ARGS_2 (a1,
这几个宏用来在寄存器中加载相应的参数,参数传递的方式和普通的C函数也没有什么太大的区别,同样都是将参数列表中的参数依次放入寄存器r0、r1、r2、r3…中。
上面的SYS_ify(name)宏,是用来获得系统调用号的。
#define SYS_ify(syscall_name)& (__NR_##syscall_name)
也就是__NR___syscall_open,在libc/sysdeps/linux/common/open.c中可以看到这个宏的定义:
#define __NR___syscall_open __NR_open
__NR_open在内核代码的头文件中有定义。
在这里我们忽略定义__thumb__的情况,而假设我们编译出来的库函数使用的都是ARM指令集。在上面的代码中,我们看到,根据是否定义宏__ARM_EABI__,INTERNAL_SYSCALL会被展开为两种不同的版本。关于这一点,与应用二进制接口ABI有关,不同的ABI,则会有不同的传递系统调用号的方法。对于比较新的EABI,则在r7寄存器保存系统调用号,通过swi&&
0x0来陷入内核。否则,通过swi指令的24位立即数参数来传递系统调用号。后面还会有内核中关于这个问题的更详细的说明。
同时这两种调用方式的系统调用号也是存在这区别的,在内核的文件arch/arm/inclue/asm/unistd.h中可以看到:
#define __NR_OABI_SYSCALL_BASE 0x900000
#if defined(__thumb__) ||
defined(__ARM_EABI__)
#define __NR_SYSCALL_BASE&& 0
#define __NR_SYSCALL_BASE&&
__NR_OABI_SYSCALL_BASE
#define __NR_restart_syscall&&&&&
(__NR_SYSCALL_BASE+&
#define __NR_exit&&&&&&&
(__NR_SYSCALL_BASE+&
#define __NR_fork&&&&&&&
(__NR_SYSCALL_BASE+&
#define __NR_read&&&&&&&
(__NR_SYSCALL_BASE+&
#define __NR_write&&&&&&
(__NR_SYSCALL_BASE+&
#define __NR_open&&&&&&&
(__NR_SYSCALL_BASE+&
接下来来看操作系统对系统调用的处理。我们回到ARM Linux的异常向量表,因为当执行swi时,会从异常向量表中取例程的地址从而跳转到相应的处理程序中。在文件arch/arm/kernel/entry-armv.S中我们看到SWI异常向量:
&& W(ldr) pc, .LCvswi +
stubs_offset
而.LCvswi在同一个文件中定义为:
.word vector_swi
也就是最终会执行例程vector_swi来完成对系统调用的处理,接下来我们来看下在arch/arm/kernel/entry-common.S中定义的vector_swi例程(删去一些和我们的示例平台无关的代码):
ENTRY(vector_swi)
sp, #S_FRAME_SIZE
stmia sp, {r0 - r12}&&&&&&&
@ Calling r0 - r12
sp, #S_PC&&&&&
&ARM( stmdb r8, {sp,
)& @ Calling sp,
spsr&&&&&&&
@ called from non-FIQ mode, so ok.
[sp, #S_PC]&&&&&&
@ Save calling PC
[sp, #S_PSR]&&&&&
@ Save CPSR
[sp, #S_OLD_R0]&&&&&
@ Save OLD_R0
#if defined(CONFIG_OABI_COMPAT)
ldr&& r10,
[lr, #-4]&&&&&&&
@ get SWI instruction
#ifdef CONFIG_CPU_ENDIAN_BE8
//rev指令的功能是反转字中的字节序
rev&& r10,
r10&&&&&&&
@ little endian instruction
#elif defined(CONFIG_AEABI)
ldr&& scno,
[lr, #-4]&&&&&&
@ get SWI instruction
#ifdef CONFIG_ALIGNMENT_TRAP
__cr_alignment
mcr&& p15,
0, ip, c1, c0&&& @
update control register
enable_irq
// tsk 是寄存器r9的别名,在arch/arm/kernel/entry-header.S中定义:// tsk .req&& r9&&&&
@ current thread_info
// 获得线程对象的基地址。
get_thread_info tsk
// tbl是r8寄存器的别名,在arch/arm/kernel/entry-header.S中定义:
// tbl& .req&& r8&&&&
@ syscall table pointer,
// 用来存放系统调用表的指针,系统调用表在后面调用
adr&& tbl,
sys_call_table&&&&&
@ load syscall table pointer
[tsk, #TI_FLAGS]&&&&
@ check for syscall tracing
#if defined(CONFIG_OABI_COMPAT)
bics& r10, r10,
#0xff000000
eorne scno, r10, #__NR_OABI_SYSCALL_BASE
ldrne tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
scno是寄存器r7的别名
bic&& scno,
scno, #0xff000000&&&&
@ mask off SWI op-code
eor&& scno,
scno, #__NR_SYSCALL_BASE @ check OS number
stmdb sp!, {r4, r5}&&&&&&&&
@ push fifth and sixth args
#_TIF_SYSCALL_TRACE&&&&
@ are we tracing syscalls?
__sys_trace
cmp&& scno,
#NR_syscalls&&& @
check upper syscall limit
BSYM(ret_fast_syscall)& @
return address
ldrcc pc, [tbl, scno, lsl #2]&&&&
@ call sys_* routine
sp, #S_OFF
// why也是r8寄存器的别名
2: mov&& why, #0&&&&&&&&&&&
@ no longer a real syscall
cmp&& scno,
#(__ARM_NR_BASE - __NR_SYSCALL_BASE)
scno, #__NR_SYSCALL_BASE&& @ put OS number
arm_syscall
sys_ni_syscall&&&&&&&
@ not private func
ENDPROC(vector_swi)
上面的zero_fp是一个宏,在arch/arm/kernel/entry-header.S中定义:
.macro zero_fp
#ifdef CONFIG_FRAME_POINTER
而fp位寄存器r11。
像每一个异常处理程序一样,要做的第一件事当然就是保护现场了。紧接着是获得系统调用的系统调用号。然后以系统调用号作为索引来查找系统调用表,如果系统调用号正常的话,就会调用相应的处理例程来处理,就是上面的那个ldrcc& pc,
[tbl, scno, lsl #2]语句,然后通过例程ret_fast_syscall来返回。
在这个地方我们接着来讨论ABI的问题。现在,我们首先来看两个宏,一个是CONFIG_OABI_COMPAT 意思是说与old ABI兼容,另一个是CONFIG_AEABI 意思是说指定现在的方式为EABI。这两个宏可以同时配置,也可以都不配,也可以配置任何一种。我们来看一下内核是怎么处理这一问题的。我们知道,sys_call_table
在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open)。内核是根据一个系统调用号(对于EABI来说为系统调用表的索引)找到实际该调用内核哪个函数,然后通过运行该函数完成系统调用的。
首先,对于old
ABI,内核给出的处理是为它建立一个单独的system call table,叫sys_oabi_call_table。这样,兼容方式下就会有两个system call table, 以old ABI方式的系统调用会执行old_syscall_table表中的系统调用函数,EABI方式的系统调用会用sys_call_table中的函数指针。配置无外乎以下4中:
第一、两个宏都配置行为就是上面说的那样。 第二、只配置CONFIG_OABI_COMPAT,那么以old ABI方式调用的会用sys_oabi_call_table,以EABI方式调用的用sys_call_table,和1实质上是相同的。只是情况1更加明确。第三、只配置CONFIG_AEABI系统中不存在sys_oabi_call_table,对old ABI方式调用不兼容。只能 以EABI方式调用,用sys_call_table。
第四、两个都没有配置,系统默认会只允许old ABI方式,但是不存在old_syscall_table,最终会通过sys_call_table 完成函数调用
系统会根据ABI的不同而将相应的系统调用表的基地址加载进tbl寄存器,也就是r8寄存器。接下来来看系统调用表,如前面所说的那样,有两个,同样都在文件arch/arm/kernel/entry-armv.S中:
#define ABI(native, compat) native
#ifdef CONFIG_AEABI
#define OBSOLETE(syscall) sys_ni_syscall
#define OBSOLETE(syscall) syscall
.type sys_call_table, #object
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
另外一个为:
#define ABI(native, compat) compat
#define OBSOLETE(syscall) syscall
.type sys_oabi_call_table, #object
ENTRY(sys_oabi_call_table)
#include "calls.S"
#undef ABI
#undef OBSOLETE
这样看来貌似两个系统调用表是完全一样的。这里预处理指令include的独特用法也挺有意思,系统调用表的内容就是整个arch/arm/kernel/calls.S文件的内容(由于太长,这里就不全部列出了):
CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
CALL(sys_write)
CALL(sys_open)
CALL(sys_close)
上面的CALL()是个宏,它同样在文件arch/arm/kernel/entry-armv.S中定义:
#define CALL(x) .equ
NR_syscalls,NR_syscalls+1
#include "calls.S"
#undef CALL
#define CALL(x) .long x
在定义宏CALL()的地方,我们看到calls.S已经被包含了一次,只不过在这里,不是为了建立系统调用表,而仅仅是为了获得系统的系统调用的数量,并保存在宏NR_syscalls中。在SWI向量中,我们也看到,是使用了这个宏的。
最后再罗嗦一点,如果用sys_open来搜的话,是搜不到系统调用open的定义的,系统调用函数都是用宏来定义的,比如对于open,有这样的定义:
---------------------------------------------------------------------
1066 SYSCALL_DEFINE3(open, const char __user *, filename,
int, flags, int, mode)
1068&&&&&&&
1070&&&&&&&&
if (force_o_largefile())
1071&&&&&&&&&&&&&&&&
flags |= O_LARGEFILE;
1073&&&&&&&&
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
1074&&&&&&&&
1075&&&&&&&&
asmlinkage_protect(3, ret, filename, flags, mode);
1076&&&&&&&&
---------------------------------------------------------------------
继续回到vector_swi,如果系统调用号不正确,则会调用arm_syscall函数来进行处理,这个函数定义如下:
---------------------------------------------------------------------
arch/arm/kernel/traps.c
465 #define NR(x) ((__ARM_NR_##x) -
__ARM_NR_BASE)
466 asmlinkage int arm_syscall(int no, struct pt_regs
468&& struct thread_info
*thread = current_thread_info();
469&& siginfo_t
471&& if ((no
(__ARM_NR_BASE&& 16))
472&&&&&&&
return bad_syscall(no, regs);
474&& switch (no
& 0xffff) {
475&& case
476&&&&&&&
info.si_signo = SIGSEGV;
477&&&&&&&
info.si_errno = 0;
478&&&&&&&
info.si_code& =
SEGV_MAPERR;
479&&&&&&&
info.si_addr& =
481&&&&&&&
arm_notify_die("branch through zero", regs, &info,
482&&&&&&&
484&& case
NR(breakpoint):
485&&&&&&&
regs-&ARM_pc -= thumb_mode(regs) ? 2 :
486&&&&&&&
ptrace_break(current, regs);
487&&&&&&&
return regs-&ARM_r0;
503&& case
NR(cacheflush):
504&&&&&&&
do_cache_op(regs-&ARM_r0,
regs-&ARM_r1,
regs-&ARM_r2);
505&&&&&&&
507&& case
NR(usr26):
508&&&&&&&
if (!(elf_hwcap & HWCAP_26BIT))
509&&&&&&&&&&&&
510&&&&&&&
regs-&ARM_cpsr &=
~MODE32_BIT;
511&&&&&&&
return regs-&ARM_r0;
513&& case
NR(usr32):
514&&&&&&&
if (!(elf_hwcap & HWCAP_26BIT))
515&&&&&&&&&&&&
516&&&&&&&
regs-&ARM_cpsr |= MODE32_BIT;
517&&&&&&&
return regs-&ARM_r0;
519&& case
NR(set_tls):
520&&&&&&&
thread-&tp_value =
regs-&ARM_r0;
521 #if defined(CONFIG_HAS_TLS_REG)
522&&&&&&&
asm ("mcr p15, 0, %0, c13, c0, 3" : : "r"
(regs-&ARM_r0) );
523 #elif !defined(CONFIG_TLS_REG_EMUL)
524&&&&&&&
530&&&&&&&
*((unsigned int *)0xffff0ff0) =
regs-&ARM_r0;
531 #endif
532&&&&&&&
534 #ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG
NR(cmpxchg):
547& for (;;)
extern void do_DataAbort(unsigned long addr, unsigned int
549&&&&&&&&&&&&&&&&&&&&&&&&&&&&
struct pt_regs *regs);
unsigned long addr = regs-&ARM_r2;
struct mm_struct *mm = current-&
pgd_t * pmd_t * pte_t *
spinlock_t *
regs-&ARM_cpsr &=
~PSR_C_BIT;
down_read(&mm-&mmap_sem);
pgd = pgd_offset(mm, addr);
if (!pgd_present(*pgd))
&&&&&&&&goto
pmd = pmd_offset(pgd, addr);
if (!pmd_present(*pmd))
563&&&&&&&&&&&&
pte = pte_offset_map_lock(mm, pmd, addr,
&if (!pte_present(*pte) ||
!pte_dirty(*pte)) {
566&&&&&&&&&&&&
pte_unmap_unlock(pte, ptl);
567&&&&&&&&&&&&
val = *(unsigned long *)
val -= regs-&ARM_r0;
if (val == 0) {
572&&&&&&&&&&&&
*(unsigned long *)addr =
regs-&ARM_r1;
573&&&&&&&&&&&&
regs-&ARM_cpsr |= PSR_C_BIT;
pte_unmap_unlock(pte, ptl);
up_read(&mm-&mmap_sem);
bad_access:
&up_read(&mm-&mmap_sem);
do_DataAbort(addr, 15 + (1 && 11),
584 #endif
587&&&&&&&
591&&&&&&&
if ((no & 0xffff) &=
592&&&&&&&&&&&
593&&&&&&&
595 #ifdef CONFIG_DEBUG_USER
600&& if (user_debug
& UDBG_SYSCALL) {
601&&&&&&&
printk("[%d] %s: arm syscall %d\n",
602&& &&&&&&&&&task_pid_nr(current),
current-&comm, no);
603&&&&&&&
dump_instr("", regs);
604&&&&&&&
if (user_mode(regs)) {
605&&&&&&&&&&&&
__show_regs(regs);
606&&&&&&&&&&
&&c_backtrace(regs-&ARM_fp,
processor_mode(regs));
607&&&&&&&
609 #endif
610&& info.si_signo =
611&& info.si_errno =
612&& info.si_code
ILL_ILLTRP;
info.si_addr& = (void __user
*)instruction_pointer(regs) -
614&&&&&&&&&&&&
(thumb_mode(regs) ? 2 : 4);
616&& arm_notify_die("Oops
- bad syscall(2)", regs, &info, no,
617&& return
---------------------------------------------------------------------
这个函数处理所有的辨别不出来的系统调用。系统调用号正确也好不正确也好,最终都是通过ret_fast_syscall例程来返回,因为我们看到,在进入系统调用处理函数之前,先加载了符号ret_fast_syscall进lr寄存器。ret_fast_syscall定义如下:
---------------------------------------------------------------------
arch/arm/kernel/entry-common.S
ret_fast_syscall:
&UNWIND(.fnstart&& )
&UNWIND(.cantunwind&& )
disable_irq&&&&&&&&&&
@ disable interrupts
[tsk, #TI_FLAGS]
#_TIF_WORK_MASK
fast_work_pending
arch_ret_to_user r1, lr
restore_user_regs fast = 1, offset = S_OFF
&UNWIND(.fnend&&&&
fast_work_pending:
[sp, #S_R0+S_OFF]!&&&&&
@ returned r0
work_pending:
#_TIF_NEED_RESCHED
work_resched
#_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
no_work_pending
sp&&&&&&&&&
why&&&&&&&&&&&
@ 'syscall'
bl do_notify_resume
ret_slow_syscall&&&&&
@ Check work again
work_resched:
bl schedule
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq&&&&&&&&&&
@ disable interrupts
[tsk, #TI_FLAGS]
#_TIF_WORK_MASK
work_pending
no_work_pending:
arch_ret_to_user r1, lr
restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user)
---------------------------------------------------------------------
对于我们的平台来说,上面的arch_ret_to_user为空。restore_user_regs宏用于恢复现场并返回,restore_user_regs宏定义如下:
---------------------------------------------------------------------
arch/arm/kernel/entry-header.S
.macro restore_user_regs, fast = 0, offset = 0
[sp, #\offset + S_PSR]& @
get calling cpsr
[sp, #\offset + S_PC]!& @
spsr_cxsf, r1&&&&&&&&
@ save in spsr_svc
#if defined(CONFIG_CPU_32v6K)
clrex&&&&&&&&&&&&&
@ clear the exclusive monitor
#elif defined (CONFIG_CPU_V6)
strex r1, r2, [sp]&&&&&&
@ clear the exclusive monitor
ldmdb sp, {r1 - lr}^&&&&&&&
@ get calling r1 - lr
ldmdb sp, {r0 - lr}^&&&&&&&
@ get calling r0 - lr
r0&&&&&&&&&
@ ARMv5T and earlier require a nop
&&&&&&&&&&&&&&&&&&
@ after ldm {}^
sp, #S_FRAME_SIZE - S_PC
movs& pc, lr&&&&&&&&&
@ return & move spsr_svc into cpsr
---------------------------------------------------------------------
添加新的系统调用
第一、打开arch/arm/kernel/calls.S,在最后添加系统调用的函数原型的指针,例如:
CALL(sys_set_senda)
补充说明一点关于NR_syscalls的东西,这个常量表示系统调用的总的个数,在较新版本的内核中,文件arch/arm/kernel/entry-common.S中可以找到:
NR_syscalls,0
#define CALL(x) .equ
NR_syscalls,NR_syscalls+1
#include "calls.S"
#undef CALL
#define CALL(x) .long x
相当的巧妙,不是吗?在系统调用表中每添加一个系统调用,NR_syscalls就自动增加一。在这个地方先求出NR_syscalls,然后重新定义CALL(x)宏,这样也可以不影响文件后面系统调用表的建立。
第二、打开include/asm-arm/unistd.h,添加系统调用号的宏,感觉这步可以省略,因为这个地方定义的系统调用号主要是个C库,比如uClibc、Glibc用的。例如:
&&&&#define
__NR_plan_set_senda&&&&&&&&&&&&
(__NR_SYSCALL_BASE+365)
为了向后兼容,系统调用只能增加而不能减少,这里的编号添加时,也必须按顺序来。否则会导致核心运行错误。
第三,实例化该系统调用,即编写新添加系统调用的实现例如:
SYSCALL_DEFINE1(set_senda, int,iset)
UART_PUT_CR(&at91_port[2],AT91C_US_SENDA);
UART_PUT_CR(&at91_port[2],AT91C_US_RSTSTA);
第四、打开include/linux/syscalls.h添加函数声明
&&&&asmlinkage
long sys_set_senda(int iset);
第五、在应用程序中调用该系统调用,可以参考uClibc的实现。
第六、结束。
参考文档:
[精华] arm Linux 2.6高版本中的系统调用方式
ARM&Linux下添加新的系统调用
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 linux shellcode 的文章

 

随机推荐