MIPS Linux O32 之间接系统调用

来自Jack's Lab
跳转到: 导航, 搜索


arch/mips/kernel/scall32-o32.S 中实现了这么一个函数 sys_syscall,该函数位于 O32 之系统调用表的第一项,系统调用号为 4000:

LEAF(sys_syscall)
subu    t0, a0, __NR_O32_Linux    # check syscall number
sltiu    v0, t0, __NR_O32_Linux_syscalls + 1
beqz    t0, einval        # do not recurse
sll    t1, t0, 3
beqz    v0, einval
lw    t2, sys_call_table(t1)        # syscall routine

/* Some syscalls like execve get their arguments from struct pt_regs
and claim zero arguments in the syscall table. Thus we have to
assume the worst case and shuffle around all potential arguments.
If you want performance, don't use indirect syscalls. */

move    a0, a1                # shift argument registers
move    a1, a2
move    a2, a3
lw    a3, 16(sp)
lw    t4, 20(sp)
lw    t5, 24(sp)
lw    t6, 28(sp)
sw    t4, 16(sp)
sw    t5, 20(sp)
sw    t6, 24(sp)
sw    a0, PT_R4(sp)            # .. and push back a0 - a3, some
sw    a1, PT_R5(sp)            # syscalls expect them there
sw    a2, PT_R6(sp)
sw    a3, PT_R7(sp)
sw    a3, PT_R26(sp)            # update a3 for syscall restarting
jr    t2
/* Unreached */

einval:    li    v0, -ENOSYS
jr    ra
END(sys_syscall)


其第一个参数 a0 是即将要执行的系统调用号,后面的参数是即将要执行的系统调用的参数,因此 sys_syscall 要完成内核栈的调整,然后去执行调用号为 a0 的系统调用,其原型可写为如下:


 sys_syscall (real_syscall_no, arg1, arg2, arg3 ...)


可用如下测试程序加深理解:

.text                             
.globl    main                    
.ent      main                  

main:                              
li        $4, 4011                # 现 a0 置 execve 系统调用号
lui       $5, %hi(cmd)            # Load Upper Immediate
addiu     $5, $5, %lo(cmd)       # execve 的第一个参数置于a1, 为字符串/bin/echo的首地址

lui       $6, %hi(argv)            # %hi, %lo为汇编器定义的宏,分别求地址的高16和低16位
addiu     $6, $6, %lo(argv)       # execve的第二个参数置于a2, 为字符指针数组的首地址

move      $7, $0                 # 第三个参数 a3 为0 (NULL)
li        $2, 4000                 # 将 sys_syscall 系统调用号置入v0寄存器
syscall                            # 触发系统调用

move      $2, $0                  #main的返回值0, 置于v0
jr        $31                     #main返回

.end   main                     


.data                             #以下内容位于目标文件的数据段

cmd:
.asciiz     "/bin/echo"

msg:
.asciiz     "Hello world!"

argv:
.word     cmd         # /bin/echo的首地址
.word     msg         # Hello world!的首地址
.word     0x0         # NULL


有意思的是这个两个操作: sw a3, PT_R7(sp); sw a3, PT_R26(sp)


a3 保存入 pt_regs 结构后,又将其扔入 k0 (r26) 在 pt_regs 的坑中。


事实上 Linux MIPS 之系统调用在设计时,将 a3 又设计为返回 error flag,因此在系统调用时将 error flag 适时放入 pt_regs 之 PT_R7 中,系统调用完,栈恢复时直接将该值恢复入 a3,用户态程序直接从 a3 中取 error flag 即可。


如: arch/mips/kernel/scall32-o32.S

......
li  t0, -EMAXERRNO - 1  # error?
sltu    t0, t0, v0
sw  t0, PT_R7(sp)       # set error flag
......

bad_stack:
negu    v0              # error
sw  v0, PT_R0(sp)
sw  v0, PT_R2(sp)
li  t0, 1               # set error flag
sw  t0, PT_R7(sp)
j   o32_syscall_exit
......


因此系统调用得这样封装:

#define _syscall1(type,fname,sname,atype,a) \
type fname(atype a) \
{ \
register unsigned long __a0 asm("$4") = (unsigned long) a; \
register unsigned long __a3 asm("$7"); \
unsigned long __v0; \
\
__asm__ volatile ( \
".set\tnoreorder\n\t" \
"li\t$2, %3\t\t\t# " #fname "\n\t" \
"syscall\n\t" \
"move\t%0, $2\n\t" \
".set\treorder" \
: "=&r" (__v0), "=r" (__a3) \
: "r" (__a0), "i" (__NR_##sname) \
: "$2", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \
"memory"); \
\
        if (__a3 == 0) \
return (type) __v0; \
return (type) -1; \
}


因此此处将 a3 写入 PT_R7(sp) 是有可能被覆盖的,为了保留 a3 以留他用,又将其保存于 k0 的坑中,根据 MIPS ABI,k0 和 k1 任何时候都是不需要保存的。









个人工具
名字空间

变换
操作
导航
工具箱