MSVC 2010: Listing 17.1: MSVC 2010

    1. push 128 ; 00000080H
    2. push 4
    3. push 0
    4. push 1
    5. push -1073741824 ; c0000000H
    6. push OFFSET $SG78813
    7. call DWORD PTR __imp__CreateFileA@28
    8. mov DWORD PTR _fh$[ebp], eax

    我们再查看WinNT.h:

    Listing 17.2: WinNT.h

    1. #define GENERIC_READ (0x80000000L)
    2. #define GENERIC_WRITE (0x40000000L)
    3. #define GENERIC_EXECUTE (0x20000000L)
    4. #define GENERIC_ALL (0x10000000L)

    容易看出GENERIC_READ | GENERIC_WRITE = 0x80000000 | 0x40000000 = 0xC0000000,该值作为CreateFile()1函数的第二个参数。 CreateFile()如何检查该标志呢? 以Windows XP SP3 x86为例,在kernel32.dll中查看CreateFileW检查该标志的代码片段: Listing 17.3: KERNEL32.DLL (Windows XP SP3 x86)

    if ((dwDesiredAccess&0x40000000) == 0) goto loc_7C83D417

    如果AND指令没有设置ZF位,JZ将不触发跳转。如果dwDesiredAccess不等于0x40000000,AND结果将是0,ZF位将会被设置,条件跳转将被触发。

    我们在linux GCC 4.4.1下查看:

    1. #include <stdio.h>
    2. #include <fcntl.h>
    3. void main()
    4. {
    5. int handle;
    6. handle=open ("file", O_RDWR | O_CREAT);
    7. };

    我们得到: Listing 17.4: GCC 4.4.1

    1. public main
    2. main proc near
    3. var_20 = dword ptr -20h
    4. var_1C = dword ptr -1Ch
    5. var_4 = dword ptr -4
    6. mov ebp, esp
    7. sub esp, 20h
    8. mov [esp+20h+var_1C], 42h
    9. mov [esp+20h+var_20], offset aFile ; "file"
    10. call _open
    11. mov [esp+20h+var_4], eax
    12. leave
    13. retn
    14. main endp

    因此open()对于标志位的检测在内核中。 对于linux2.6,当sys_open被调用时,最终传递到do_sys_open内核函数,然后进入do_filp_open()函数(该函数位于源码fs/namei.c中)。 除了通过堆栈传递参数,还可以通过寄存器传递方式,这种调用方式成为fastcall(47.3)。这种调用方式CPU不需要访问堆栈就可以直接读取参数的值,所以速度更快。GCC有编译选项regram2,可以设置通过寄存器传递的参数的个数。 Linux2.6内核编译附加选项为-mregram=33 4。 这意味着前3个参数通过EAX、EDX、ECX寄存器传递,剩余的参数通过堆栈传递。如果参数小于3,仅部分寄存器被使用。 我们下载linux内核2.6.31源码,在Ubuntu中编译:make vmlinux,在IDA中打开,找到do_filp_open()函数。在开始部分我们可以看到(注释个人添加): Listing 17.6:do_filp_open() (linux kernel 2.6.31)

    1. do_filp_open proc near
    2. ...
    3. push ebp
    4. mov ebp, esp
    5. push edi
    6. push esi
    7. push ebx
    8. mov ebx, ecx
    9. add ebx, 1
    10. sub esp, 98h
    11. mov esi, [ebp+arg_4] ; acc_mode (5th arg)
    12. test bl, 3
    13. mov [ebp+var_80], eax ; dfd (1th arg)
    14. mov [ebp+var_7C], edx ; pathname (2th arg)
    15. mov [ebp+var_78], ecx ; open_flag (3th arg)
    16. jnz short loc_C01EF684
    17. mov ebx, ecx ; ebx <- open_flag

    GCC保存3个参数的值到堆栈。否则,可能会造成寄存器浪费。 我们来看代码片段: Listing 17.7: do_filp_open() (linux kernel 2.6.31)

    1. loc_C01EF6B4: ; CODE XREF: do_filp_open+4F
    2. test bl, 40h ; O_CREAT
    3. jnz loc_C01EF810
    4. mov edi, ebx
    5. shr edi, 11h
    6. and edi, 1
    7. jz short loc_C01EF6D3
    8. or edi, 2

    O_CREAT宏等于0x40,如果open_flag为0x40,标志位被置1,接下来的JNZ指令将被触发。

    17.1.2 ARM

    Linux kernel3.8.0检测O_CREAT过程有点不同。 Listing 17.8: linux kernel 3.8.0

    1. ...
    2. .text:C0169EA8 MOV R9, R3 ; R3 - (4th argument) open_flag
    3. ...
    4. .text:C0169ED4 LDR R6, [R9] ; R6 - open_flag
    5. ...
    6. .text:C0169F68 TST R6, #0x40 ; jumptable C0169F00 default case
    7. .text:C0169F6C BNE loc_C016A128
    8. .text:C0169F70 LDR R2, [R4,#0x10]
    9. .text:C0169F74 ADD R12, R4, #8
    10. .text:C0169F78 LDR R3, [R4,#0xC]
    11. .text:C0169F7C MOV R0, R4
    12. .text:C0169F80 STR R12, [R11,#var_50]
    13. .text:C0169F84 LDRB R3, [R2,R3]
    14. .text:C0169F88 MOV R2, R8
    15. .text:C0169F8C CMP R3, #0
    16. .text:C0169F90 ORRNE R1, R1, #3
    17. .text:C0169F94 STRNE R1, [R4,#0x24]
    18. .text:C0169F98 ANDS R3, R6, #0x200000
    19. .text:C0169F9C MOV R1, R12
    20. .text:C0169FA0 LDRNE R3, [R4,#0x24]
    21. .text:C0169FA4 ANDNE R3, R3, #1
    22. .text:C0169FA8 EORNE R3, R3, #1
    23. .text:C0169FAC STR R3, [R11,#var_54]
    24. .text:C0169FB0 SUB R3, R11, #-var_38
    25. .text:C0169FB4 BL lookup_fast
    26. ...
    27. .text:C016A128 loc_C016A128 ; CODE XREF: do_last.isra.14+DC
    28. .text:C016A128 MOV R0, R4
    29. ...

    TST指令类似于x86下的TEST指令。 这段代码来自do_last()函数源码,有两个分支lookup_fast()和complete_walk()。这里O_CREAT宏也等于0x40。