- 第一章 CPU 简介
- 第二章 Hello,world!
- 第三章 函数开始和结束
- 第四章 栈
- Chapter 5 printf() 与参数处理
- Chapter 6 scanf()
- CHAPER7 访问传递参数
- Chapter 8 一个或者多个字的返回值
- Chapter 9 指针
- Chapter 10 条件跳转
- 第 11 章 选择结构 switch()/case/default
- 第 12 章 循环结构
- 第 13 章 strlen()
- Chapter 14 Division by 9
- chapter 15 用 FPU 工作
- Chapter 16 数组
- Chapter 17 位域
- 第 18 章 结构体
- 19 章 联合体
- 第二十章 函数指针
- 第 21 章 在 32 位环境中的 64 位值
- 第二十二章 SIMD
- 23 章 64 位化
- 24 章 使用 x64 下的 SIMD 来处理浮点数
- 25 章 温度转换
- 26 章 C99 的限制
- 27 章 内联函数
- 第 28 章 得到不正确反汇编结果
- 第 29 章 花指令
- 第 30 章 16 位 Windows
- 第 31 章 类
- 三十二 ostream
- 34.2.2 MSVC
- 34.2.3 C++ 11 std::forward_list
- 34.3 std::vector
- 34.4 std::map and std::set
17.1 Specific bit checking
17.1.1 x86
Win32 API 例子:
#!cpp
HANDLE fh;
fh=CreateFile ("file", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
MSVC 2010: Listing 17.1: MSVC 2010
#!bash
push 0
push 128 ; 00000080H
push 4
push 0
push 1
push -1073741824 ; c0000000H
push OFFSET $SG78813
call DWORD PTR __imp__CreateFileA@28
mov DWORD PTR _fh$[ebp], eax
我们再查看 WinNT.h:
Listing 17.2: WinNT.h
#!cpp
#define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#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)
#!bash
.text:7C83D429 test byte ptr [ebp+dwDesiredAccess+3], 40h
.text:7C83D42D mov [ebp+var_8], 1
.text:7C83D434 jz short loc_7C83D417
.text:7C83D436 jmp loc_7C810817
我们来看 TEST 指令,该指令并未检测整个第二个参数,仅检测关键的一个字节(ebp+dwDesiredAccess+3),检测 0x40 标志(这里代表 GENERIC_WRITE 标志)。 Test 对两个参数(目标,源) 执行 AND 逻辑操作,并根据结果设置标志寄存器,结果本身不会保存(CMP 和 SUB 与此类似(6.6.1))。 该代码片段逻辑如下:
#!cpp
if ((dwDesiredAccess&0x40000000) == 0) goto loc_7C83D417
如果 AND 指令没有设置 ZF 位,JZ 将不触发跳转。如果 dwDesiredAccess 不等于 0x40000000,AND 结果将是 0,ZF 位将会被设置,条件跳转将被触发。
我们在 linux GCC 4.4.1 下查看:
#!bash
#include <stdio.h>
#include <fcntl.h>
void main()
{
int handle;
handle=open ("file", O_RDWR | O_CREAT);
};
我们得到: Listing 17.4: GCC 4.4.1
#!bash
public main
main proc near
var_20 = dword ptr -20h
var_1C = dword ptr -1Ch
var_4 = dword ptr -4
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 20h
mov [esp+20h+var_1C], 42h
mov [esp+20h+var_20], offset aFile ; "file"
call _open
mov [esp+20h+var_4], eax
leave
retn
main endp
我们在 libc.so.6 库中查看 open() 函数,看到 syscall: Listing 17.5: open() (libc.so.6)
#!bash
.text:000BE69B mov edx, [esp+4+mode] ; mode
.text:000BE69F mov ecx, [esp+4+flags] ; flags
.text:000BE6A3 mov ebx, [esp+4+filename] ; filename
.text:000BE6A7 mov eax, 5
.text:000BE6AC int 80h ; LINUX - sys_open
因此 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)
#!bash
do_filp_open proc near
...
push ebp
mov ebp, esp
push edi
push esi
push ebx
mov ebx, ecx
add ebx, 1
sub esp, 98h
mov esi, [ebp+arg_4] ; acc_mode (5th arg)
test bl, 3
mov [ebp+var_80], eax ; dfd (1th arg)
mov [ebp+var_7C], edx ; pathname (2th arg)
mov [ebp+var_78], ecx ; open_flag (3th arg)
jnz short loc_C01EF684
mov ebx, ecx ; ebx <- open_flag
GCC 保存 3 个参数的值到堆栈。否则,可能会造成寄存器浪费。 我们来看代码片段: Listing 17.7: do_filp_open() (linux kernel 2.6.31)
#!bash
loc_C01EF6B4: ; CODE XREF: do_filp_open+4F
test bl, 40h ; O_CREAT
jnz loc_C01EF810
mov edi, ebx
shr edi, 11h
xor edi, 1
and edi, 1
test ebx, 10000h
jz short loc_C01EF6D3
or edi, 2
O_CREAT 宏等于 0x40,如果 open_flag 为 0x40,标志位被置 1,接下来的 JNZ 指令将被触发。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论