第二章--Win32程序运行原理 (部分概念及代码讲解)

学习《Windows程序设计》记录

概念贴士:

1.  每个进程都有赋予它自己的私有地址空间。当进程内的线程运行时,该线程仅仅能够访问属于它的进程的内存,而属于其他进程的内存被屏蔽了起来,不能被该线程访问。

   PS:进程A在其地址空间的0x12345678地址处能够有一个数据结构,而进程B能够在其地址空间的0x12345678处存储一个完全不同的数据。彼此不能访问。

2.  在大多数系统中,Windows将地址空间的一半(4GB的前一半,0x00000000-0x7FFFFFFF)留给进程作为私有存储,自己使用另一半(4GB的后一半,0x80000000-0xFFFFFFFFF)来存储操作系统内部使用的数据。

3.  各进程的地址空间被分成了用户空间和系统空间两部分。用户空间部分是进程私有地址空间,一个进程不能一任何方式读、写其他进程此部分空间中的数据。系统空间部分放置操作系统的代码,包括内核代码、设备驱动代码、设备I/O缓冲区等。系统空间部分在所有的进程中是共享的。在部分系统中,这些数据结构是被保护的。试图访问时,会遇到访问异常。

4.  处理器定义了多个特权级别。如80386处理器共定义了4种(0-3)特权级别,或者称为环。其中0级是最高级(特权级),3级是最低级(用户级)。

5.  为了阻止应用应用程序访问或者修改关键的系统数据(即2GB系统空间内的数据),Windows使用了两种访问模式:内核模式和用户模式,它们分别使用了处理器中的0和3这两个特权级别。用户程序的代码在用户模式下运行,系统程序(如系统服务程序和硬件驱动)的代码在内核模式下运行。

6.  内核对象是系统提供的用户模式下代码与内核模式下代码进行交互的基本接口。使用内核交互对象是应用程序和系统内核进行交互的重要方式之一。

7.  引入内核对象后,系统可以较为方便的完成以下4个任务:

    1)为系统资源提供可识别的名字;

    2)在进程之间共享资源和数据;

    3)保护资源不会被未经许可的代码访问;

    4)跟踪对象的引用情况,这使得系统知道什么时候一个对象不再被使用了,以便释放它占用的空间。

8.  内核对象的数据结构仅能够从内核模式访问,所以直接在内存中定位这些数据结构对应用程序来说是不可能的。应用程序必须使用API函数来访问内核对象。

9.  调用函数创建内核对象时,函数会返回标识此内核对象的句柄。句柄是进程相关的,仅对创建该内核对象的进程有效。当然,多个进程共享一个内核对象也是可能的,调用DuplicateHandle函数复制一个进程句柄传给其他进程即可。

10.  内核对象中最简单最常用的属性---使用计数。使用计数属性指明进程对特定内核对象的应用次数,当系统发现引用次数为0时,它会自动关闭资源。(创建时初始化为1,每次打开这个内核对象时,计数加1,关闭则减1。当减到0时,说明进程对这个内核对象的所有引用都已经关闭了,系统应该释放此内核对象资源了。)

11.  进程(Process)是一个正在运行的程序,它拥有自己的虚拟地址空间,拥有自己的代码、数据和其他系统资源,如进程创建的文件、管道、同步对象等。一个进程也包含一个或者多个运行在此进程内的线程(Thread)。

12.  程序与进程在表面很相似。但是,程序是一连串静态的指令,而进程是一个容器,它包含了一系列运行在这个程序实例上下问中的线程使用的资源。

13.  线程是进程内执行代码的独立实体。操作系统创建了进程后,会创建一个线程执行进程中的代码。通常称这个线程为主线程。主线程在运行过程中可能会创建其他线程(称为辅助线程)。

14.  Win进程两个组成部分:

    1)进程内核对象。操作系统使用此内核对象来管理该进程。这个内核对象也是操作系统存放进程统计信息的地方。

    2)私有的虚拟地址空间。此地址空间包含了所有可执行的或者是DLL模块的代码和数据,它也是程序动态申请内存的地方,比如说线程堆栈和进程堆。

