这是一个非常基础且重要的问题,现从基本概念到深层机制全面解析这三者之间的关系。
-
可执行程序:存储在磁盘上的一个文件,例如一个.exe文件。它包含程序代码、数据以及操作系统如何运行它的指令。
-
可执行程序实例:当可执行程序被加载到内存中并运行时,就创建了一个实例。在Windows中,每个实例由一个实例句柄(HINSTANCE)标识。
-
进程:操作系统为运行的实例创建的一个执行环境。它包括地址空间、执行线程、系统资源等。一个进程可以看作是一个正在运行的程序实例。
三者之间的关系是:
-
可执行程序是静态的,存储在磁盘上。
-
当你运行一个可执行程序时,操作系统会创建一个进程,并将可执行程序加载到内存中,这就成为了一个实例。
-
一个可执行程序可以同时有多个实例(即多个进程),每个实例独立运行。
工作机制和原理:
-
可执行程序格式:
Windows使用PE(Portable Executable)格式。PE文件包含代码、数据、资源以及导入导出表等。 -
进程创建:
1). 当用户或系统启动一个可执行程序时,操作系统会创建一个新的进程。 2). 进程创建包括:分配进程控制块(PCB)、创建虚拟地址空间、加载可执行程序到内存、创建主线程等。
-
实例初始化:
1). 每个实例都会有一个实例句柄(HINSTANCE),它实际上是可执行程序加载到内存中的基地址。 2). 对于同一个可执行程序,每次运行都会创建一个新的进程,每个进程都有自己的实例句柄。但是,如果多个实例使用同一个可执行程序,它们可能在不同的基地址上加载(由于ASLR),所以实例句柄可能不同。
-
多个实例:
1). 在Windows中,同一个可执行程序可以运行多个实例,每个实例在独立的进程中。 2). 每个实例有自己独立的地址空间,因此一个实例的崩溃不会直接影响其他实例。
-
实例句柄(HINSTANCE)的使用:
1). Windows程序中,实例句柄用于标识程序实例,特别是在访问资源(如图标、菜单、字符串等)时,需要指定实例句柄。 2). 在WinMain函数中,第一个参数就是该实例的句柄。
-
进程与实例的关系:
1). 一个进程对应一个实例。但是,一个进程可以包含多个模块(例如多个DLL),每个模块也有自己的实例句柄(HMODULE,实际上与HINSTANCE是同一个东西)。
-
如何区分多个实例:
1). 每个进程有唯一的进程ID(PID),而实例句柄是模块的基地址。同一个可执行程序的多个实例,它们的实例句柄可能相同(如果加载到相同的基地址)也可能不同(如果加载到不同的基地址)。
-
实例句柄的获取:
1). 在WinMain中通过参数获取。 2). 也可以通过GetModuleHandle(NULL)来获取当前进程的主模块的实例句柄。
-
单实例应用程序:
1). 有些应用程序只允许运行一个实例,这通常通过互斥体、窗口查找等机制实现。
前面对进程,可执行程序,可执行程序实例之间的关系,工作机制和原理作了简单的描述,现在做一下系统的概述如下。
第一部分:基本概念与关系
1.1 核心定义
- **可执行程序**:存储在磁盘上的**静态文件**(如 `.exe`, `.com`)
- **可执行程序实例**:程序被加载到内存中的**具体表现**
- **进程**:操作系统为运行中的程序实例分配的**执行环境**
1.2 关系类比
| 概念 | 类比 | 说明 |
|----------------------------|----------------------------------|---------------------------|
| **可执行程序** | 菜谱 | 静态的指令集合 |
| **可执行程序实例** | 按照菜谱准备的一桌菜 | 内存中的具体表现 |
| **进程** | 厨房+厨师+食材 | 完整的执行环境 |
1.3 核心关系总结
可执行程序 (磁盘文件)
│
▼ 加载
可执行程序实例 (内存中的映像)
│
▼ 包装
进程 (执行环境 + 资源)
**关键点**:一个可执行程序可以对应多个实例,每个实例运行在独立的进程中。
第二部分:详细工作机制与原理
2.1 可执行程序的结构
2.1.1 Windows PE 文件格式
PE 文件结构
┌─────────────────┐
│ DOS头 │ ← "MZ" 签名
├─────────────────┤
│ DOS存根程序 │
├─────────────────┤
│ PE文件头 │ ← "PE" 签名, 机器类型
├─────────────────┤
│ 可选头 │ ← 入口点, 映像基址, 子系统
├─────────────────┤
│ 节区表 │ ← 描述各个节区的属性
├─────────────────┤
│ .text节 │ ← 代码段
├─────────────────┤
│ .data节 │ ← 已初始化数据
├─────────────────┤
│ .rdata节 │ ← 只读数据
├─────────────────┤
│ .rsrc节 │ ← 资源
├─────────────────┤
│ .reloc节 │ ← 重定位信息
└─────────────────┘
2.1.2 可执行程序的关键属性
// PE 头中的重要字段
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // "PE\0\0"
IMAGE_FILE_HEADER FileHeader; // 机器类型、节区数等
IMAGE_OPTIONAL_HEADER OptionalHeader; // 入口点、基址等
} IMAGE_NT_HEADERS;
// 可选头中的关键信息
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 魔数
DWORD AddressOfEntryPoint; // 入口点RVA
DWORD ImageBase; // 首选加载基址
DWORD SectionAlignment; // 内存中对齐
DWORD FileAlignment; // 文件中对齐
DWORD SizeOfImage; // 内存中总大小
// ... 其他字段
} IMAGE_OPTIONAL_HEADER;
2.2 从可执行程序到实例的加载过程
2.2.1 加载器的工作流程
// 简化的可执行程序加载过程
PROCESS* LoadExecutable(const char* exe_path) {
// 1. 创建空的进程结构
PROCESS* process = CreateEmptyProcess();
// 2. 打开并验证可执行文件
FILE_OBJECT* file_obj = OpenExecutableFile(exe_path);
if (!VerifyPEFormat(file_obj)) {
return NULL;
}
// 3. 解析PE头信息
PE_HEADERS headers = ParsePEHeaders(file_obj);
// 4. 创建进程地址空间
CreateAddressSpace(process, headers.SizeOfImage);
// 5. 映射各个节区到内存
for (int i = 0; i < headers.NumberOfSections; i++) {
SECTION_HEADER sect = headers.SectionHeaders[i];
void* virtual_address = headers.ImageBase + sect.VirtualAddress;
// 设置内存保护属性
DWORD protection = GetMemoryProtection(sect.Characteristics);
// 映射节区到内存
MapSectionToMemory(process, file_obj, sect, virtual_address, protection);
}
// 6. 处理重定位(如果实际加载地址与首选基址不同)
if (headers.ActualBase != headers.ImageBase) {
ApplyRelocations(process, headers);
}
// 7. 解析导入表,加载依赖的DLL
ResolveImports(process, headers);
// 8. 初始化进程环境块(PEB)
InitializePEB(process, headers);
// 9. 创建主线程
CreateMainThread(process, headers.AddressOfEntryPoint);
return process;
}
2.3 内存映射的详细过程
void MapSectionToMemory(PROCESS* process, FILE_OBJECT* file,
SECTION_HEADER section, void* address, DWORD protection) {
// 计算在文件中的偏移和大小
DWORD file_offset = section.PointerToRawData;
DWORD file_size = section.SizeOfRawData;
DWORD memory_size = section.VirtualSize;
// 在进程地址空间中分配内存
void* allocated_address = VirtualAllocEx(process->Handle, address,
memory_size,
MEM_COMMIT | MEM_RESERVE,
protection);
// 读取节区数据从文件到内存
SetFilePointer(file->Handle, file_offset, NULL, FILE_BEGIN);
DWORD bytes_read;
ReadFile(file->Handle, allocated_address, file_size, &bytes_read, NULL);
// 如果内存大小大于文件大小,清零剩余部分
if (memory_size > file_size) {
ZeroMemory((BYTE*)allocated_address + file_size, memory_size - file_size);
}
// 记录节区信息到进程结构
AddSectionToProcess(process, section.Name, allocated_address,
memory_size, protection);
}
2.4 进程环境的创建
2.4.1 进程控制块(PCB)结构
// 简化的进程控制块
typedef struct _PROCESS_CONTROL_BLOCK {
DWORD ProcessId; // 进程ID
DWORD ParentProcessId; // 父进程ID
HANDLE ProcessHandle; // 进程句柄
// 内存管理
void* ImageBase; // 映像基址
void* EntryPoint; // 入口点
MEMORY_REGION* MemoryRegions; // 内存区域链表
// 线程管理
THREAD* MainThread; // 主线程
LIST_ENTRY ThreadList; // 线程列表
// 资源管理
HANDLE_TABLE* HandleTable; // 句柄表
PEB* ProcessEnvironmentBlock; // 进程环境块
// 调度信息
PROCESS_PRIORITY Priority; // 优先级
PROCESS_STATE State; // 状态
KERNEL_STATE* KernelState; // 内核状态
} PROCESS_CONTROL_BLOCK;
2.4.2 进程环境块(PEB)初始化
PEB* InitializeProcessEnvironmentBlock(PROCESS* process, PE_HEADERS headers) {
PEB* peb = AllocateMemory(sizeof(PEB));
peb->ImageBaseAddress = headers.ActualBase;
peb->Ldr = InitializeLoaderData(process, headers); // 加载器数据
peb->ProcessParameters = InitializeProcessParameters(process);
peb->AtlThunkSListPtr = NULL;
peb->TlsBitmap = NULL;
peb->TlsBitmapBits[0] = 0;
peb->TlsBitmapBits[1] = 0;
peb->NumberOfProcessors = GetSystemNumberOfProcessors();
peb->NtGlobalFlag = GetGlobalFlags();
// 设置进程的PEB指针
process->Peb = peb;
return peb;
}
第三部分:具体示例与代码分析
3.1 示例:创建多个程序实例
#include <windows.h>
#include <stdio.h>
void create_multiple_instances() {
const char* program_path = "notepad.exe";
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi[3];
printf("创建多个程序实例:\n");
for (int i = 0; i < 3; i++) {
// 每个CreateProcess调用创建一个新的实例和进程
if (CreateProcess(
program_path, // 可执行程序路径
NULL, // 命令行参数
NULL, NULL, // 进程/线程安全属性
FALSE, // 句柄继承
0, NULL, NULL, // 创建标志、环境、当前目录
&si, &pi[i] // 输出进程和线程信息
)) {
printf("实例 %d:\n", i + 1);
printf(" 进程ID: %d\n", pi[i].dwProcessId);
printf(" 进程句柄: 0x%p\n", pi[i].hProcess);
printf(" 线程ID: %d\n", pi[i].dwThreadId);
printf(" 线程句柄: 0x%p\n", pi[i].hThread);
} else {
printf("创建实例 %d 失败,错误: %d\n", i + 1, GetLastError());
}
}
// 等待一段时间让用户看到效果
Sleep(5000);
// 清理资源
for (int i = 0; i < 3; i++) {
if (pi[i].hProcess) {
TerminateProcess(pi[i].hProcess, 0);
CloseHandle(pi[i].hProcess);
CloseHandle(pi[i].hThread);
}
}
}
3.2 示例:分析程序实例信息
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
void analyze_process_instances() {
printf("=== 系统中所有notepad.exe实例 ===\n");
// 创建进程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
// 遍历所有进程
if (Process32First(hSnapshot, &pe32)) {
do {
// 查找notepad.exe进程
if (_stricmp(pe32.szExeFile, "notepad.exe") == 0) {
printf("进程ID: %d\n", pe32.th32ProcessID);
printf("父进程ID: %d\n", pe32.th32ParentProcessID);
printf("线程数: %d\n", pe32.cntThreads);
printf("优先级: %d\n", pe32.pcPriClassBase);
// 打开进程获取更多信息
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ, FALSE, pe32.th32ProcessID);
if (hProcess) {
// 获取可执行文件路径
char module_path[MAX_PATH];
if (GetModuleFileNameEx(hProcess, NULL, module_path, MAX_PATH)) {
printf("可执行文件: %s\n", module_path);
}
// 获取加载基址
HMODULE hModules[1024];
DWORD cbNeeded;
if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) {
printf("加载基址: 0x%p\n", hModules[0]);
}
CloseHandle(hProcess);
}
printf("---\n");
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
3.3 示例:实例数据隔离演示
#include <windows.h>
#include <stdio.h>
// 全局变量 - 每个实例有独立的副本
int g_InstanceData = 0;
char g_InstanceName[64] = "Default";
// 共享数据段(所有实例共享)
#pragma data_seg(".shared")
int g_SharedData = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")
DWORD WINAPI InstanceWorker(LPVOID lpParam) {
int instance_id = (int)lpParam;
// 修改实例私有数据
g_InstanceData = instance_id * 100;
sprintf(g_InstanceName, "Instance-%d", instance_id);
// 修改共享数据(需要同步)
InterlockedIncrement(&g_SharedData);
for (int i = 0; i < 5; i++) {
printf("[进程%d] 实例数据: %d, 实例名: %s, 共享数据: %d\n",
GetCurrentProcessId(), g_InstanceData, g_InstanceName, g_SharedData);
// 修改数据
g_InstanceData++;
InterlockedIncrement(&g_SharedData);
Sleep(1000);
}
return 0;
}
void demonstrate_instance_isolation() {
printf("演示实例数据隔离:\n");
printf("当前进程ID: %d\n", GetCurrentProcessId());
// 创建多个线程模拟多个实例的行为
HANDLE threads[3];
for (int i = 0; i < 3; i++) {
threads[i] = CreateThread(NULL, 0, InstanceWorker, (LPVOID)(i + 1), 0, NULL);
}
// 等待所有线程完成
WaitForMultipleObjects(3, threads, TRUE, INFINITE);
for (int i = 0; i < 3; i++) {
CloseHandle(threads[i]);
}
printf("最终共享数据值: %d\n", g_SharedData);
}
第四部分:高级机制与原理
4.1 写时复制机制
多个实例共享相同的代码段,但数据段使用写时复制:
// 写时复制的工作原理
void HandleCopyOnWrite(PROCESS* process, void* address, DWORD size) {
// 检查页面保护
MEMORY_BASIC_INFORMATION mbi;
VirtualQueryEx(process->Handle, address, &mbi, sizeof(mbi));
if (mbi.Protect & PAGE_WRITECOPY) {
// 页面处于写时复制状态
if (IsPageSharedWithOtherProcess(process, address)) {
// 复制页面到私有副本
void* private_copy = VirtualAllocEx(process->Handle, NULL, size,
MEM_COMMIT, PAGE_READWRITE);
// 复制数据
SIZE_T bytes_read;
ReadProcessMemory(process->Handle, address, private_copy, size, &bytes_read);
// 更新页表指向私有副本
UpdatePageTable(process, address, private_copy, PAGE_READWRITE);
} else {
// 直接修改保护属性为可写
DWORD old_protect;
VirtualProtectEx(process->Handle, address, size, PAGE_READWRITE, &old_protect);
}
}
}
4.2 地址空间布局随机化
// ASLR 地址随机化机制
void* DetermineImageBase(const char* module_path, PROCESS* process) {
void* preferred_base = GetPreferredImageBase(module_path);
// 检查是否支持ASLR
if (IsASLREnabled(module_path) && IsASLREnabledSystemWide()) {
// 随机化基址
void* random_base = GenerateRandomBaseAddress(preferred_base);
// 检查地址是否可用
while (!IsAddressRangeAvailable(process, random_base, GetImageSize(module_path))) {
random_base = GenerateRandomBaseAddress(preferred_base);
}
return random_base;
} else {
// 使用首选基址
return preferred_base;
}
}
4.3 实例句柄与模块基址
// 实例句柄的本质
void demonstrate_instance_handle() {
// 获取当前模块的实例句柄
HMODULE hInstance = GetModuleHandle(NULL);
printf("实例句柄分析:\n");
printf("实例句柄值: 0x%p\n", hInstance);
// 实例句柄就是模块的加载基址
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hInstance;
printf("DOS头签名: 0x%04X ('MZ')\n", pDosHeader->e_magic);
// 获取PE头
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hInstance + pDosHeader->e_lfanew);
printf("PE头签名: 0x%08X ('PE\\0\\0')\n", pNtHeaders->Signature);
printf("首选基址: 0x%p\n", (void*)pNtHeaders->OptionalHeader.ImageBase);
printf("实际基址: 0x%p\n", hInstance);
printf("入口点RVA: 0x%08X\n", pNtHeaders->OptionalHeader.AddressOfEntryPoint);
// 检查基址是否随机化
if ((void*)pNtHeaders->OptionalHeader.ImageBase != hInstance) {
printf("ASLR: 已启用 (基址已随机化)\n");
} else {
printf("ASLR: 未启用 (使用固定基址)\n");
}
}
4.4 进程创建的系统调用层次
// Windows 进程创建的系统调用链
NTSTATUS CreateProcessSystemCall(
PUNICODE_STRING ImagePath,
PUNICODE_STRING CommandLine,
PSECURITY_DESCRIPTOR ProcessSecurity,
PSECURITY_DESCRIPTOR ThreadSecurity,
BOOLEAN InheritHandles,
ULONG CreateFlags,
PVOID Environment,
PUNICODE_STRING CurrentDirectory,
PPROCESS_INFORMATION ProcessInformation
) {
// 1. 验证参数和权限
NTSTATUS status = ValidateProcessCreationParameters(ImagePath, CreateFlags);
if (!NT_SUCCESS(status)) return status;
// 2. 创建进程对象
HANDLE hProcess;
status = NtCreateProcessEx(&hProcess, PROCESS_ALL_ACCESS, NULL,
NtCurrentProcess(), CreateFlags,
NULL, NULL, NULL);
// 3. 初始化进程地址空间
if (NT_SUCCESS(status)) {
status = InitializeProcessAddressSpace(hProcess, ImagePath);
}
// 4. 创建主线程
HANDLE hThread;
if (NT_SUCCESS(status)) {
status = NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL,
hProcess, StartAddress, Parameter,
CreateFlags, 0, 0, 0, NULL);
}
// 5. 设置进程和线程信息
if (NT_SUCCESS(status)) {
ProcessInformation->hProcess = hProcess;
ProcessInformation->hThread = hThread;
ProcessInformation->dwProcessId = GetProcessId(hProcess);
ProcessInformation->dwThreadId = GetThreadId(hThread);
}
return status;
}
第五部分:调试与分析工具
5.1 进程和实例信息查看
void comprehensive_process_analysis() {
printf("=== 综合进程分析 ===\n");
// 获取当前进程信息
DWORD current_pid = GetCurrentProcessId();
HANDLE hProcess = GetCurrentProcess();
HMODULE hInstance = GetModuleHandle(NULL);
printf("当前进程ID: %d\n", current_pid);
printf("当前实例句柄: 0x%p\n", hInstance);
printf("进程句柄: 0x%p\n", hProcess);
// 获取可执行文件路径
char exe_path[MAX_PATH];
GetModuleFileName(NULL, exe_path, MAX_PATH);
printf("可执行文件: %s\n", exe_path);
// 获取命令行参数
printf("命令行: %s\n", GetCommandLine());
// 获取环境块
PVOID env_block = GetEnvironmentStrings();
printf("环境块地址: 0x%p\n", env_block);
// 分析PEB
_asm {
mov eax, fs:[0x30] // PEB在FS:[0x30]
mov [peb_address], eax
}
printf("PEB地址: 0x%p\n", peb_address);
}
5.2 内存映射分析
void analyze_memory_mapping() {
printf("=== 内存映射分析 ===\n");
HMODULE hInstance = GetModuleHandle(NULL);
printf("模块基址: 0x%p\n", hInstance);
// 获取模块信息
MODULEINFO mod_info;
if (GetModuleInformation(GetCurrentProcess(), hInstance, &mod_info, sizeof(mod_info))) {
printf("模块大小: %u bytes\n", mod_info.SizeOfImage);
printf("入口点: 0x%p\n", mod_info.EntryPoint);
}
// 遍历内存区域
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
void* address = sys_info.lpMinimumApplicationAddress;
while (address < sys_info.lpMaximumApplicationAddress) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(address, &mbi, sizeof(mbi))) {
if (mbi.State == MEM_COMMIT) {
printf("区域: 0x%p - 0x%p, 大小: %u KB, 保护: 0x%08X",
mbi.BaseAddress,
(BYTE*)mbi.BaseAddress + mbi.RegionSize,
mbi.RegionSize / 1024,
mbi.Protect);
if (mbi.Type == MEM_IMAGE) {
printf(" [映像]\n");
} else if (mbi.Type == MEM_MAPPED) {
printf(" [映射文件]\n");
} else if (mbi.Type == MEM_PRIVATE) {
printf(" [私有]\n");
}
}
address = (BYTE*)mbi.BaseAddress + mbi.RegionSize;
} else {
break;
}
}
}
第六部分 概述总结
6.1 三者的核心关系:
6.1.1. **可执行程序 → 实例**:
**静态到动态**的转换
- 可执行程序是磁盘上的文件
- 实例是内存中的运行时代码和数据
6.1.2 **实例 → 进程**:
**内容到环境**的包装
- 实例是程序的具体表现
- 进程是为实例提供的执行环境
6.1.3 **可执行程序 → 进程**:
**蓝图到运行时**的完整链条
- 一个可执行程序可以创建多个进程
- 每个进程运行一个独立的实例
6.2 工作机制要点:
- **加载机制**:PE加载器将磁盘文件映射到内存
- **内存管理**:实例使用进程的地址空间,但有独立的数据副本
- **资源隔离**:每个进程有独立的句柄表和环境
- **实例标识**:HINSTANCE本质上是模块的加载基地址
6.3 实际作用:
- **稳定性**:进程隔离确保一个实例崩溃不影响其他实例
- **安全性**:每个实例在独立的沙箱中运行
- **资源管理**:操作系统可以精确控制每个实例的资源使用
- **调试支持**:开发人员可以单独调试每个实例
理解这三者之间的关系是Windows系统编程的基础,对于开发稳定、安全的应用程序至关重要。

2万+

被折叠的 条评论
为什么被折叠?



