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

This commit is contained in:
flysand7
2024-07-15 07:27:44 +11:00
parent 8f4755532e
commit 4eca60946c
2 changed files with 252 additions and 64 deletions
+39 -10
View File
@@ -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 **Note**: The resulting information may or may contain the fields specified
selected fields. Please check the `fields` field of the `Process_Info` by the `selection` parameter. Always check whether the returned
struct to see if the struct contains the desired fields **before** checking `Process_Info` struct has the required fields before checking the error code
the error code returned by this function. returned by this function.
*/ */
process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
return _process_info(pid, selection, allocator) 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 **Note**: The resulting information may or may contain the fields specified
fields. Check the `fields` field of the `Process_Info` struct to see, if the by the `selection` parameter. Always check whether the returned
struct contains the selected fields **before** checking the error code `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.
+213 -54
View File
@@ -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) entry, entry_err := _process_entry_by_pid(info.pid)
if snap == windows.INVALID_HANDLE_VALUE { if entry_err != nil {
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 {
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( exe_path, exe_path_err := _process_exe_by_pid(pid, allocator)
windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, if exe_path_err != nil {
u32(pid), err = exe_path_err
)
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 {
return return
} }
info.fields |= {.Executable_Path} info.fields |= {.Executable_Path}
@@ -269,6 +235,161 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti
return return
} }
_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 {
free_process_info(info, allocator)
}
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 {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
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
}
}
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) { _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = cast(int) windows.GetCurrentProcessId() info.pid = cast(int) windows.GetCurrentProcessId()
defer if err != nil { defer if err != nil {
@@ -276,20 +397,8 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
} }
need_snapprocess := .PPid in selection || .Priority in selection need_snapprocess := .PPid in selection || .Priority in selection
if need_snapprocess { if need_snapprocess {
snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) entry, entry_err := _process_entry_by_pid(info.pid)
if snap == windows.INVALID_HANDLE_VALUE { if entry_err != nil {
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
}
status = windows.Process32NextW(snap, &entry)
}
if entry.th32ProcessID != u32(info.pid) {
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()