15.  应用程序必须有一个入口函数,它在程序开始运行的时候被调用。如果创建的是控制台应用程序,此入口函数就是main。

    PS:int main(int argc,char *argv[]);

16.  事实上,操作系统并不是真的调用main函数,而是去调用C/C++运行期启动函数,此函数会初始化C/C++运行期库。因此,在程序中可以调用malloc和free之类的函数。

17.  在控制台程序中,C/C++运行期启动函数会调用程序入口函数main。如果没有,将会返回“unresolved external symbol”错误。

18.  Win32程序的启动过程就是进程的创建过程,操作系统通过调用CreateProcess函数(代码解释中将会有对该函数的具体解释)来创建新的进程的。当一个线程调用CreateProcess函数的时候,系统会创建一个进程内核对象,其使用计数被初始化为1.此进程内核对象不是进程本身,仅仅是一个系统用来管理这个进程的小的数据结构。系统然后会为新的进程创建一个虚拟地址空间,加载应用程序运行时所需要的代码和数据。系统接着会为新进程创建一个主线程,这个主线程通过执行C/C++运行期启动代码开始运行,C/C++运行期启动代码又会调用main函数。如果系统能够成功创建新的进程和进程的主线程,CreateProcess函数会返回TRUE,否则会返回FALSE。

19.  一般将创建进程称为父进程,被创建的进程称为子进程。系统在创建新的进程时会为新进程指定一个STARTUPINFO类型的变量,这个结构包含了父进程传递给子进程的一些显示信息。(对图形界面应用程序来说,这些信息会影响新的进程中主线程的主窗口的显示等。)

代码解释:

1.CreateProcess(create process)

  PS:一个完整的创建进程的程序,打开了Windows自带的命令行程序cmd.exe。(不过我一般是Win+R,直接cmd进入。)

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <stdio.h>
 4
 5 int main(int argc, char* argv[])
 6 {
 7     char szCommandLine[] = "cmd";
 8     STARTUPINFO si = { sizeof(si) };
 9     PROCESS_INFORMATION pi;
10
11     si.dwFlags = STARTF_USESHOWWINDOW;    // 指定wShowWindow成员有效
12     si.wShowWindow = TRUE;            // 此成员设为TRUE的话则显示新建进程的主窗口,
13                         // 为FALSE的话则不显示
14     BOOL bRet = ::CreateProcess (
15         NULL,            // 不在此指定可执行文件的文件名
16         szCommandLine,        // 命令行参数
17         NULL,            // 默认进程安全性
18         NULL,            // 默认线程安全性
19         FALSE,            // 指定当前进程内的句柄不可以被子进程继承
20         CREATE_NEW_CONSOLE,    // 为新进程创建一个新的控制台窗口
21         NULL,            // 使用本进程的环境变量
22         NULL,            // 使用本进程的驱动器和目录
23         &si,
24         &pi);
25
26     if(bRet)
27     {
28         // 既然我们不使用两个句柄,最好是立刻将它们关闭
29         ::CloseHandle (pi.hThread);
30         ::CloseHandle (pi.hProcess);
31
32         printf(" 新进程的进程ID号:%d \n", pi.dwProcessId);
33         printf(" 新进程的主线程ID号:%d \n", pi.dwThreadId);
34     }
35     return 0;
36 }

2.ProcessList(process list)

  PS:使用ToolHelp函数中的CreateToolhelp32Snapshop函数给当前系统内执行的进程拍快照(Snapshot),获取进程列表。然后利用Process32First函数和Process32Next函数遍历快照中的记录的列表。

 1 #include "stdafx.h"
 2 #include <windows.h>
 3 #include <tlhelp32.h> // 声明快照函数的头文件
 4
 5 int main(int argc, char* argv[])
 6 {
 7     PROCESSENTRY32 pe32;
 8     // 在使用这个结构之前,先设置它的大小
 9     pe32.dwSize = sizeof(pe32);
10
11     // 给系统内的所有进程拍一个快照
12     HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
13     if(hProcessSnap == INVALID_HANDLE_VALUE)
14     {
15         printf(" CreateToolhelp32Snapshot调用失败! \n");
16         return -1;
17     }
18
19     // 遍历进程快照,轮流显示每个进程的信息
20     BOOL bMore = ::Process32First(hProcessSnap, &pe32);
21     while(bMore)
22     {
23         printf(" 进程名称:%s \n", pe32.szExeFile);
24         printf(" 进程ID号:%u \n\n", pe32.th32ProcessID);
25
26         bMore = ::Process32Next(hProcessSnap, &pe32);
27     }
28
29     // 不要忘记清除掉snapshot对象
30     ::CloseHandle(hProcessSnap);
31     return 0;
32 }

