win32-process 0.7.5 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7abeeac7e3b746666569ab363e702c8939a4a596
4
- data.tar.gz: 693447613255756de95668ddfefa702ecc5b9921
3
+ metadata.gz: 47640c7bc0c96cd8615348e2f8757c0e1aecec95
4
+ data.tar.gz: 3f391446c320d0bd0feac23f0effff0bc945ae65
5
5
  SHA512:
6
- metadata.gz: 2a7faa782828c7a8cbbf54f997ded9a3c97023b641417207e03938378520f0072b29e7fc1d11c26307e59c94ff975cdfdfebdbe9cbc0adedf2abe57a1251ecee
7
- data.tar.gz: 9a1ba82a14f16365850fbc7e50787c3233fedd3d54307757202dd60d05a9f4057cc4e617d3ad7d97783861c4098ee82c6bfdb56a8d4210d6404aca1a0682e613
6
+ metadata.gz: 63c279644c090065293a308b6b1e68b43c67b7d25c53e0019e82dfc2d1022a6a800ab152b4675188e10077037f82b7cb9f4ecdaae0982c3aee8ba7e81dd6a697
7
+ data.tar.gz: f5b8a0d72aaa046fdd0a749720f7f5ae61ad99a2b157a3339c8f72cfb35685aa6916522626d036d447f7fc5bf2068cfdf8cd74531e222f6619915dc9fba9a697
data/CHANGES CHANGED
@@ -1,3 +1,7 @@
1
+ = 0.8.0 - 29-Apr-2015
2
+ * Added the Process.snapshot method that lets you gather information for
3
+ the heap, threads, modules, and processes.
4
+
1
5
  = 0.7.5 - 3-Mar-2015
2
6
  * Use require_relative where possible.
3
7
  * Fixed a bug in Process.setrlimit. Note that this method has been marked
@@ -10,7 +10,7 @@ module Process
10
10
  extend Process::Constants
11
11
 
12
12
  # The version of the win32-process library.
13
- WIN32_PROCESS_VERSION = '0.7.5'
13
+ WIN32_PROCESS_VERSION = '0.8.0'
14
14
 
15
15
  # Disable popups. This mostly affects the Process.kill method.
16
16
  SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX)
@@ -914,6 +914,75 @@ module Process
914
914
  exitcode
915
915
  end
916
916
  end
917
+
918
+ # Returns a list of process information structs in the form of a hash,
919
+ # with the pid as the key, and an array of information as the value of
920
+ # that key. The type of information in that array depends on the
921
+ # +info_type+ parameter. The possible values for +info_type+, and the
922
+ # type of information they each return is as follows:
923
+ #
924
+ # :thread => ThreadSnapInfo[:thread_id, :process_id, :base_priority]
925
+ # :heap => HeapSnapInfo[:address, :block_size, :flags, :process_id, :heap_id]
926
+ # :module => ModuleSnapInfo[:process_id, :address, :module_size, :handle, :name, :path]
927
+ # :process => ProcessSnapInfo[:process_id, :threads, :parent_process_id, :priority, :flags, :path]
928
+ #
929
+ # If no argument is provided, then :thread is assumed. Note that it is up
930
+ # to you to filter by pid if you wish.
931
+ #
932
+ # Example:
933
+ #
934
+ # # Get all thread info
935
+ # Process.snapshot.each{ |pid, v|
936
+ # puts "PID: #{pid}"
937
+ # p v
938
+ # }
939
+ #
940
+ # # Get module info for just the current process
941
+ # p Process.snapshot(:module)[Process.pid]
942
+ #
943
+ # # Get heap info for just the current process
944
+ # p Process.snapshot(:heap)[Process.pid]
945
+ #
946
+ # # Show pids of all running processes
947
+ # p Process.snapshot(:process).keys
948
+ #
949
+ def snapshot(info_type = 'thread')
950
+ case info_type.to_s.downcase
951
+ when 'thread'
952
+ flag = TH32CS_SNAPTHREAD
953
+ when 'heap'
954
+ flag = TH32CS_SNAPHEAPLIST
955
+ when 'module'
956
+ flag = TH32CS_SNAPMODULE
957
+ when 'process'
958
+ flag = TH32CS_SNAPPROCESS
959
+ else
960
+ raise ArgumentError, "info_type '#{info_type}' unsupported"
961
+ end
962
+
963
+ begin
964
+ handle = CreateToolhelp32Snapshot(flag, Process.pid)
965
+
966
+ if handle == INVALID_HANDLE_VALUE
967
+ raise SystemCallError.new('CreateToolhelp32Snapshot', FFI.errno)
968
+ end
969
+
970
+ case info_type.to_s.downcase
971
+ when 'thread'
972
+ array = get_thread_info(handle)
973
+ when 'heap'
974
+ array = get_heap_info(handle)
975
+ when 'module'
976
+ array = get_module_info(handle)
977
+ when 'process'
978
+ array = get_process_info(handle)
979
+ end
980
+
981
+ array
982
+ ensure
983
+ CloseHandle(handle) if handle
984
+ end
985
+ end
917
986
  end
