caritas 发表于 2004-12-27 10:31:01

用skyeye分析linux的fork函数执行的问题

大家好!最近我在用skyeye学习arm linux kernel。skyeye真是一个好东西,从
开机开始的Linux kernel代码的运行情况都可以近距离的观察。

不过,现在我遇到一个问题:所有fork出的子进程都会被kernel kill掉。

我用的toolchain是用crosstool编译的gcc-3.2.3 + glibc-2.3.2 +
    linux_kernel_header 2.4.24,目标为arm-softfloat
内核代码是linux-2.6.9或linux-2.4.24+vrs1patched
romfs image使用uclinux制作,把sash代码中的vfork改为fork,编译应用程序使
用的c库为uclibc,并打开了uclibc的mmu配置选项。

skyeye的配置为ep7312

使用这种方法制作的kernel和romfs image,在skyeye中可以正常启动内核,但
是在应用程序中一旦使用fork系统调用,子进程就会立刻被kill,而父进程还可以正常运行。通过在skyeye中在force_sig_info函数处设置断点发现,子进程一被
fork就立刻发生一次page fault,并被内核发送SIGSEGV信号kill。

如果sash中的fork改为vfork则一切正常。(skyeye test suite中的romfs及initrd
中只使用了vfork而没有使用fork)

察看uclibc的源码发现,在uclibc中fork和vfork的实现不同:
fork使用内联汇编实现:

#define INTERNAL_SYSCALL(name, err, nr, args...)                \
({ unsigned int _sys_result;                                  \
   {                                                          \
       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_result; })
#define _syscall0(type,name) \
type name(void) \
{ \
return (type) (INLINE_SYSCALL(name, 0)); \
}
#define __NR___libc_fork __NR_fork
      _syscall0(pid_t, __libc_fork);

而vfork使用汇编实现:

vfork:

#ifdef __NR_vfork
      swi   __NR_vfork
      cmn   r0, #4096
      movcc   pc, lr

      /* Check if vfork even exists.*/
      ldr   r1, =-ENOSYS
      teq   r0, r1
      bne   __syscall_error
#endif

按照vfork的方法,使用汇编语言实现了fork后,发现fork系统调用不再出错。
但是不知道为什么,所以向大家求教。
(我到armlinux上搜索了相关的主题,但没有人提到,所以怀疑是否与skyeye
相关,不知道用开发板的朋友有没有类似的问题)

另:请问除了使用gdb之外,还有什么方法可以反汇编,谢谢。

caritas 发表于 2004-12-27 17:06:28

one step forwoard

我用gcc -S,把uclibc中fork的汇编码dump出来,如下。

      .file   "syscalls.c"
      .text
      .align2
      .global __libc_fork
      .type   __libc_fork, %function
__libc_fork:
      @ args = 0, pretend = 0, frame = 0
      @ frame_needed = 0, uses_anonymous_args = 0
--->   stmfd   sp!, {r5, lr}
--->   swi   #9437186      @ syscall __libc_fork
      mov   r5, r0
      cmn   r0, #4096
      bls   .L2
      bl      __errno_location(PLT)
      rsb   r3, r5, #0
      str   r3,
      mvn   r5, #0
.L2:
      mov   r0, r5
      ldmfd   sp!, {r5, pc}
      .size   __libc_fork, .-__libc_fork
      .weak   fork
fork = __libc_fork
      .ident"GCC: (GNU) 3.3.2"

用这个文件代替系统的fork,出现和以前一样的错误。但是如果把上文汇编代
码中用---->标出的两句颠倒位置,则fork系统调用能够正常工作。
why?

颠倒后的代码为:      .file   "syscalls.c"
      .text
      .align2
      .global __libc_fork
      .type   __libc_fork, %function
__libc_fork:
      @ args = 0, pretend = 0, frame = 0
      @ frame_needed = 0, uses_anonymous_args = 0
      swi   #9437186      @ syscall __libc_fork
      stmfd   sp!, {r5, lr}
      mov   r5, r0
      cmn   r0, #4096
      bls   .L2
      bl      __errno_location(PLT)
      rsb   r3, r5, #0
      str   r3,
      mvn   r5, #0
.L2:
      mov   r0, r5
      ldmfd   sp!, {r5, pc}
      .size   __libc_fork, .-__libc_fork
      .weak   fork
fork = __libc_fork
      .ident"GCC: (GNU) 3.3.2"

chyyuu 发表于 2004-12-28 09:19:41

arm-elf-oujdump 可以反汇编。 gcc -S 也是一个好的选择!
欢迎用skyeye对内核进行探索。如果有了分析和结论,我会把你的文章定为精华,让更多的人进行学习。

caritas 发表于 2004-12-29 10:11:13

解决了

谢谢版主,objdump很好用!

通过使用skyeye的log功能,并对skyeye进行了一点小小的hack(让skyeye在
fork系统调用时开始输出log)终于察出了问题。
为便于描述,列出编号代码如下:

1   .file "syscalls.c"
2   .text
3   .align 2
4   .global __libc_fork
5   .type __libc_fork, %function
6   __libc_fork:
7   @ args = 0, pretend = 0, frame = 0
8   @ frame_needed = 0, uses_anonymous_args = 0
9   stmfd sp!, {r5, lr}
10 swi #9437186 @ syscall __libc_fork
11 mov r5, r0
12 cmn r0, #4096
13 bls .L2
14 bl __errno_location(PLT)
15 rsb r3, r5, #0
16 str r3,
17 mvn r5, #0
18 .L2:
19 mov r0, r5
20 ldmfd sp!, {r5, pc}
21 .size __libc_fork, .-__libc_fork
22 .weak fork
23 fork = __libc_fork
24 .ident "GCC: (GNU) 3.3.2"

Linux为了提高系统性能(减小物理内存使用量)对fork系统调用采用了写时复
制技术。即在fork系统调用后并不为子进程分配内存空间,只是在子进程的页
表中把相应的表项设置为无效。这样,当子进程退出fork系统调用执行第一条
访问内存指令(20行)时,会发生缺页异常,在由Linux系统给子进程分配相
应的存储空间后,重新执行访问内存指令。

问题在于:ARM CPU的不同版本对缺页异常的处理细节(Ldm, stm指令)
不同。

以下参考:ARM Architecture Reference Manual
2.6.5 Data Abort
There are three Abort Model in ARM arch.

Early Abort Model: used in some ARMv3 and earlier implementations.
In this model, base register wirteback occurred for LDC,LDM,STC,STM instructions, and the base register was unchanged for all other
instructions. (oldest)

Base Restored Abort Model: If a Data Abort occurs in an instruction
which specifies base register writeback, the value in the base register
is unchanged. (strongarm, xscale)

Base Updated Abort Model: If a Data Abort occurs in an instruction which
specifies base register writeback, the base register writeback still occurs.
(arm720T)

本文中使用的CPU配置为ep7312(arm720T),所以相应的模型为Base Update
Abort Model。Linux内核中也对此进行了处理,即如果是arm720T cpu,则对
于如上文所列20行处的指令,通过修改sp补偿对base address register的更新。

通过察看skyeye的log发现,skyeye的实现中并没有进行对ldm指令中base
address register的更新,而Linux内核仍然进行补偿,所以造成子进程堆栈
定位的错误而异常退出。

通过修改skyeye的LoadMult函数中更新base address register的部分,fork系
统调用现在已经可以正常工作。但这部分代码在原代码中是被注释掉的,并标
记为:
/* chy 2005-11-24, bug found by [email protected], etc */
不知这里的bug指的是什么?

还有一个问题:
Linux应该把子进程的页表项设为只读,而非不能读写。因此ldm指令不应产生
缺页异常。另,子进程退出fork系统调用的第一条指令(11行),会发生预取指异
常,这似乎也不应该发生,因为是读访问而非写访问。

chyyuu 发表于 2004-12-29 12:04:28

欢迎更多的人讨论,用skyeye动态分析比较有效!
页: [1]
查看完整版本: 用skyeye分析linux的fork函数执行的问题