1

Тема: AHK: Получить переменные окружения процесса, зная его PID

В Total Commanderе есть свои внутренние переменные окружения, которые работают пока работает процесс Тотала. В частности внутренняя переменная %commander_path% интерпретируется Тоталом как путь к каталогу с главным исполняющим файлом (то есть путь к корневому каталогу Тотала).

В простейших случаях если скрипт запущен из-под Тотала - эту переменную можно узнать следующим образом:

#NoEnv
EnvGet, commander_path, commander_path

А вот если запущено несколько копий Тотала из разных каталогов, то получается, что в каждом из них - своя собственная переменная %commander_path%. AHK-скрипт запущен из-под одной из копий программы (или вообще не из-под Тотала). Нужно работать с активным окном Тотала. Мы можем узнать PID процесса активного окна:
WinGet, TCPID, PID, A

Как теперь, зная PID, получить переменные окружения этого процесса?

2

Re: AHK: Получить переменные окружения процесса, зная его PID

Если из самого скрипта, то, полагаю, так же:

EnvGet, commander_path, commander_path
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

3

Re: AHK: Получить переменные окружения процесса, зная его PID

teadrinker, а если запущено несколько копий ТС из разных каталогов, чей commander_path окажется в переменной?

Если интересует именно эта внутренняя переменная ТС, то можно найти каталог, откуда запущен активный процесс - такая тема была на форуме, если не ошибаюсь.

4

Re: AHK: Получить переменные окружения процесса, зная его PID

У меня не установлен ТК, не могу проверить, но почему бы в скрипте не быть переменной именно той копии ТК, из которой он запущен?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

5

Re: AHK: Получить переменные окружения процесса, зная его PID

Кажется, ты невнимательно прочитал задачу. Нужна переменная процесса активного окна ТС, независимо от того, как запущен скрипт.

6

Re: AHK: Получить переменные окружения процесса, зная его PID

А, ну наверно, только не переменная, а

Aslan пишет:

Как теперь, зная PID, получить переменные окружения этого процесса?

Если нужно узнать путь к файлу образа по PID, то это не проблема.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

7

Re: AHK: Получить переменные окружения процесса, зная его PID

внутренняя переменная %commander_path% интерпретируется Тоталом как путь к каталогу с главным исполняющим файлом (то есть путь к корневому каталогу Тотала).

Чем тут просто WinGet ProcessPath неподходит?


1::
    WinGet, WinProcessPath, ProcessPath, A
    Loop, %WinProcessPath%
        WinProcessPath = %A_LoopFileLongPath%
    SplitPath, WinProcessPath, , OutDir
    MsgBox % OutDir
    Return
По вопросам возмездной помощи пишите в личку
E-Mail: serzh82saratov@mail.ru
OS: Win7x64, AutoHotkey_L v1.1.24.05 (Unicode 32-bit).

8 (изменено: Aslan, 2014-06-16 23:06:56)

Re: AHK: Получить переменные окружения процесса, зная его PID

Интересовал вопрос в целом, потому обобщил и написал "переменных" (приведя в качестве наиболее простого примера commander_path в Тотале).
Но похоже, что простого решения нет. Тут несколько ссылок на реализацию на C, но чересчур сложно для моего понимания.

Конкретно сейчас интересуют переменные commander_path (путь к каталогу активной копии запущенного Тотала) и commander_ini (путь к файлу конфигурации Тотала, который может быть вообще в любом месте; окольными путями методом перебора его можно определить, но хотелось более изящного решения - считать прямо из самого процесса).

serzh82saratov
Спасибо.

9

Re: AHK: Получить переменные окружения процесса, зная его PID

Если ini файл лежит не в папке ТС, а в другой директории, то путь к нему указывается в качестве параметра к исполняемому файлу. И что-то мне подсказывает, что получить командную строку произвольного процесса вполне по силам АНК.

10 (изменено: Flasher, 2014-06-17 13:10:48)

Re: AHK: Получить переменные окружения процесса, зная его PID

Irbis, запуск ТС с настройками определённого конфига через ключ /i= - только частный случай.
Есть ещё варианты (1, 2).

11 (изменено: YMP, 2014-06-17 20:00:58)

Re: AHK: Получить переменные окружения процесса, зная его PID