918
987
 
919
988
  class << self
@@ -926,6 +995,137 @@ module Process
926
995
  bool ? buf.read_string : nil
927
996
  end
928
997
 
998
+ # Return thread info for Process.snapshot
999
+ def get_thread_info(handle, pid = nil)
1000
+ lpte = THREADENTRY32.new
1001
+ lpte[:dwSize] = lpte.size
1002
+
1003
+ hash = Hash.new{ |h,k| h[k] = [] }
1004
+
1005
+ if Thread32First(handle, lpte)
1006
+ hash[lpte[:th32OwnerProcessID]] << ThreadSnapInfo.new(lpte[:th32ThreadID], lpte[:th32OwnerProcessID], lpte[:tpBasePri])
1007
+ else
1008
+ if FFI.errno == ERROR_NO_MORE_FILES
1009
+ return hash
1010
+ else
1011
+ raise SystemCallError.new('Thread32First', FFI.errno)
1012
+ end
1013
+ end
1014
+
1015
+ while Thread32Next(handle, lpte)
1016
+ hash[lpte[:th32OwnerProcessID]] << ThreadSnapInfo.new(lpte[:th32ThreadID], lpte[:th32OwnerProcessID], lpte[:tpBasePri])
1017
+ end
1018
+
1019
+ hash
1020
+ end
1021
+
1022
+ # Return heap info for Process.snapshot
1023
+ def get_heap_info(handle)
1024
+ hash = Hash.new{ |h,k| h[k] = [] }
1025
+
1026
+ hl = HEAPLIST32.new
1027
+ hl[:dwSize] = hl.size
1028
+
1029
+ if Heap32ListFirst(handle, hl)
1030
+ while Heap32ListNext(handle, hl)
1031
+ he = HEAPENTRY32.new
1032
+ he[:dwSize] = he.size
1033
+
1034
+ if Heap32First(he, Process.pid, hl[:th32HeapID])
1035
+ hash[he[:th32ProcessID]] << HeapSnapInfo.new(he[:dwAddress], he[:dwBlockSize], he[:dwFlags], he[:th32ProcessID], he[:th32HeapID])
1036
+ else
1037
+ if FFI.errno == ERROR_NO_MORE_FILES
1038
+ break
1039
+ else
1040
+ raise SystemCallError.new('Heap32First', FFI.errno)
1041
+ end
1042
+ end
1043
+
1044
+ while Heap32Next(he)
1045
+ hash[he[:th32ProcessID]] << HeapSnapInfo.new(he[:dwAddress], he[:dwBlockSize], he[:dwFlags], he[:th32ProcessID], he[:th32HeapID])
1046
+ end
1047
+ end
1048
+ end
1049
+
1050
+ hash
1051
+ end
1052
+
1053
+ # Return module info for Process.snapshot
1054
+ def get_module_info(handle)
1055
+ hash = Hash.new{ |h,k| h[k] = [] }
1056
+
1057
+ me = MODULEENTRY32.new
1058
+ me[:dwSize] = me.size
1059
+
1060
+ if Module32First(handle, me)
1061
+ hash[me[:th32ProcessID]] << ModuleSnapInfo.new(
1062
+ me[:th32ProcessID],
1063
+ me[:modBaseAddr].to_i,
1064
+ me[:modBaseSize],
1065
+ me[:hModule],
1066
+ me[:szModule].to_s,
1067
+ me[:szExePath].to_s
1068
+ )
1069
+ else
1070
+ if FFI.errno == ERROR_NO_MORE_FILES
1071
+ return hash
1072
+ else
1073
+ raise SystemCallError.new('Module32First', FFI.errno)
1074
+ end
1075
+ end
1076
+
1077
+ while Module32Next(handle, me)
1078
+ hash[me[:th32ProcessID]] << ModuleSnapInfo.new(
1079
+ me[:th32ProcessID],
1080
+ me[:modBaseAddr].to_i,
1081
+ me[:modBaseSize],
1082
+ me[:hModule],
1083
+ me[:szModule].to_s,
1084
+ me[:szExePath].to_s
1085
+ )
1086
+ end
1087
+
1088
+ hash
1089
+ end
1090
+
1091
+ # Return process info for Process.snapshot
1092
+ def get_process_info(handle)
1093
+ hash = Hash.new{ |h,k| h[k] = [] }
1094
+
1095
+ pe = PROCESSENTRY32.new
1096
+ pe[:dwSize] = pe.size
1097
+
1098
+ if Process32First(handle, pe)
1099
+ hash[pe[:th32ProcessID]] = ProcessSnapInfo.new(
1100
+ pe[:th32ProcessID],
1101
+ pe[:cntThreads],
1102
+ pe[:th32ParentProcessID],
1103
+ pe[:pcPriClassBase],
1104
+ pe[:dwFlags],
1105
+ pe[:szExeFile].to_s
1106
+ )
1107
+ else
1108
+ if FFI.errno == ERROR_NO_MORE_FILES
1109
+ return hash
1110
+ else
1111
+ raise SystemCallError.new('Process32First', FFI.errno)
1112
+ end
1113
+ end
1114
+
1115
+ while Process32Next(handle, pe)
1116
+ hash[pe[:th32ProcessID]] = ProcessSnapInfo.new(
1117
+ pe[:th32ProcessID],
1118
+ pe[:cntThreads],
1119
+ pe[:th32ParentProcessID],
1120
+ pe[:pcPriClassBase],
1121
+ pe[:dwFlags],
1122
+ pe[:szExeFile].to_s
1123
+ )
1124
+ end
1125
+
1126
+ hash
1127
+ end
1128
+
929
1129
  # Private method that returns the Windows major version number.
930
1130
  def windows_version
931
1131
  ver = OSVERSIONINFO.new
@@ -938,4 +1138,4 @@ module Process
938
1138
  ver[:dwMajorVersion]
939
1139
  end
940
1140
  end
941
- end
1141
+ end
@@ -110,4 +110,12 @@ module Process::Constants
110
110
  # GetExitCodeProcess
111
111
 
112
112
  STILL_ACTIVE = 259
113
+
114
+ # Snapshot constants
115
+
116
+ TH32CS_SNAPHEAPLIST = 0x00000001
117
+ TH32CS_SNAPPROCESS = 0x00000002
118
+ TH32CS_SNAPTHREAD = 0x00000004
119
+ TH32CS_SNAPMODULE = 0x00000008
120
+ ERROR_NO_MORE_FILES = 0x00000018
113
121
  end
@@ -24,6 +24,7 @@ module Process::Functions
24
24
  ffi_lib :kernel32
25
25
 
26
26
  attach_pfunc :CloseHandle, [:handle], :bool
27
+ attach_pfunc :CreateToolhelp32Snapshot, [:dword, :dword], :handle
27
28
  attach_pfunc :GenerateConsoleCtrlEvent, [:dword, :dword], :bool
28
29
  attach_pfunc :GetCurrentProcess, [], :handle
