BetaMao

Windows编程基础

字数统计: 2.7k阅读时长: 10 min
2017/01/28 Share

旧文迁移,新年第一篇,稍微回顾(bu)了下C(翁凯老师的C进阶),以后遇到了再记上来吧~

C复习

地址

&取地址,只能对存在地址的变量取地址

指针

定义时,*代表类型是指针

1
2
int* p, q;
int *x, y;

使用时,*代表取存储的地址处的数据
应用场景:
函数要改变传入值,函数要返回多个结果数据(return返回运算状态)
常见错误:
定义指针类型后未赋值就开始使用->里面存的值未初始化,使用即在错误的地址取值

1
2
3
4
5
//这四种是等价的,只是对于无名参数只能在声明函数时使用
RESULT funA(int a[]);
RESULT funB(int[]);
RESULT funC(int *a);
RESULT funD(int *);

数组名是指针常量,于是每个元素都是常量

1
2
const int a[4] = { 1,2,3,4 };
a[1] = 3;//错误

常量

const的作用范围仅限于直接对象,例如int * const q代表指针q是常量,即里面保存的地址不能变,但是地址上的值却是可变的

枚举类型(不用符号常量化)

1
2
3
4
5
6
7
8
9
10
11
enum name{name1[=num],name2[=num],name3,….};//编号从零开始,可以指定,后续自增
#include<stdio.h>
void f(enum color c) {
printf("%d", c);
}
int main(void) {
enum color{red = 1,yellow,blue};
enum color a = red;
f(a);
return 0;
}

结构体

定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct _a {
int a;
float b;
};
struct _a a;

struct _b {
int a;
float b;
}b;

struct {
int a;
float b;
}c;


赋值:
可单独赋值,也可整体赋值(使用{})
(注意:结构体变量名不是结构体变量的地址,所以才有p->next这种鬼)

typedef

C高阶的标志,所有类型重定义,略略略~

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
//#define BETAMAO 666666
int main(void) {
#ifdef BETAMAO
printf("betamao66666\n");
#else
printf("betamao23333\n");
#endif
return 0;
}

联合

1
#define MAX(x, y)(x>y ? x : y)

(注意最好不要加;不然容易死。。。)

位运算

输出一个数的二进制形式:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

int main(void) {
int number;
scanf("%d", &number);
unsigned long mask = 1u<<31;//正宗的在32位上的1
for (; mask; mask >>= 1) {
printf("%d", number&mask ? 1 : 0);
}
return 0;
}

位段:

1
2
3
4
5
6
7
8
9
struct  weiduan
{
int image : 1;
int flag1 : 3;
int flag2 : 1;
int peding : 28;
}a;
a.image = 0;
a.flag1 = 3;

Windows基础

内核对象

C的结构体吧,存储在系统空间,使用对象句柄(HANDLE)访问,它的所有者是操作系统而非进程,用户只能通过操作系统提供的特定接口访问特定的对象。

对于进程,每个进程都有一个句柄表,句柄相当于索引

索引 指向内核对象内存块的指针 访问掩码 标志
1 0x00000000 (不可用) (不可用)
2 0x05000000 0x???????? 0x00000001

所以多个进程间不能直接使用句柄共享内核对象,默认句柄不会被子进程继承,可以通过创建时设置标志允许继承,当对对象命名后,在有权限的情况下,其他进程就可以通过对象名打开那个对象。

进程

进程是资源拥有的基本单位,上一篇已经记了,虚拟内存空间分布,其中用户空间属于进程独有,一般一个程序加载后布局:

另一部分,进程还有个进程内核对象:
进程控制块是进程实体的一部分,是操作系统中最重要的记录型数据结构。PCB中记录了操作系统所需的,用于描述进程进展情况及控制进程运行所需的全部信息。PCB是进程存在的惟一标志。一般把PCB存放在操作系统专门开辟的PCB区内。
在进程控制块中,主要包括下述4方面的信息:
(1)进程描述信息
进程标识符:每个进程都有惟一的进程标识符,用以识别不同的进程。
用户名或用户标识号:每个进程都隶属于某个用户,有利于资源共享与保护。
家族关系:标识进程之间的家族关系。

(2)处理机状态信息
通用寄存器、指令计数器、程序状态字(PSW)、用户栈指针等

(3)进程调度信息
进程状态:指明进程的当前状态,以作为进程调度和进程对换时的依据。
进程优先级:用于描述进程使用处理机的优先级别的一个整数,优先级别高的进先获得处理机。
进程调度所需的其他信息:如进程已等待CPU的时间总和、进程已执行的时间总和等。
事件:指进程被阻塞的原因。

(4)进程控制信息
程序和数据的地址:指出该进程的程序和数据所在的内存或外存地址,以便再调度到该进程执行时,能从中找到其程序和数据。
进程同步和通信机制:指实现进程同步和进程通信时所必须的机制,如消息队列指针、信号量等。这些数据应全部或部分地存放在PCB中。
实际上进程不会执行任何东西,它只是线程的容器(未使用到多线程时,也是进程的主线程执行整个程序),在C中,我们的程序是以_tmain/_tWinMain函数为入口点,但是实际编译时,在主函数之前还需要做一些初始工作,这部分代码由编译器添加,所以程序入口点地址(称作”入口函数吧”)并不是主函数地址。下面是一个程序的生命周期执行过程:

一旦用户登录一个系统后将拥有一个安全令牌(security token),以后访问受保护资源时都会使用这个令牌,令牌会传给子进程,一个进程拥有令牌后不能改变,若要更高权限的令牌也只能创建新进程并在那时申请更高权限令牌(UAC).

线程

与进程一样,线程也分两部分:在内核空间中的线程内核对象和用户空间中的线程栈
内核对象:
同进程,也记录着线程执行的一些信息,便于线程切换(用户态上下文和内核态的不一样)等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
typedef struct _CONTEXT {
//GetThreadContext()
//SetThreadContext()传入线程句柄与CONTEXT地址设置/获取上下文

DWORD ContextFlags;

//如果ContextFlags指定了CONTEXT_DEBUG_REGISTERS,这部分将被返回/指定,注意CONTEXT_DEBUG_REGISTERS不被包含在CONTEXT_FULL里
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;

//如果ContextFlags指定了CONTEXT_FLOATING_POINT,这部分将被返回/指定
FLOATING_SAVE_AREA FloatSave;

//如果ContextFlags指定了CONTEXT_SEGMENTS,这部分将被返回/指定
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;

//如果ContextFlags指定了CONTEXT_INTEGER,这部分将被返回/指定
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;

//如果ContextFlags指定了CONTEXT_CONTROL,这部分将被返回
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // 指定这两个要格外小心
DWORD EFlags;
DWORD Esp;
DWORD SegSs;

//如果ContextFlags指定了CONTEXT_EXTENDED_REGISTERS,这部分将被返回,这里的格式和上下文由处理器指定
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

typedef struct _CONTEXT {

//GetThreadContext()
//SetThreadContext()传入线程句柄与CONTEXT地址设置/获取上下文
DWORD ContextFlags;

//如果ContextFlags指定了CONTEXT_DEBUG_REGISTERS,这部分将被返回/指定,注意CONTEXT_DEBUG_REGISTERS不被包含在CONTEXT_FULL里
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;

//如果ContextFlags指定了CONTEXT_FLOATING_POINT,这部分将被返回/指定
FLOATING_SAVE_AREA FloatSave;

//如果ContextFlags指定了CONTEXT_SEGMENTS,这部分将被返回/指定
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;

//如果ContextFlags指定了CONTEXT_INTEGER,这部分将被返回/指定
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;

//如果ContextFlags指定了CONTEXT_CONTROL,这部分将被返回
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // 指定这两个要格外小心
DWORD EFlags;
DWORD Esp;
DWORD SegSs;

//如果ContextFlags指定了CONTEXT_EXTENDED_REGISTERS,这部分将被返回,这里的格式和上下文由处理器指定
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

线程栈:

创建线程后,会在用户空间申请一块区域存放线程局部数据(传入参数,局部变量都在这个栈中)
因为线程共享同一进程空间,所以线程是可以访问所属进程中所有的句柄,内存,其他线程的数据的
只有窗口和挂扣对象属于线程,其他用户对象都属于进程,所以若不显式关闭,线程结束时只有前两类句柄会被自动关闭(使用计数-1)

虚拟内存

虚拟内存

使用VirtualAlloc()使用MEM_REVERSE参数可以预定一块地址空间,此时它在虚拟内存里被预定但是并未被映射到物理存储,所以还是不能使用的,可以使用MEM_COMMIT参数调拨物理存储器空间,这样就能使用这块地址了。

内存映射文件

内存映射文件的物理存储器来自磁盘上已有的文件。例如运行exe文件,加载dll文件等,都是直接将文件映射到内存(这与CreateFile不一样,它只是打开文件,可以通过文件句柄,偏移指针访问文件内容,而内存映射是可以完全映射内容的),当同一程序被同时多次执行,它只会被映射一次,其他进程只需复制它的映射视图即可,不过默认情况下他们的全局变量和静态变量不会被共享,而且当一个进程修改了其他部分(如hook API修改了代码)会发生写时复制。

对这种神奇的东西细节留到堆溢出部分来记录,现在先记下大概的使用。它是一块预定的地址空间区域,物理存储器始终是页交换文件,进程初始化时会被分配一个默认堆,可使用GetProcessHeap()获取句柄,用户可以自己创建HeapCreate()堆取,然后从定义的堆取里面分配HeapAlloc()内存

DLL

隐式调用

在编写程序时,只包含dll的头文件,其中头文件会声明导出数据结构,符号,函数和变量,它需要用到*.lib,生成的可执行文件IAT中会有各种需要导入的dll数据,在程序运行前dll就会被加载到内存中。

显示调用

在编写程序时,包含头文件并使用LoadLibrary()显式加载dll,加载的dll不会出现在IAT中,只有程序运行到函数出才会动态加载dll并在使用完以后可以通过FreeLibrary卸载,在进程/线程对此dll进行装载/卸载时可以执行指定的初始化/退出代码(具体笔记在消息勾取处)

CATALOG
  1. 1. C复习
    1. 1.1. 地址
    2. 1.2. 指针
    3. 1.3. 常量
    4. 1.4. 枚举类型(不用符号常量化)
    5. 1.5. 结构体
    6. 1.6. typedef
    7. 1.7. 联合
    8. 1.8.
    9. 1.9. 位运算
  2. 2. Windows基础
    1. 2.1. 内核对象
    2. 2.2. 进程
    3. 2.3. 线程
    4. 2.4. 虚拟内存
      1. 2.4.1. 虚拟内存
      2. 2.4.2. 内存映射文件
      3. 2.4.3.
      4. 2.4.4. DLL
        1. 2.4.4.1. 隐式调用
        2. 2.4.4.2. 显示调用