Почитав ссылки, удалось собрать рабочий код, но с ограничениями. Если битности скрипта и процесса совпадают, всё работает. Если битность процесса выше, не работает, т.к. NtQueryInformationProcess вместо адреса структуры PEB возвращает 0. Если битность процесса ниже, чем у скрипта, возвращённый адрес отличается от реального на целое число страниц памяти, чаще всего на 0x1000 байт в большую сторону. Сейчас в коде делается такая поправка, но иногда разница другая, так что тут как повезёт. Не знаю пока, в чём причина этого и можно ли как-то обойти.


MsgBox, % PsGetEnv("AutoHotkey.exe", "Temp")    ; Переменная.
MsgBox, % PsGetEnv("AutoHotkey.exe")            ; Весь список.

; Переменная активного окна.
F10::
    WinGet, PID, PID, A
    MsgBox, % PsGetEnv(PID, "Path")
Return


; ============================= Функция ===================================

PsGetEnv(Process, Var = 0)
{
    Static cbPBI := A_PtrSize * 6, PBI  ; PROCESS_BASIC_INFORMATION
    Static cbMBI := A_PtrSize * 7, MBI  ; MEMORY_BASIC_INFORMATION
    Static PROCESS_VM_READ := 0x10, PROCESS_QUERY_INFORMATION := 0x400
    Static STATUS_SUCCESS := 0, ProcessBasicInformation := 0
    Static cbPES_max := 0x4000
    If (!PBI) {
        VarSetCapacity(PBI, cbPBI, 0), VarSetCapacity(MBI, cbMBI, 0)
    }
    Process, Exist, %Process%
    If !(PID := ErrorLevel)
        Return Error("Процесс не найден")
    If !(hProcess := DllCall("OpenProcess", "uint", PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
                                          , "int", 0, "uint", PID, "ptr"))
        Return Error("Ошибка OpenProcess")
    If (!A_Is64bitOS) {
        PsPtrSize := 4, pptr := "uint *"
    }
    Else {
        DllCall("IsWow64Process", "ptr", hProcess, "int *", Wow64Process)
        PsPtrSize := Wow64Process? 4:8
        pptr := Wow64Process? "uint *":"int64 *"
    }
    If (DllCall("ntdll.dll\NtQueryInformationProcess", "ptr", hProcess, "int", ProcessBasicInformation
                , "ptr", &PBI, "uint", cbPBI, "uint *", ReturnLength, "uint") <> STATUS_SUCCESS)
        Return Error("Ошибка NtQueryInformationProcess", hProcess)

    pPEB := NumGet(PBI, A_PtrSize, "ptr")   ; PEB (Process Environment Block)

    If (A_PtrSize > PsPtrSize)
        pPEB -= 0x1000  ; Если скрипт x64, а процесс x86.

    ; RTL_USER_PROCESS_PARAMETERS
    If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", pPEB + PsPtrSize * 4
                                   , pptr, pRUPP, "ptr", PsPtrSize, "ptr *", BytesRead)
        Return Error("Ошибка ReadProcessMemory: pRUPP", hProcess)

    ; Process Environment Strings.
    If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", pRUPP + (PsPtrSize = 4? 0x48:0x80)
                                   , pptr, pPES, "ptr", PsPtrSize, "ptr *", BytesRead)
        Return Error("Ошибка ReadProcessMemory: pPES", hProcess)

    DllCall("VirtualQueryEx", "ptr", hProcess, "ptr", pPES, "ptr", &MBI, "ptr", cbMBI)
    cbRegion := NumGet(MBI, A_PtrSize * 3, "ptr")
    cbPES := ((pPES + cbRegion) & 0xFFFFFFFFFFFFF000) - pPES
    If (cbPES > cbPES_max)
        cbPES := cbPES_max
    VarSetCapacity(PES, cbPES, 0)

    If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", pPES, "ptr", &PES, "ptr", cbPES
                                   , "ptr *", BytesRead)
        Return Error("Ошибка ReadProcessMemory: PES", hProcess)

    DllCall("CloseHandle", "ptr", hProcess)

    Loop, % cbPES // 2
    {
        If ((char := NumGet(PES, (A_Index - 1) * 2, "uchar")) = 0) {
            If (prev = 0)
                Break
            NumPut(0xA, PES, (A_Index - 1) * 2, "uchar")
        }
        prev := char
    }
    EnvList := StrGet(&PES, "utf-16")
    If (!Var)
        Return EnvList
    RegExMatch(EnvList, "im`n)^" Var "=([^\n]+)$", Found)
    Return Found1
}

Error(Msg, hProcess = 0)
{
    If (hProcess)
        DllCall("CloseHandle", "ptr", hProcess)
    MsgBox, %Msg%
    Return False
}

12

Re: AHK: Получить переменные окружения процесса, зная его PID

Класс, тоже пробовал аналогично, удалось прочитать только =::=::\ smile. А что за число 0xFFFFFFFFFFFFF000 ?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

13

Re: AHK: Получить переменные окружения процесса, зная его PID

teadrinker пишет:

А что за число 0xFFFFFFFFFFFFF000 ?

Это чтобы обрезать до границы страницы памяти. Проблема тут в том, что не известен размер блока со списком переменных. Чтобы его как-то оценить и по крайней мере при считывании не залезть на невыделенную память, запрашиваю размер региона памяти, начинающегося со страницы, где находится адрес блока. Размер региона включает и часть страницы до адреса, поэтому и надо обрезать хвост после сложения. Регион может быть большим, т.к., видимо, используется для разных целей. Поэтому я задал ещё и максимальный размер считывания в cbPES_max.

14

Re: AHK: Получить переменные окружения процесса, зная его PID

Я пока не очень разобрался, а что определяет его размер (0xFFFFFFFFFFFFF000), и почему нули в конце?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

15

Re: AHK: Получить переменные окружения процесса, зная его PID

Размер — чтобы было 64-битное число, а нули как раз и отрезают то, что меньше страницы, там же оператор AND. Страница памяти — это 0x1000 байт.

Допустим, размер региона 4 страницы, адрес блока переменных где-то посреди первой. Мне нужно получить адрес конца 4-й страницы, чтобы вычесть из него адрес блока переменных и получить размер данных, которые можно считать, не выходя в невыделенную память (что вызвало бы ошибку). Я прибавляю к адресу блока размер 4-х страниц, при этом получается адрес уже за пределами региона. И вот то, что выходит за пределы, нужно отрезать по границе страницы.

16

Re: AHK: Получить переменные окружения процесса, зная его PID

Ага, спасибо, теперь ясно.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

17

Re: AHK: Получить переменные окружения процесса, зная его PID

Почитал ещё по ссылкам и удалось сделать скрипт универсальным. Теперь работает при любых сочетаниях битностей скрипта и процесса. Попутно выяснилось, что обрезание через "& 0xFFFFFFFFFFFFF000" в АНК не работает, т.к. он ведь поддерживает целые только до 0x7FFFFFFFFFFFFFFF. И никакого предупреждения не выводится, что интересно. Убил из-за этого кучу времени, наблюдая непонятные глюки NtWow64QueryVirtualMemory64. Так что решил эту задачу сдвигом.


MsgBox, % PsGetEnv("AutoHotkey.exe", "Temp")    ; Переменная.
MsgBox, % PsGetEnv("AutoHotkey.exe")            ; Весь список.

; Переменная активного окна.
F10::
    WinGet, PID, PID, A
    MsgBox, % PsGetEnv(PID, "Path")
Return


; ============================= Функция ===================================

PsGetEnv(Process, Var = 0)
{
    Static PBI  ; PROCESS_BASIC_INFORMATION
    Static MBI, pMBI, cbMBI := 48  ; MEMORY_BASIC_INFORMATION
    Static PROCESS_VM_READ := 0x10, PROCESS_QUERY_INFORMATION := 0x400
    Static STATUS_SUCCESS := 0, cbPES_max := 0x4000 ; Макс. размер блока переменных.
    If (!PBI) {
        VarSetCapacity(PBI, 48), VarSetCapacity(MBI, cbMBI + 16)
        ; Выравнять по границе 16 байт для NtWow64QueryVirtualMemory64.
        pMBI := ((&MBI + 16) >> 4) << 4
    }
    Process, Exist, %Process%
    If !(PID := ErrorLevel)
        Return Error("Процесс не найден")
    If !(hProcess := DllCall("OpenProcess", "uint", PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
                                          , "int", 0, "uint", PID, "ptr"))
        Return Error("Ошибка OpenProcess")
    If (A_Is64bitOS)
        DllCall("IsWow64Process", "ptr", hProcess, "int *", Wow64Process)
    If (!A_Is64bitOS || Wow64Process)   ; Процесс 32-битный.
        PsPtrSize := 4, pptr := "uint *", OffsetRUPP := 16, OffsetPES := 0x48
    Else                                ; Процесс 64-битный.
        PsPtrSize := 8, pptr := "int64 *", OffsetRUPP := 32, OffsetPES := 0x80
    If (A_PtrSize < PsPtrSize) {    ; Скрипт х32, процесс х64.
        InfoClass := 0, cbPBI := 48, OffsetPEB := 8
        ;;; PROCESS_BASIC_INFORMATION (PBI)
        If (DllCall("ntdll.dll\NtWow64QueryInformationProcess64"
                , "ptr", hProcess, "int", InfoClass, "ptr", &PBI, "uint", cbPBI
                , "int64 *", ReturnLength) <> STATUS_SUCCESS)
            Return Error("Ошибка NtWow64QueryInformationProcess64", hProcess)
        ;;; Process Environment Block (PEB)
        pPEB := NumGet(PBI, OffsetPEB, "int64")
        ;;; RTL_USER_PROCESS_PARAMETERS (RUPP)
        If (DllCall("ntdll.dll\NtWow64ReadVirtualMemory64"
                , "ptr", hProcess, "int64", pPEB + OffsetRUPP, "int64 *", pRUPP
                , "int64", 8, "int64 *", BytesRead) <> STATUS_SUCCESS)
            Return Error("Ошибка NtWow64ReadVirtualMemory64: pRUPP", hProcess)
        ;;; Process Environment Strings (PES)
        If (DllCall("ntdll.dll\NtWow64ReadVirtualMemory64"
                , "ptr", hProcess, "int64", pRUPP + OffsetPES, "int64 *", pPES
                , "int64", 8, "int64 *", BytesRead) <> STATUS_SUCCESS)
            Return Error("Ошибка NtWow64ReadVirtualMemory64: pPES", hProcess)
        ;;; Размер региона памяти, где находится PES.
        If (DllCall("ntdll.dll\NtWow64QueryVirtualMemory64"
                , "ptr", hProcess, "int64", pPES, "uint", 0, "ptr", pMBI
                , "int64", cbMBI, "int64 *", ReturnLength) <> STATUS_SUCCESS)
            Return Error("Ошибка NtWow64QueryVirtualMemory64", hProcess)
        cbRegion := NumGet(pMBI+0, 24, "int64")
    }
    Else {
        If (A_PtrSize > PsPtrSize)  ; Скрипт х64, процесс х32.
            InfoClass := 26, cbPBI := 8, OffsetPEB := 0
        Else
            InfoClass := 0, cbPBI := A_PtrSize * 6, OffsetPEB := A_PtrSize
        ;;; PROCESS_BASIC_INFORMATION (PEB)
        If (DllCall("ntdll.dll\NtQueryInformationProcess"
                , "ptr", hProcess, "int", InfoClass, "ptr", &PBI, "uint", cbPBI
                , "uint *", ReturnLength) <> STATUS_SUCCESS)
        Return Error("Ошибка NtQueryInformationProcess", hProcess)
        ;;; Process Environment Block (PEB)
        pPEB := NumGet(PBI, OffsetPEB, "ptr")
        ;;; RTL_USER_PROCESS_PARAMETERS (RUPP)
        If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", pPEB + OffsetRUPP
                                       , pptr, pRUPP, "ptr", PsPtrSize, "ptr *", BytesRead)
            Return Error("Ошибка ReadProcessMemory: pRUPP", hProcess)
        ;;; Process Environment Strings (PES)
        If !DllCall("ReadProcessMemory", "ptr", hProcess, "ptr", pRUPP + OffsetPES
                                       , pptr, pPES, "ptr", PsPtrSize, "ptr *", BytesRead)
            Return Error("Ошибка ReadProcessMemory: pPES", hProcess)
        ;;; Размер региона памяти, где находится PES.
        If !DllCall("VirtualQueryEx", "ptr", hProcess, "ptr", pPES, "ptr", pMBI
                                    , "ptr", cbMBI, "int64")
            Return Error("Ошибка VirtualQueryEx", hProcess)
        cbRegion := NumGet(pMBI+0, A_PtrSize * 3, "ptr")
    }
    ;;; Размер от начала блока PES до конца региона.
    cbPES := (((pPES + cbRegion) >> 12) << 12) - pPES
    If (cbPES > cbPES_max) ; Если размер большой, ограничить разумной величиной.
        cbPES := cbPES_max
    VarSetCapacity(PES, cbPES, 0)
    If (A_PtrSize < PsPtrSize) {
        If (DllCall("ntdll.dll\NtWow64ReadVirtualMemory64"
                , "ptr", hProcess, "int64", pPES, "ptr", &PES
                , "int64", cbPES, "int64 *", ReturnLength, "uint") <> STATUS_SUCCESS)
            Return Error("Ошибка NtWow64ReadVirtualMemory64: PES", hProcess)
    }
    Else {
        If !DllCall("ReadProcessMemory"
                , "ptr", hProcess, "ptr", pPES, "ptr", &PES
                , "ptr", cbPES, "ptr *", BytesRead)
            Return Error("Ошибка ReadProcessMemory: PES", hProcess)
    }
    DllCall("CloseHandle", "ptr", hProcess)

    Loop, % cbPES // 2  ; Строки в блоке разделены нулями, на конце два нуля.
    {                   ; Заменяем нули на переводы строки, кроме последних.
        If ((char := NumGet(PES, (A_Index - 1) * 2, "uchar")) = 0) {
            If (prev = 0)
                Break
            NumPut(0xA, PES, (A_Index - 1) * 2, "uchar")
        }
        prev := char
    }
    EnvList := StrGet(&PES, "utf-16")
    If (!Var)
        Return EnvList
    RegExMatch(EnvList, "im`n)^" Var "=([^\n]+)$", Found)
    Return Found1
}

Error(Msg, hProcess = 0)
{
    If (hProcess)
        DllCall("CloseHandle", "ptr", hProcess)
    MsgBox, %Msg%
    Return False
}

18

Re: AHK: Получить переменные окружения процесса, зная его PID

Отлично, можно и увековечить.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

19

Re: AHK: Получить переменные окружения процесса, зная его PID

Спасибо за реализацию. Действительно скрипту место в Коллекции.

20

Re: AHK: Получить переменные окружения процесса, зная его PID

Да будет так. Поместил.

21

Re: AHK: Получить переменные окружения процесса, зная его PID

YMP, писал тут код на основе твоего (второй вариант). Не пойму, как в структуре RTL_USER_PROCESS_PARAMETERS ты посчитал сдвиг OffsetPES := 0x48 для 32 и OffsetPES := 0x80 для 64? Я только методом тыка определил.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

22

Re: AHK: Получить переменные окружения процесса, зная его PID

UNICODE_STRING — это структура:


typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} U

Занимает 8 и 16 байт соответственно, потому что в х64 нужно делать выравнивание каждого типа по его естественной границе, т.е. указатель должен быть по смещению 8. Кроме того такое выравнивание должно быть здесь:


  PVOID                   ConsoleHandle;
  ULONG                   ConsoleFlags;
  HANDLE                  StdInputHandle;

Т.е. к ULONG добавляется ещё 4 байта, чтобы HANDLE был на смещении, кратном 8.

23

Re: AHK: Получить переменные окружения процесса, зная его PID

YMP пишет:

UNICODE_STRING — это структура

А, ясно, спасибо, я думал, это просто пойнтер.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

24

Re: AHK: Получить переменные окружения процесса, зная его PID

teadrinker
Вот тут структуры явно обозначены: http://www.nirsoft.net/kernel_struct/vi … ETERS.html.

25

Re: AHK: Получить переменные окружения процесса, зная его PID

Кстати, в Win10 функция ntdll.dll\NtWow64QueryVirtualMemory64 не существует, так что мой код уже не работает, если скрипт 32 бита, а процесс 64. Функции чтения и записи пока что присутствуют. NtWow64QueryInformationProcess64 тоже есть.