29
30
  attach_pfunc :GetModuleHandle, :GetModuleHandleA, [:string], :hmodule
@@ -31,12 +32,22 @@ module Process::Functions
31
32
  attach_pfunc :GetPriorityClass, [:handle], :dword
32
33
  attach_pfunc :GetProcAddress, [:hmodule, :string], :pointer
33
34
  attach_pfunc :GetVersionExA, [:pointer], :bool
35
+ attach_pfunc :Heap32ListFirst, [:handle, :pointer], :bool
36
+ attach_pfunc :Heap32ListNext, [:handle, :pointer], :bool
37
+ attach_pfunc :Heap32First, [:pointer, :dword, :uintptr_t], :bool
38
+ attach_pfunc :Heap32Next, [:pointer], :bool
39
+ attach_pfunc :Module32First, [:handle, :pointer], :bool
40
+ attach_pfunc :Module32Next, [:handle, :pointer], :bool
34
41
  attach_pfunc :IsProcessInJob, [:handle, :pointer, :pointer], :bool # 2nd arg optional
35
42
  attach_pfunc :OpenProcess, [:dword, :bool, :dword], :handle
43
+ attach_pfunc :Process32First, [:handle, :pointer], :bool
44
+ attach_pfunc :Process32Next, [:handle, :pointer], :bool
36
45
  attach_pfunc :SetHandleInformation, [:handle, :dword, :dword], :bool
37
46
  attach_pfunc :SetErrorMode, [:uint], :uint
38
47
  attach_pfunc :SetPriorityClass, [:handle, :dword], :bool
39
48
  attach_pfunc :TerminateProcess, [:handle, :uint], :bool
49
+ attach_pfunc :Thread32First, [:handle, :pointer], :bool
50
+ attach_pfunc :Thread32Next, [:handle, :pointer], :bool
40
51
  attach_pfunc :WaitForSingleObject, [:handle, :dword], :dword
41
52
 
42
53
  attach_pfunc :CreateRemoteThread,
@@ -10,4 +10,4 @@ class String
10
10
  def to_wide_string!
11
11
  replace((self + 0.chr).encode('UTF-16LE'))
12
12
  end
13
- end
13
+ end
@@ -108,11 +108,111 @@ module Process::Structs
108
108
  )
109
109
  end
110
110
 
111
+ class THREADENTRY32 < FFI::Struct
112
+ layout(
113
+ :dwSize, :dword,
114
+ :cntUsage, :dword,
115
+ :th32ThreadID, :dword,
116
+ :th32OwnerProcessID, :dword,
117
+ :tpBasePri, :long,
118
+ :tpDeltaPri, :long,
119
+ :dwFlags, :dword
120
+ )
121
+ end
122
+
123
+ class HEAPLIST32 < FFI::Struct
124
+ layout(
125
+ :dwSize, :size_t,
126
+ :th32ProcessID, :dword,
127
+ :th32HeapID, :uintptr_t,
128
+ :dwFlags, :dword
129
+ )
130
+ end
131
+
132
+ class HEAPENTRY32 < FFI::Struct
133
+ layout(
134
+ :dwSize, :size_t,
135
+ :hHandle, :handle,
136
+ :dwAddress, :uintptr_t,
137
+ :dwBlockSize, :size_t,
138
+ :dwFlags, :dword,
139
+ :dwLockCount, :dword,
140
+ :dwResvd, :dword,
141
+ :th32ProcessID, :dword,
142
+ :th32HeapID, :uintptr_t
143
+ )
144
+ end
145
+
146
+ class MODULEENTRY32 < FFI::Struct
147
+ layout(
148
+ :dwSize, :dword,
149
+ :th32ModuleID, :dword,
150
+ :th32ProcessID, :dword,
151
+ :GlblcntUsage, :dword,
152
+ :ProccntUsage, :dword,
153
+ :modBaseAddr, :pointer,
154
+ :modBaseSize, :dword,
155
+ :hModule, :handle,
156
+ :szModule, [:char, 256],
157
+ :szExePath, [:char, 260]
158
+ )
159
+ end
160
+
161
+ class PROCESSENTRY32 < FFI::Struct
162
+ layout(
163
+ :dwSize, :dword,
164
+ :cntUsage, :dword,
165
+ :th32ProcessID, :dword,
166
+ :th32DefaultHeapID, :uintptr_t,
167
+ :th32ModuleID, :dword,
168
+ :cntThreads, :dword,
169
+ :th32ParentProcessID, :dword,
170
+ :pcPriClassBase, :long,
171
+ :dwFlags, :dword,
172
+ :szExeFile, [:char, 260]
173
+ )
174
+ end
175
+
111
176
  # Used by Process.create
