Browse Source

[os2/process]: Refactor process_info procs, add process_info_by_handle

flysand7 1 year ago
parent
commit
4eca60946c
2 changed files with 251 additions and 63 deletions
  1. 39 10
      core/os/os2/process.odin
  2. 212 53
      core/os/os2/process_windows.odin

+ 39 - 10
core/os/os2/process.odin

@@ -168,17 +168,37 @@ Process_Info :: struct {
 	This procedure obtains an information, specified by `selection` parameter of
 	This procedure obtains an information, specified by `selection` parameter of
 	a process given by `pid`.
 	a process given by `pid`.
 	
 	
-	Use `free_process_info` to free the memory allocated by this function. In
+	Use `free_process_info` to free the memory allocated by this procedure. In
 	case the function returns an error all temporary allocations would be freed
 	case the function returns an error all temporary allocations would be freed
 	and as such, calling `free_process_info()` is not needed.
 	and as such, calling `free_process_info()` is not needed.
 
 
-	**Note**: The resulting information may or may not contain the
-	selected fields. Please check the `fields` field of the `Process_Info`
-	struct to see if the struct contains the desired fields **before** checking
-	the error code returned by this function.
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
 */
 */
-process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
-	return _process_info(pid, selection, allocator)
+process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_pid(pid, selection, allocator)
+}
+
+/*
+	Obtain information about a process.
+
+	This procedure obtains information, specified by `selection` parameter
+	about a process that has been opened by the application, specified in
+	the `process` parameter.
+
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error, all temporary allocations would be freed
+	and as such, calling `free_process_info` is not needed.
+
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
+	returned by this function.
+*/
+process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
+	return _process_info_by_handle(process, selection, allocator)
 }
 }
 
 
 /*
 /*
@@ -191,15 +211,24 @@ process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtim
 	case this function returns an error, all temporary allocations would be
 	case this function returns an error, all temporary allocations would be
 	freed and as such calling `free_process_info()` is not needed.
 	freed and as such calling `free_process_info()` is not needed.
 
 
-	**Note**: The resulting `Process_Info` may or may not contain the selected
-	fields. Check the `fields` field of the `Process_Info` struct to see, if the
-	struct contains the selected fields **before** checking the error code
+	**Note**: The resulting information may or may contain the fields specified
+	by the `selection` parameter. Always check whether the returned
+	`Process_Info` struct has the required fields before checking the error code
 	returned by this function.
 	returned by this function.
 */
 */
 current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
 current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
 	return _current_process_info(selection, allocator)
 	return _current_process_info(selection, allocator)
 }
 }
 
 
+/*
+	Obtain information about the specified process.
+*/
+process_info :: proc {
+	process_info_by_pid,
+	process_info_by_handle,
+	current_process_info,
+}
+
 /*
 /*
 	Free the information about the process.
 	Free the information about the process.
 
 

+ 212 - 53
core/os/os2/process_windows.odin

@@ -66,7 +66,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
 	return pid_list[:], nil
 	return pid_list[:], nil
 }
 }
 
 
-_process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 	info.pid = pid
 	info.pid = pid
 	defer if err != nil {
 	defer if err != nil {
 		free_process_info(info, allocator)
 		free_process_info(info, allocator)
@@ -83,22 +83,8 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti
 	need_process_handle := need_peb || .Username in selection
 	need_process_handle := need_peb || .Username in selection
 	// Data obtained from process snapshots
 	// Data obtained from process snapshots
 	if need_snapprocess {
 	if need_snapprocess {
-		snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
-		if snap == windows.INVALID_HANDLE_VALUE {
-			return info, _get_platform_error()
-		}
-		defer windows.CloseHandle(snap)
-		entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
-		status := windows.Process32FirstW(snap, &entry)
-		found := false
-		for status {
-			if u32(pid) == entry.th32ProcessID {
-				found = true
-				break
-			}
-			status = windows.Process32NextW(snap, &entry)
-		}
-		if !found {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
 			err = General_Error.Not_Exist
 			err = General_Error.Not_Exist
 			return
 			return
 		}
 		}
@@ -111,30 +97,10 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti
 			info.priority = int(entry.pcPriClassBase)
 			info.priority = int(entry.pcPriClassBase)
 		}
 		}
 	}
 	}
-	// Note(flysand): Not sure which way it's better to get the executable path:
-	// via toolhelp snapshots or by reading other process' PEB memory. I have
-	// a slight suspicion that if both exe path and command line are desired,
-	// it's faster to just read both from PEB, but maybe the toolhelp snapshots
-	// are just better...?
 	if need_snapmodule {
 	if need_snapmodule {
-		snap := windows.CreateToolhelp32Snapshot(
-			windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32,
-			u32(pid),
-		)
-		if snap == windows.INVALID_HANDLE_VALUE {
-			err = _get_platform_error()
-			return
-		}
-		defer windows.CloseHandle(snap)
-		entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) }
-		status := windows.Module32FirstW(snap, &entry)
-		if !status {
-			err = _get_platform_error()
-			return
-		}
-		exe_path: string
-		exe_path, err = windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1,  allocator)
-		if err != nil {
+		exe_path, exe_path_err := _process_exe_by_pid(pid, allocator)
+		if exe_path_err != nil {
+			err = exe_path_err
 			return
 			return
 		}
 		}
 		info.fields |= {.Executable_Path}
 		info.fields |= {.Executable_Path}
@@ -269,27 +235,170 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti
 	return
 	return
 }
 }
 
 
-_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
-	info.pid = cast(int) windows.GetCurrentProcessId()
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	pid := process.pid
+	info.pid = pid
 	defer if err != nil {
 	defer if err != nil {
 		free_process_info(info, allocator)
 		free_process_info(info, allocator)
 	}
 	}
-	need_snapprocess := .PPid in selection || .Priority in selection
+	need_snapprocess := \
+		.PPid in selection ||
+		.Priority in selection
+	need_snapmodule := \
+		.Executable_Path in selection
+	need_peb := \
+		.Command_Line in selection ||
+		.Environment in selection ||
+		.CWD in selection
+	// Data obtained from process snapshots
 	if need_snapprocess {
 	if need_snapprocess {
-		snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
-		if snap == windows.INVALID_HANDLE_VALUE {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
+			err = General_Error.Not_Exist
 			return
 			return
 		}
 		}
-		defer windows.CloseHandle(snap)
-		entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
-		status := windows.Process32FirstW(snap, &entry)
-		for status {
-			if entry.th32ProcessID == u32(info.pid) {
-				break
+		if .PPid in selection {
+			info.fields |= {.PPid}
+			info.ppid = int(entry.th32ParentProcessID)
+		}
+		if .Priority in selection {
+			info.fields |= {.Priority}
+			info.priority = int(entry.pcPriClassBase)
+		}
+	}
+	if need_snapmodule {
+		exe_path, exe_path_err := _process_exe_by_pid(pid, allocator)
+		if exe_path_err != nil {
+			err = exe_path_err
+			return
+		}
+		info.fields |= {.Executable_Path}
+		info.executable_path = exe_path
+	}
+	ph := cast(windows.HANDLE) process.handle
+	if need_peb {
+		ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll"))
+		if ntdll_lib == nil {
+			err = _get_platform_error()
+			return
+		}
+		defer windows.FreeLibrary(ntdll_lib)
+		NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess")
+		if NtQueryInformationProcess == nil {
+			err = _get_platform_error()
+			return
+		}
+		process_info_size: u32 = ---
+		process_info: PROCESS_BASIC_INFORMATION = ---
+		status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
+		if status != 0 {
+			// TODO(flysand): There's probably a mismatch between NTSTATUS and
+			// windows userland error codes, I haven't checked.
+			err = Platform_Error(status)
+			return
+		}
+		if process_info.PebBaseAddress == nil {
+			// Not sure what the error is
+			err = General_Error.Unsupported
+			return
+		}
+		process_peb: PEB = ---
+		bytes_read: uint = ---
+		read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, dest, size_of(T), br)
+		}
+		read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL {
+			return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br)
+		}
+		if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		process_params: RTL_USER_PROCESS_PARAMETERS = ---
+		if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) {
+			err = _get_platform_error()
+			return
+		}
+		if .Command_Line in selection || .Command_Args in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator())
+			if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			if .Command_Line in selection {
+				cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator)
+				if cmdline_err != nil {
+					err = cmdline_err
+					return
+				}
+				info.fields |= {.Command_Line}
+				info.command_line = cmdline
+			}
+			if .Command_Args in selection {
+				args, args_err := _parse_command_line(raw_data(cmdline_w), allocator)
+				if args_err != nil {
+					err = args_err
+					return
+				}
+				info.fields += {.Command_Args}
+				info.command_args = args
 			}
 			}
-			status = windows.Process32NextW(snap, &entry)
 		}
 		}
-		if entry.th32ProcessID != u32(info.pid) {
+		if .Environment in selection {
+			TEMP_ALLOCATOR_GUARD()
+			env_len := process_params.EnvironmentSize / 2
+			envs_w := make([]u16, env_len, temp_allocator())
+			if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator)
+			if envs_err != nil {
+				err = envs_err
+				return
+			}
+			info.fields |= {.Environment}
+			info.environment = envs
+		}
+		if .CWD in selection {
+			TEMP_ALLOCATOR_GUARD()
+			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator())
+			if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) {
+				err = _get_platform_error()
+				return
+			}
+			cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator)
+			if cwd_err != nil {
+				err = cwd_err
+				return
+			}
+			info.fields |= {.CWD}
+			info.cwd = cwd
+		}
+	}
+	if .Username in selection {
+		username, username_err := _get_process_user(ph, allocator)
+		if username_err != nil {
+			err = username_err
+			return
+		}
+		info.fields |= {.Username}
+		info.username = username
+	}
+	err = nil
+	return
+}
+
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = cast(int) windows.GetCurrentProcessId()
+	defer if err != nil {
+		free_process_info(info, allocator)
+	}
+	need_snapprocess := .PPid in selection || .Priority in selection
+	if need_snapprocess {
+		entry, entry_err := _process_entry_by_pid(info.pid)
+		if entry_err != nil {
 			err = General_Error.Not_Exist
 			err = General_Error.Not_Exist
 			return
 			return
 		}
 		}
@@ -474,6 +583,56 @@ _filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration {
 	return time.Duration(ticks * 100)
 	return time.Duration(ticks * 100)
 }
 }
 
 
+@(private)
+_process_entry_by_pid :: proc(pid: int) -> (windows.PROCESSENTRY32W, Error) {
+	snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return {}, _get_platform_error()
+	}
+	defer windows.CloseHandle(snap)
+	entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
+	status := windows.Process32FirstW(snap, &entry)
+	found := false
+	for status {
+		if u32(pid) == entry.th32ProcessID {
+			found = true
+			break
+		}
+		status = windows.Process32NextW(snap, &entry)
+	}
+	if !found {
+		return {}, General_Error.Not_Exist
+	}
+	return entry, nil
+}
+
+// Note(flysand): Not sure which way it's better to get the executable path:
+// via toolhelp snapshots or by reading other process' PEB memory. I have
+// a slight suspicion that if both exe path and command line are desired,
+// it's faster to just read both from PEB, but maybe the toolhelp snapshots
+// are just better...?
+@(private)
+_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (string, Error) {
+	snap := windows.CreateToolhelp32Snapshot(
+		windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32,
+		u32(pid),
+	)
+	if snap == windows.INVALID_HANDLE_VALUE {
+		return "", _get_platform_error()
+	}
+	defer windows.CloseHandle(snap)
+	entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) }
+	status := windows.Module32FirstW(snap, &entry)
+	if !status {
+		return "", _get_platform_error()
+	}
+	exe_path, err := windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1,  allocator)
+	if err != nil {
+		return "", err
+	}
+	return exe_path, nil
+}
+
 @(private)
 @(private)
 _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
 _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()