mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-16 16:01:26 -07:00
555 lines
15 KiB
Odin
555 lines
15 KiB
Odin
//+build windows
|
|
package os2
|
|
|
|
import "core:sys/windows"
|
|
import "core:strings"
|
|
import "base:runtime"
|
|
|
|
_Process_Handle :: windows.HANDLE
|
|
|
|
_exit :: proc "contextless" (code: int) -> ! {
|
|
windows.ExitProcess(u32(code))
|
|
}
|
|
|
|
_get_uid :: proc() -> int {
|
|
return -1
|
|
}
|
|
|
|
_get_euid :: proc() -> int {
|
|
return -1
|
|
}
|
|
|
|
_get_gid :: proc() -> int {
|
|
return -1
|
|
}
|
|
|
|
_get_egid :: proc() -> int {
|
|
return -1
|
|
}
|
|
|
|
_get_pid :: proc() -> int {
|
|
return cast(int) windows.GetCurrentProcessId()
|
|
}
|
|
|
|
_get_ppid :: proc() -> int {
|
|
our_pid := windows.GetCurrentProcessId()
|
|
snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
|
if snap == windows.INVALID_HANDLE_VALUE {
|
|
return -1
|
|
}
|
|
defer windows.CloseHandle(snap)
|
|
entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
|
|
status := windows.Process32FirstW(snap, &entry)
|
|
for status {
|
|
if entry.th32ProcessID == our_pid {
|
|
return cast(int) entry.th32ParentProcessID
|
|
}
|
|
status = windows.Process32NextW(snap, &entry)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
_process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) {
|
|
pid_list := make([dynamic]int, allocator)
|
|
snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
|
if snap == windows.INVALID_HANDLE_VALUE {
|
|
return pid_list[:], _get_platform_error()
|
|
}
|
|
entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) }
|
|
status := windows.Process32FirstW(snap, &entry)
|
|
for status {
|
|
append(&pid_list, cast(int) entry.th32ProcessID)
|
|
status = windows.Process32NextW(snap, &entry)
|
|
}
|
|
return pid_list[:], nil
|
|
}
|
|
|
|
_process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
|
|
info.pid = pid
|
|
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
|
|
need_process_handle := need_peb || .Username in selection
|
|
// Data obtained from process snapshots
|
|
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 {
|
|
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)
|
|
}
|
|
}
|
|
// 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 {
|
|
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 {
|
|
return
|
|
}
|
|
info.fields |= {.Executable_Path}
|
|
info.executable_path = exe_path
|
|
}
|
|
defer if .Executable_Path in info.fields && err != nil {
|
|
delete(info.executable_path, allocator)
|
|
}
|
|
ph := windows.INVALID_HANDLE_VALUE
|
|
if need_process_handle {
|
|
ph = windows.OpenProcess(
|
|
windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ,
|
|
false,
|
|
u32(pid),
|
|
)
|
|
if ph == windows.INVALID_HANDLE_VALUE {
|
|
err = _get_platform_error()
|
|
return
|
|
}
|
|
}
|
|
defer if ph != windows.INVALID_HANDLE_VALUE {
|
|
windows.CloseHandle(ph)
|
|
}
|
|
defer if .CWD in info.fields && err != nil {
|
|
delete(info.cwd, allocator)
|
|
}
|
|
defer if .Environment in info.fields && err != nil {
|
|
for s in info.environment {
|
|
delete(s, allocator)
|
|
}
|
|
delete(info.environment, allocator)
|
|
}
|
|
defer if .Command_Line in info.fields && err != nil {
|
|
delete(info.command_line, allocator)
|
|
}
|
|
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 {
|
|
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
|
|
}
|
|
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 .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()
|
|
need_snapprocess := .PPid in selection || .Priority in selection
|
|
if need_snapprocess {
|
|
snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
|
if snap == windows.INVALID_HANDLE_VALUE {
|
|
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
|
|
return
|
|
}
|
|
if .PPid in selection {
|
|
info.fields += {.PPid}
|
|
info.ppid = int(entry.th32ProcessID)
|
|
}
|
|
if .Priority in selection {
|
|
info.fields += {.Priority}
|
|
info.priority = int(entry.pcPriClassBase)
|
|
}
|
|
}
|
|
if .Executable_Path in selection {
|
|
exe_filename_w: [256]u16
|
|
path_len := windows.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
|
|
exe_filename, exe_filename_err := windows.utf16_to_utf8(exe_filename_w[:path_len], allocator)
|
|
if exe_filename_err != nil {
|
|
err = exe_filename_err
|
|
return
|
|
}
|
|
info.fields += {.Executable_Path}
|
|
info.executable_path = exe_filename
|
|
}
|
|
defer if .Executable_Path in selection && err != nil {
|
|
delete(info.executable_path, allocator)
|
|
}
|
|
if .Command_Line in selection {
|
|
command_line_w := windows.GetCommandLineW()
|
|
command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator)
|
|
if command_line_err != nil {
|
|
err = command_line_err
|
|
return
|
|
}
|
|
info.fields += {.Command_Line}
|
|
info.command_line = command_line
|
|
}
|
|
defer if .Command_Line in selection && err != nil {
|
|
delete(info.command_line, allocator)
|
|
}
|
|
if .Environment in selection {
|
|
env_block := windows.GetEnvironmentStringsW()
|
|
envs, envs_err := _parse_environment_block(env_block, allocator)
|
|
if envs_err != nil {
|
|
err = envs_err
|
|
return
|
|
}
|
|
info.fields += {.Environment}
|
|
info.environment = envs
|
|
}
|
|
defer if .Environment in selection && err != nil {
|
|
for s in info.environment {
|
|
delete(s, allocator)
|
|
}
|
|
delete(info.environment)
|
|
}
|
|
if .Username in selection {
|
|
process_handle := windows.GetCurrentProcess()
|
|
username, username_err := _get_process_user(process_handle, allocator)
|
|
if username_err != nil {
|
|
err = username_err
|
|
return
|
|
}
|
|
info.fields += {.Username}
|
|
info.username = username
|
|
}
|
|
defer if .Username in selection && err != nil {
|
|
delete(info.username)
|
|
}
|
|
err = nil
|
|
return
|
|
}
|
|
|
|
|
|
@(private)
|
|
_get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
|
|
TEMP_ALLOCATOR_GUARD()
|
|
token_handle: windows.HANDLE = ---
|
|
if !windows.OpenProcessToken(process_handle, windows.TOKEN_QUERY, &token_handle) {
|
|
err = _get_platform_error()
|
|
return
|
|
}
|
|
token_user_size: u32 = ---
|
|
if !windows.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
|
|
// Note(flysand): Make sure the buffer too small error comes out, and not any other error
|
|
err = _get_platform_error()
|
|
if v, ok := err.(Platform_Error); !ok || int(v) != 0x7a {
|
|
return
|
|
}
|
|
}
|
|
token_user := cast(^windows.TOKEN_USER) raw_data(make([]u8, token_user_size, temp_allocator()))
|
|
if !windows.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
|
|
err = _get_platform_error()
|
|
return
|
|
}
|
|
sid_type: windows.SID_NAME_USE = ---
|
|
username_w: [256]u16 = ---
|
|
domain_w: [256]u16 = ---
|
|
username_chrs: u32 = 256
|
|
domain_chrs: u32 = 256
|
|
if !windows.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
|
|
err = _get_platform_error()
|
|
return
|
|
}
|
|
username, username_err := windows.utf16_to_utf8(username_w[:username_chrs], temp_allocator())
|
|
if username_err != nil {
|
|
err = username_err
|
|
return
|
|
}
|
|
domain, domain_err := windows.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator())
|
|
if domain_err != nil {
|
|
err = domain_err
|
|
return
|
|
}
|
|
full_name, full_name_err := strings.concatenate([]string {domain, "\\", username}, allocator)
|
|
if full_name_err != nil {
|
|
err = full_name_err
|
|
return
|
|
}
|
|
return full_name, nil
|
|
}
|
|
|
|
@(private)
|
|
_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) {
|
|
zt_count := 0
|
|
for idx := 0; true; {
|
|
if block[idx] == 0x0000 {
|
|
zt_count += 1
|
|
if block[idx+1] == 0x0000 {
|
|
zt_count += 1
|
|
break
|
|
}
|
|
}
|
|
idx += 1
|
|
}
|
|
// Note(flysand): Each string in the environment block is terminated
|
|
// by a NUL character. In addition, the environment block itself is
|
|
// terminated by a NUL character. So the number of strings in the
|
|
// environment block is the number of NUL character minus the
|
|
// block terminator.
|
|
env_count := zt_count - 1
|
|
envs := make([]string, env_count, allocator)
|
|
env_idx := 0
|
|
last_idx := 0
|
|
idx := 0
|
|
for block[idx] != 0x0000 {
|
|
for block[idx] != 0x0000 {
|
|
idx += 1
|
|
}
|
|
env_w := block[last_idx:idx]
|
|
env, env_err := windows.utf16_to_utf8(env_w, allocator)
|
|
if env_err != nil {
|
|
return nil, env_err
|
|
}
|
|
envs[env_idx] = env
|
|
env_idx += 1
|
|
idx += 1
|
|
last_idx = idx
|
|
}
|
|
return envs, nil
|
|
}
|
|
|
|
|
|
@(private="file")
|
|
PROCESSINFOCLASS :: enum i32 {
|
|
ProcessBasicInformation = 0,
|
|
ProcessDebugPort = 7,
|
|
ProcessWow64Information = 26,
|
|
ProcessImageFileName = 27,
|
|
ProcessBreakOnTermination = 29,
|
|
ProcessTelemetryIdInformation = 64,
|
|
ProcessSubsystemInformation = 75,
|
|
}
|
|
|
|
@(private="file")
|
|
NtQueryInformationProcess_T :: #type proc (
|
|
ProcessHandle: windows.HANDLE,
|
|
ProcessInformationClass: PROCESSINFOCLASS,
|
|
ProcessInformation: rawptr,
|
|
ProcessInformationLength: u32,
|
|
ReturnLength: ^u32,
|
|
) -> u32
|
|
|
|
@(private="file")
|
|
PROCESS_BASIC_INFORMATION :: struct {
|
|
_: rawptr,
|
|
PebBaseAddress: ^PEB,
|
|
_: [2]rawptr,
|
|
UniqueProcessId: ^u32,
|
|
_: rawptr,
|
|
}
|
|
|
|
@(private="file")
|
|
PEB :: struct {
|
|
_: [2]u8,
|
|
BeingDebugged: u8,
|
|
_: [1]u8,
|
|
_: [2]rawptr,
|
|
Ldr: ^PEB_LDR_DATA,
|
|
ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS,
|
|
_: [104]u8,
|
|
_: [52]rawptr,
|
|
PostProcessInitRoutine: #type proc "stdcall" (),
|
|
_: [128]u8,
|
|
_: [1]rawptr,
|
|
SessionId: u32,
|
|
}
|
|
|
|
@(private="file")
|
|
PEB_LDR_DATA :: struct {
|
|
_: [8]u8,
|
|
_: [3]rawptr,
|
|
InMemoryOrderModuleList: LIST_ENTRY,
|
|
}
|
|
|
|
@(private="file")
|
|
RTL_USER_PROCESS_PARAMETERS :: struct {
|
|
MaximumLength: u32,
|
|
Length: u32,
|
|
Flags: u32,
|
|
DebugFlags: u32,
|
|
ConsoleHandle: rawptr,
|
|
ConsoleFlags: u32,
|
|
StdInputHandle: rawptr,
|
|
StdOutputHandle: rawptr,
|
|
StdErrorHandle: rawptr,
|
|
CurrentDirectoryPath: UNICODE_STRING,
|
|
CurrentDirectoryHandle: rawptr,
|
|
DllPath: UNICODE_STRING,
|
|
ImagePathName: UNICODE_STRING,
|
|
CommandLine: UNICODE_STRING,
|
|
Environment: rawptr,
|
|
StartingPositionLeft: u32,
|
|
StartingPositionTop: u32,
|
|
Width: u32,
|
|
Height: u32,
|
|
CharWidth: u32,
|
|
CharHeight: u32,
|
|
ConsoleTextAttributes: u32,
|
|
WindowFlags: u32,
|
|
ShowWindowFlags: u32,
|
|
WindowTitle: UNICODE_STRING,
|
|
DesktopName: UNICODE_STRING,
|
|
ShellInfo: UNICODE_STRING,
|
|
RuntimeData: UNICODE_STRING,
|
|
DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR,
|
|
EnvironmentSize: u32,
|
|
}
|
|
|
|
RTL_DRIVE_LETTER_CURDIR :: struct {
|
|
Flags: u16,
|
|
Length: u16,
|
|
TimeStamp: u32,
|
|
DosPath: UNICODE_STRING,
|
|
}
|
|
|
|
@(private="file")
|
|
UNICODE_STRING :: struct {
|
|
Length: u16,
|
|
MaximumLength: u16,
|
|
Buffer: [^]u16,
|
|
}
|
|
|
|
@(private="file")
|
|
LIST_ENTRY :: struct {
|
|
Flink: ^LIST_ENTRY,
|
|
Blink: ^LIST_ENTRY,
|
|
} |