3.TerminateProcess(terminate process)

  PS:该程序通过输入进程独有的ID号,来结束该进程,兵返回操作结果。(你们可以从任务管理器找一个进程ID试试,建议拿QQ什么的试。千万别找Windows什么的)

 1 #include "stdafx.h"
 2 #include <windows.h>
 3
 4 BOOL TerminateProcessFromId(DWORD dwId)
 5 {
 6     BOOL bRet = FALSE;
 7     // 打开目标进程,取得进程句柄
 8     HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
 9     if(hProcess != NULL)
10     {
11         // 终止进程
12         bRet = ::TerminateProcess(hProcess, 0);
13     }
14     CloseHandle(hProcess);
15     return bRet;
16 }
17
18 int main(int argc, char* argv[])
19 {
20     DWORD dwId;
21     printf(" 请输入您要终止的进程的ID号: \n");
22     scanf("%u", &dwId);
23     if(TerminateProcessFromId(dwId))
24     {
25         printf(" 终止进程成功! \n");
26     }
27     else
28     {
29         printf(" 终止进程失败! \n");
30     }
31
32     return 0;
33 }

4.Testor

  PS:这个是为了这章最后一个程序--内存修改器服务的一个测试程序。后面会通过内存修改器修改g_nNum和i的值。

 1 #include "stdafx.h"
 2 #include <stdio.h>
 3 // 全局变量测试
 4 int g_nNum;
 5 int main(int argc, char* argv[])
 6 {
 7     int i = 198;    // 局部变量测试
 8     g_nNum = 1003;
 9
10     while(1)
11     {
12         // 输出个变量的值和地址
13         printf(" i = %d, addr = %08lX;   g_nNum = %d, addr = %08lX \n",
14                                 ++i, &i, --g_nNum, &g_nNum);
15         getchar();
16     }
17
18     return 0;
19 }

5.MemRepair(memory repair)

  PS:这章最后一个程序--内存修改器。具体说明程序备注有写。(大家可以试试,游戏挂了不负责。另外,在第七章后将会有一个GUI版本的。)

  1 #include "stdafx.h"
  2 #include "windows.h"
  3 #include "stdio.h"
  4 #include <iostream.h>
  5
  6
  7 BOOL FindFirst(DWORD dwValue);    // 在目标进程空间进行第一次查找
  8 BOOL FindNext(DWORD dwValue);    // 在目标进程地址空间进行第2、3、4……次查找
  9
 10 DWORD g_arList[1024];        // 地址列表
 11 int g_nListCnt;            // 有效地址的个数
 12 HANDLE g_hProcess;        // 目标进程句柄
 13
 14
 15 //////////////////////
 16
 17 BOOL WriteMemory(DWORD dwAddr, DWORD dwValue);
 18 void ShowList();
 19
 20
 21 int main(int argc, char* argv[])
 22 {
 23     // 启动02testor进程
 24     char szFileName[] = "..\\02testor\\debug\\02testor.exe";
 25     STARTUPINFO si = { sizeof(si) };
 26     PROCESS_INFORMATION pi;
 27     ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE,
 28                             CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
 29     // 关闭线程句柄,既然我们不使用它
 30     ::CloseHandle(pi.hThread);
 31     g_hProcess = pi.hProcess;
 32
 33     // 输入要修改的值
 34     int    iVal;
 35     printf(" Input val = ");
 36     scanf("%d", &iVal);
 37
 38     // 进行第一次查找
 39     FindFirst(iVal);
 40
 41     // 打印出搜索的结果
 42     ShowList();
 43
 44
 45     while(g_nListCnt > 1)
 46     {
 47         printf(" Input val = ");
 48         scanf("%d", &iVal);
 49
 50         // 进行下次搜索
 51         FindNext(iVal);
 52
 53         // 显示搜索结果
 54         ShowList();
 55     }
 56
 57
 58     // 取得新值
 59     printf(" New value = ");
 60     scanf("%d", &iVal);
 61
 62     // 写入新值
 63     if(WriteMemory(g_arList[0], iVal))
 64         printf(" Write data success \n");
 65
 66
 67     ::CloseHandle(g_hProcess);
 68     return 0;
 69 }
 70
 71 BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
 72 {
 73     // 读取1页内存
 74     BYTE arBytes[4096];
 75     if(!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
 76         return FALSE;    // 此页不可读
 77
 78     // 在这1页内存中查找
 79     DWORD* pdw;
 80     for(int i=0; i<(int)4*1024-3; i++)
 81     {
 82         pdw = (DWORD*)&arBytes[i];
 83         if(pdw[0] == dwValue)    // 等于要查找的值?
 84         {
 85             if(g_nListCnt >= 1024)
 86                 return FALSE;
 87             // 添加到全局变量中
 88             g_arList[g_nListCnt++] = dwBaseAddr + i;
 89         }
 90     }
 91
 92     return TRUE;
 93 }
 94
 95 BOOL FindFirst(DWORD dwValue)
 96 {
 97     const DWORD dwOneGB = 1024*1024*1024;    // 1GB
 98     const DWORD dwOnePage = 4*1024;        // 4KB
 99
100     if(g_hProcess == NULL)
101         return FALSE;
102
103     // 查看操作系统类型,以决定开始地址
104     DWORD dwBase;
105     OSVERSIONINFO vi = { sizeof(vi) };
106     ::GetVersionEx(&vi);
107     if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
108         dwBase = 4*1024*1024;        // Windows 98系列,4MB
109     else
110         dwBase = 640*1024;        // Windows NT系列,64KB
111
112     // 在开始地址到2GB的地址空间进行查找
113     for(; dwBase < 2*dwOneGB; dwBase += dwOnePage)
114     {
115         // 比较1页大小的内存
116         CompareAPage(dwBase, dwValue);
117     }
118
119     return TRUE;
120 }
121
122 BOOL FindNext(DWORD dwValue)
123 {
124     // 保存m_arList数组中有效地址的个数,初始化新的m_nListCnt值
125     int nOrgCnt = g_nListCnt;
126     g_nListCnt = 0;
127
128     // 在m_arList数组记录的地址处查找
129     BOOL bRet = FALSE;    // 假设失败
130     DWORD dwReadValue;
131     for(int i=0; i<nOrgCnt; i++)
132     {
133         if(::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL))
134         {
135             if(dwReadValue == dwValue)
136             {
137                 g_arList[g_nListCnt++] = g_arList[i];
138                 bRet = TRUE;
139             }
140         }
141     }
142
143     return bRet;
144 }
145
146 // 打印出搜索到的地址
147 void ShowList()
148 {
149     for(int i=0; i< g_nListCnt; i++)
150     {
151         printf("%08lX \n", g_arList[i]);
152     }
153 }
154
155 BOOL WriteMemory(DWORD dwAddr, DWORD dwValue)
156 {
157     return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
158 }

总结:重要的概念、知识点、代码讲解都被标识出来了。通过这章可以对Win32程序运行等原理有着清晰的了解。真正地懂得了程序运行的完整过程、了解内存的实际情况。改变了以前对Win32程序认识的不清晰。

时间: 2024-10-14 01:32:23

第二章--Win32程序运行原理 (部分概念及代码讲解)的相关文章

Windows程序设计笔记1:第2章:win32程序运行原理

第2章:win32程序运行原理 内核对象:对象句柄,标示符,进程相关的,只能被1个进程里面的其他线程访问,不透明的,封装过的. 创建进程:返回STARTUPINFO类型的变量对象,包含了父进程传递给子进程的显示信息,   STARTUPINFO是一种类类型,和内含的类型是一样的, STARTUPINFO si={sizeof(&si)}; //初始化startupinfo的大小 ::GetStartupinfo(&si); //调用startupinfo对象 创建进程:CreateProc

孙鑫MFC学习笔记1:Win32程序运行原理

1.MSG结构 hwnd:窗口句柄 message:消息类型 wParam & lParam:消息的附加信息(比如键值) time:消息被投递的时间 tip:typedef的作用是从变量类型区分变量用途 2.WinMain 3.程序开发步骤 4.设计窗口类 5.创建窗口.显示窗口.更新窗口 6.回调函数 tip:BeginPaint.EndPaint只能在响应WM_PAINT消息中使用,而GetDC不能在这里使用 tip:if语句中如果是常量与变量比较是否相等,应该把常量写在左边(可以避免把==

Windows程序运行原理

Windows程序运行原理 1.应用程序,操作系统,硬件之间的关系 这里涉及到消息及消息队列, 操作系统是通过消息机制(Message)来将感知到的事件传递给应用程序的. 操作系统将每个事件都包装成一个称为消息的结构体MSG来传递给应用程序. 操作系统对事件做出反应的过程就叫做消息响应 typedef struct tagMSG { // msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt

.NET 程序运行原理

“Overview of the Common Language Infrastructure”,作者Jarkko Piiroinen - 自己的作品.采用Public domain授权,来自维基共享资源. 右图即为运行图. 最上层即为各种语言,然后经过对应的编译器编译成程序集,也就是 CIL 通用中间语言 最后,再由 CLR 公共语言运行时 编译成机器码 CLI 一个规范 .NET 程序运行原理

java程序运行原理

一.JRE.JDK.JVM 要了解java程序运行原理,首先需要了解知道jre.jdk.jvm这三者是什么,他们之间又有什么联系. JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台.所有的Java 程序都要在JRE下才能运行. JDK(Java Development Kit,java开发工具包)是程序开发者用来来编译.调试java程序用的开发工具包.JDK的工具也是Java程序,也需要JRE才能运行.为了保持JDK的独立性和完整性,在JDK的安装过程

QF——iOS程序运行原理

iOS程序的运行原理: 1> main.m 主函数是所有程序的入口函数. 2> 在main函数里是UIApplicationMain函数,开启了一个无限循环,以监听该应用. 该UIApplicationMain函数有4个参数,前两个分别是main函数的参数,第3个参数UIApplication的类名,第4个是应用的代理类名. 2.1> 创建一个UIApplication实例,单例的,一个应用对应一个该对象,代表整个应用程序. 2.2> 再创建一个UIApplication的dele

逆向第三课(深入.NET程序运行原理)

注:本文适用读者范围,对Windows下的PE文件有一定认识的朋友 一. 名词解释 a)        CLR: 公共语言运行时(Common LanguageRuntime),CLR时.NET框架的核心内容之一,可以把它看为一套标准资源,可以被任何.NET程序使用.它包括:面向对象的编程模型.安全模型.类型系统(CTS).所有.NET基类.程序执行以及代码管理等. b)        JIT: 即时编译(Just In-Time compile),这是.NET运行可执行程序的基本方式,也就是在

第一节 环境的配置,网站运行原理,模式,代码风格

一.学习php之前所需要做哪些准备呢? 安装phpstudy 测试配置环境是否安装成功 个人电脑安装phpstudy时将站点默认为www目录,在根目录下面新建一个(ceshi.php)页面用来测试环境是否配置成功. 访问站点文件,切莫直接点击打开页面,这样不属于服务器站点访问. 在浏览器地址栏中输入localhost/ceshi.php即可访问.(由于站点默认为www,所以只需要输入localhost/ceshi.php) 二.网站的运行原理 访客-----请求----DNS服务器---指向服务

第三章--Win32程序的执行单元(部分概念及代码讲解)(中-线程同步

学习<Windows程序设计>记录 概念贴士: 1. 同步可以保证在一个时间内只有一个线程对其共享资源有控制权.PS:共享资源包括全局变量.公共数据成员或者句柄等. 2. 临界区内核对象和时间内核对象可以很好地用于多线程同步和它们之间的通信. 3. 线程同步必要性:当多个线程在同一个进程中执行时,可能有不止一个线程同时执行同一段代码,访问同一段内存中的数据.多个线程同时读取共享数据没有问题,但是如果同时读写,情况就不同,也许会产生极大的错误.(如:程序CountErr).解决同步问题,就是保证