177
+
112
178
  ProcessInfo = Struct.new("ProcessInfo",
113
179
  :process_handle,
114
180
  :thread_handle,
115
181
  :process_id,
116
182
  :thread_id
117
183
  )
184
+
185
+ # Used by Process.snapshot
186
+
187
+ ThreadSnapInfo = Struct.new("ThreadSnapInfo",
188
+ :thread_id,
189
+ :process_id,
190
+ :base_priority
191
+ )
192
+
193
+ HeapSnapInfo = Struct.new("HeapSnapInfo",
194
+ :address,
195
+ :block_size,
196
+ :flags,
197
+ :process_id,
198
+ :heap_id
199
+ )
200
+
201
+ ModuleSnapInfo = Struct.new("ModuleSnapInfo",
202
+ :process_id,
203
+ :address,
204
+ :module_size,
205
+ :handle,
206
+ :name,
207
+ :path
208
+ )
209
+
210
+ ProcessSnapInfo = Struct.new("ProcessSnapInfo",
211
+ :process_id,
212
+ :threads,
213
+ :parent_process_id,
214
+ :priority,
215
+ :flags,
216
+ :path
217
+ )
118
218
  end
@@ -25,7 +25,7 @@ class TC_Win32Process < Test::Unit::TestCase
25
25
  end
26
26
 
27
27
  test "win32-process version is set to the correct value" do
28
- assert_equal('0.7.5', Process::WIN32_PROCESS_VERSION)
28
+ assert_equal('0.8.0', Process::WIN32_PROCESS_VERSION)
29
29
  end
30
30
 
31
31
  test "create basic functionality" do
@@ -298,6 +298,23 @@ class TC_Win32Process < Test::Unit::TestCase
298
298
  assert_not_respond_to(Process, :volume_type)
299
299
  end
300
300
 
301
+ test "snapshot method basic functionality" do
302
+ assert_respond_to(Process, :snapshot)
303
+ assert_nothing_raised{ Process.snapshot }
304
+ assert_kind_of(Hash, Process.snapshot)
305
+ end
306
+
307
+ test "snapshot accepts :thread, :module or :heap arguments" do
308
+ assert_nothing_raised{ Process.snapshot(:thread) }
309
+ assert_nothing_raised{ Process.snapshot(:module) }
310
+ assert_nothing_raised{ Process.snapshot(:heap) }
311
+ assert_nothing_raised{ Process.snapshot(:process) }
312
+ end
313
+
314
+ test "snapshot raises an error if an invalid argument is passed" do
315
+ assert_raise(ArgumentError){ Process.snapshot(:bogus) }
316
+ end
317
+
301
318
  test "ffi functions are private" do
302
319
  assert_not_respond_to(Process, :CloseHandle)
303
320
  assert_not_respond_to(Process, :GetCurrentProcess)
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'win32-process'
5
- spec.version = '0.7.5'
5
+ spec.version = '0.8.0'
6
6
  spec.license = 'Artistic 2.0'
7
7
  spec.authors = ['Daniel Berger', 'Park Heesob']
8
8
  spec.email = 'djberg96@gmail.com'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-process
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Berger
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-03 00:00:00.000000000 Z
12
+ date: 2015-04-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi