DLL and Process Viewer - Felix John COLIBRI. |
- abstract : display the list of running processes, with their associated DLLs and Memory mapped files
- key words : Process walker, Windows Modules, Packages, PsAPI
- software used : Windows XP, Delphi 6
- hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
- scope : Delphi 5, Delphi 6, Delphi 7, Delphi 2005, Delphi 2006, Delphi 2007, Turbo Delphi for Windows
- level : Delphi developer, Windows Developer
- plan :
1 - Displaying Windows Processes
This very simple utility presents the list of running processes with their associated modules (DLLs) and memory mapped files. The tools works for NT-class windows (NT, Windows 2K, XP).
For Windows-9x class Windows (Windows 95, Windows 98), a similar technique could be used with the Windows ToolHelp library.
2 - The Windows Process List 2.1 - Walking the Process List
Displaying the Windows Process list is a very ancient activity. We did it mainly under Windows 3.1, together with memory spelunking (Jefferey RICHTER, Max PIETRECK were our idols in those times).
We recently wanted to check the process list and their associated DLLs. Since the old utilities would not work, we had to do some Googling around. The present project is the result of those searches.
On NT, the base library is PsAPI. For Delphi 6 and later, CodeGear has included the PsAPI.PAS import unit in: c:\Program Files\Borland\Delphi6\Source\Rtl\Win
Using this unit, the steps are quite straightforward: - to get the process list, we use the EnumProcesses function:
function EnumProcesses(p_pt_process_id_list: PInteger;
p_list_bytes_max: Integer;
var pv_list_bytes_read: Integer): boolean; stdcall;
| and: - p_pt_process_id_list is a pointer to structure which will receive a list of process IDs
- p_list_bytes_max is the size of this structure
- pv_list_bytes_read is the number of bytes actually transfered. This value, divided by the size of a process ID (4) will be the process count
As in many such Windows calls, we might perform two calls
- the first to get the actual process count, which will allow to allocate the correct process ID array
- a second to get the process IDs
Alternately, we can call the routine using a "reasonable" process count:
const k_process_id_array_max= 1000;
var l_process_id_array_size: DWORD;
l_process_id_array: array[0..k_process_id_array_max] of Integer;
if EnumProcesses(@ l_process_id_array,
k_process_id_array_max* 4, l_process_id_array_size)
then // ...ooo... | - once we have the table of Process IDs, we can open the Processes using (Windows.Pas):
function OpenProcess(p_desired_access: DWORD;
p_inherit_handle: BOOL; p_process_id: DWORD): THandle; stdcall;
| - and for each process, we can
- get the process file name, by calling GetModuleFileNameExA
- query the module (DLL) list with:
function EnumProcessModules(p_process_handle: THandle;
p_pt_module_handle_list: PHInst; p_list_bytes_max: Integer;
var pv_list_bytes_read: Integer) : boolean; stdcall;
| and: - p_process_handle is the process handle
- p_pt_module_handle_list is a pointer to a module handle list
- p_list_bytes_max is the size of the module handle list
- pv_list_bytes_read is the byte count transfered
- get the memory mapped file names
2.2 - The Process viewer structure We want to display the informations about the running processes, and for each
process, the module and mapped file list. We have two options: - display all the information by calling the routines and placing the results in a tMemo (or a tTreeView) on the fly
- build an in-memory structure, and display this structure with visual components
We will use the second route, since it is independent of the display components. You can use our querying structure, and display the information
using different visual component.
To save the process / DLL information, we will use our traditional tStringList encapsulation: - c_process_list handles the list of processes
- c_process contains information about each process (name, memory load address, priority etc) as well as
- a tStringList of c_module
- a tStringList of c_memory_mapped files
The process module routine is:
procedure c_process_list.get_nt_process_list(p_do_build_memory_mapped_file_list: Boolean);
const k_process_id_array_max= 1000;
var l_process_index: Integer;
// -- 4x process count l_process_id_array_size: DWORD;
l_process_id_array: array[0..k_process_id_array_max- 1] of Integer;
l_process_name: array[0..MAX_PATH- 1] of char;
l_process_handle: THandle;
l_module_handle: HMODULE;
l_module_count_x_4: DWORD;
l_module_name: array[0..MAX_PATH- 1] of char;
l_c_process: c_process;
l_priority: String; begin // build_process_list
if not EnumProcesses(@l_process_id_array,
k_process_id_array_max* 4, l_process_id_array_size)
then raise Exception.Create('PSAPI.DLL_not_found');
display('process_count_x_4 '+ f_integer_to_hex(l_process_id_array_size));
for l_process_index:= 0 to (l_process_id_array_size div SizeOf(Integer)- 1) do
begin
l_process_handle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ,
FALSE, l_process_id_array[l_process_index]);
if l_process_handle<> 0
then begin
if GetModuleFileNameExA(l_process_handle, 0, l_process_name,
SizeOf(l_process_name))> 0
then begin
l_c_process:= f_c_add_process(ExtractFileName(l_process_name));
// -- get the first module, (2nd parameter: only 1 handle),
// -- which is the process itself
// -- as well as the module count
if EnumProcessModules(l_process_handle, @l_module_handle,
SizeOf(l_module_handle), l_module_count_x_4)
then begin
with l_c_process do
begin
GetModuleFileNameExA(l_process_handle, l_module_handle,
l_module_name, SizeOf(l_module_name));
m_main_module_name:= ExtractFileName(l_process_name);
m_process_id:= l_process_id_array[l_process_index];
fill_process_times(l_process_handle, l_c_process);
case GetPriorityClass(l_process_handle) of
HIGH_PRIORITY_CLASS: l_priority:= 'High';
IDLE_PRIORITY_CLASS: l_priority:= 'Idle';
NORMAL_PRIORITY_CLASS: l_priority:= 'Normal';
REALTIME_PRIORITY_CLASS: l_priority:= 'RealTime';
end;
m_process_priority:= l_priority;
m_process_path:= ExtractFilePath(l_process_name);
m_module_count:= l_module_count_x_4 div 4;
if m_module_count> 0
then build_module_list(l_process_handle, l_c_process);
if p_do_build_memory_mapped_file_list
then build_memory_mapped_file_list(l_process_handle,
l_c_process);
end; // with l_c_process
end; // could enumerate the first module
end; // has found the process
CloseHandle(l_process_handle);
end; // process_handle> 0
end; // for l_process_index end; // build_process_list
|
And the (nested) module building routine is (partial):
procedure build_module_list(p_process_handle: tHandle; p_c_process: c_process);
var l_module_count_x_4: DWORD;
l_module_name: array[0..MAX_PATH- 1] of char;
l_module_index: Integer;
l_c_module: c_module;
l_module_info: TModuleInfo; begin
with p_c_process do begin
SetLength(m_module_handle_array, m_module_count);
EnumProcessModules(p_process_handle, @m_module_handle_array[0],
4* m_module_count, l_module_count_x_4);
for l_module_index:= 0 to m_module_count- 1 do
begin GetModuleFileNameExA(p_process_handle,
m_module_handle_array[l_module_index],
l_module_name, SizeOf(l_module_name));
l_c_module:= f_c_add_module(ExtractFileName(l_module_name));
with l_c_module do
begin m_module_name:= m_name;
m_module_path:= ExtractFilePath(l_module_name);
if GetModuleInformation(p_process_handle,
m_module_handle_array[l_module_index],
@l_module_info, SizeOf(l_module_info))
then
with l_module_info do
begin
m_pt_base_address:= lpBaseOfDll;
m_image_size:= SizeOfImage;
m_pt_entry_point:= EntryPoint;
end; // with l_module_info
end; // with l_c_module
end; // for l_module_index
end; // with p_c_process end; // build_module_list
|
To get the memory mapped files, we use:
procedure build_memory_mapped_file_list(p_process_handle: tHandle;
p_c_process: c_process);
function f_memory_type(p_memory_type: DWORD): string;
const k_memory_type_mask = DWORD($0000000F);
begin Result := '';
case p_memory_type and k_memory_type_mask of
1: Result := 'Read-only';
2: Result := 'Executable';
4: Result := 'Read/write';
5: Result := 'Copy on write';
else Result := 'Unknown';
end; // case
if p_memory_type and $100 <> 0
then Result := Result + ', Shareable';
end; // f_memory_type
const k_mapped_file_addresss_mask = DWORD($FFFFF000);
var l_working_set_array: array[0..$3FFF - 1] of DWORD;
l_mapped_file_index: Integer;
l_mapped_file_name: array[0..MAX_PATH] of char;
l_pt_working_set: Pointer;
l_c_memory_mapped_file: c_memory_mapped_file;
l_file_name: String;
begin // build_memory_mapped_file_list
with p_c_process do
if QueryWorkingSet(p_process_handle,
@l_working_set_array, SizeOf(l_working_set_array))
then
for l_mapped_file_index := 1 to l_working_set_array[0] do
begin
l_pt_working_set := Pointer(l_working_set_array[l_mapped_file_index]
and k_mapped_file_addresss_mask);
GetMappedFileName(p_process_handle, l_pt_working_set,
l_mapped_file_name, SizeOf(l_mapped_file_name));
l_file_name:= f_get_file_name(l_mapped_file_name);
l_c_memory_mapped_file:= f_c_add_memory_mapped_file(l_file_name);
with l_c_memory_mapped_file do
begin
m_pt_working_set:= l_pt_working_set;
m_memory_type:= f_memory_type(l_working_set_array[l_mapped_file_index]);
end; // with l_c_memory_mapped_file
end; // with p_c_process, QueryWorkinSeg, for l_mapped_file_index
end; // build_memory_mapped_file_list |
2.3 - The main Process Walker form
A tButton.Click event creates and builds the process list, and displays each process in a tListBox:
var g_c_process_list: c_process_list= Nil;
procedure TForm1.create_Click(Sender: TObject);
var l_process_index: Integer; begin
g_c_process_list:= c_process_list.create_process_list('process_list');
with g_c_process_list do begin
get_nt_process_list(build_mmf_.Checked);
process_listbox_.Items.Clear;
for l_process_index:= 0 to f_process_count- 1 do
with f_c_process(l_process_index) do
process_listbox_.Items.AddObject(m_name, f_c_self);
end; // with g_c_process_list end; // create_Click |
Clicking on an item or this tListBox will display the module list:
procedure TForm1.process_listbox_Click(Sender: TObject);
var l_module_index: Integer;
l_memory_mapped_file_index: Integer; begin
with process_listbox_ do
if ItemIndex>= 0 then
with c_process(Items.Objects[ItemIndex]), process_memo_.Lines do
begin Clear;
// display_module_list;
Add(m_name);
Add(' '+ m_process_path);
Add(' id '+ IntToStr(m_process_id));
Add(' priority '+ m_process_priority);
Add(' time k, c, u '+ m_kernel_time+ ' '
+ m_cpu_time+ ' '+ m_user_time);
module_count_label_.Caption:= IntToStr(m_module_count);
module_listbox_.Items.Clear;
module_listbox_.Sorted:= sort_dll_.Checked;
for l_module_index:= 0 to f_module_count- 1 do
with f_c_module(l_module_index) do
module_listbox_.Items.AddObject(m_name, f_c_self);
memory_mapped_file_listbox_.Items.Clear;
for l_memory_mapped_file_index:= 0 to f_memory_mapped_file_count- 1 do
with f_c_memory_mapped_file(l_memory_mapped_file_index) do
memory_mapped_file_listbox_.Items.AddObject(m_name, f_c_self);
end; end; // process_listbox_Click |
2.4 - Module and DLL viewer Snapshot Here is a snapshot after the creation of the process list:
and when we click on the p_dll_process_viewer exe and the PSAPI.dll:
3 - Improvements and Comments
Many improvements are possible - instead of 2 tListBoxes, we could have used a tTreeView
- we could turn this analyzer into a monitor by adding a timer and refreshing
the display upon each timer tick. In this case, the DLL list, and certainly the Memory Mapped File analysis should be disconnected because of the construction time
- in our version, we also added a snapshot saving and a delta computations to
find out which processes or DLLs were loaded between two requests
In addition, here are a couple of comments: - in addition to the module list, you can get the driver lists
- it seems that the WMI (Windows Instrumentation API) would bring back more information about processes then PsAPI, but we did not try this route
4 - Download the Sources
Here are the source code files: The .ZIP file(s) contain:
- the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP
- are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP: - create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder. The Pascal code uses the Alsacian notation, which prefixes identifier by
program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate:
the more traffic and Google references we get, the more articles we will write.
5 - References The most useful information came from: - The Delphi 5 Developer's Guide - Steve TEIXEIRA and Xavier PACHECO
Sams 2000 - ISBN 0-672-31781-8 One of the best Delphi 5 book, as all books written by those authors - many files found using Google with code snippets and a component (ggProcessViewer) from Guido GEURTS (even some on Tamaracka)
- Colin Wilson's Delphi 5 process viewer
6 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |