Чи можу я надіслати ctrl-C (SIGINT) до програми в Windows?


91

Я (в минулому) написав крос-платформний (Windows / Unix) додатки , які при запуску з командного рядка, оброблюваних користувач надрукував Ctrl- Cпоєднання таким же чином (тобто припинити застосування чисто).

Чи можливо в Windows надіслати Ctrl- C/ SIGINT / еквівалент процесу з іншого (не пов’язаного) процесу з проханням про його чистое завершення (даючи йому можливість привести в порядок ресурси тощо)?


1
Мені було цікаво надіслати Ctrl-C до сервісних процесів Java лише для отримання дампів потоків. Виявляється , що jstackможе бути надійно використовуватися замість цього для цього конкретного питання: stackoverflow.com/a/47723393/603516
Вадиму

Відповіді:


31

Найближче, до чого я прийшов, - це програма сторонніх розробників SendSignal . Автор перераховує вихідний код та виконуваний файл. Я перевірив, що він працює під 64-розрядними вікнами (працює як 32-розрядна програма, вбиваючи іншу 32-розрядну програму), але я не з'ясував, як вставляти код у програму Windows (або 32-розрядну) або 64-розрядна).

Як це працює:

Після довгих розкопок у налагоджувачі я виявив, що точкою входу, яка насправді виконує поведінку, пов’язану з таким сигналом, як ctrl-break, є kernel32! CtrlRoutine. Функція мала той самий прототип, що і ThreadProc, тому її можна використовувати з CreateRemoteThread безпосередньо, без необхідності вводити код. Однак це не експортований символ! Він знаходиться за різними адресами (і навіть має різні назви) в різних версіях Windows. Що робити?

Ось рішення, яке я нарешті придумав. Я встановлюю консольний обробник ctrl для мого додатка, а потім генерую сигнал ctrl-break для мого додатка. Коли мій обробник викликається, я озираюсь у верх стека, щоб дізнатися параметри, передані kernel32! BaseThreadStart. Я захоплюю перший параметр, який є бажаною початковою адресою потоку, яка є адресою kernel32! CtrlRoutine. Потім я повертаюся від свого обробника, вказуючи, що я обробив сигнал, і мою програму не слід припиняти. Повернувшись до основного потоку, я чекаю, поки адреса kernel32! CtrlRoutine буде отримано. Отримавши його, я створюю віддалений потік у цільовому процесі із виявленою початковою адресою. Це призводить до того, що обробники ctrl у цільовому процесі оцінюються так, ніби було натиснуто ctrl-break!

Приємно те, що це впливає лише на цільовий процес, і будь-який процес (навіть процес із вікнами) може бути націлений. Один мінус полягає в тому, що мій маленький додаток не можна використовувати в пакетному файлі, оскільки він вб’є його, коли надсилає подію ctrl-break, щоб виявити адресу kernel32! CtrlRoutine.

(Передуйте цьому, startякщо запускаєте його у пакетному файлі.)


2
+1 - зв’язаний код використовує Ctrl-Break, а не Ctrl-C, але на вигляд (дивлячись на документацію для GenerateConsoleCtrlEvent) може бути адаптований для Ctrl-C.
Метью Мердок,

8
Існує насправді функція, яка робить це для вас, ха-ха, "GenerateConsoleCtrlEvent". Див .: msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx Див. Також мою відповідь тут: serverfault.com/a/501045/10023
Трійко

1
Посилання на Github, пов'язане з відповіддю вище: github.com/walware/statet/tree/master/…
meawoppl

3
Це викликає занепокоєння, що Windows старий, як і є, і тим не менше, Microsoft не створила механізм для консольного додатка A для прослуховування сигналу від консольного додатка B, щоб консольний додаток B міг сказати консольному додатку A чисте вимкнення. У будь-якому випадку, відхиляючи MS, я спробував SendSignal і отримав "CreateRemoteThread не вдалося з 0x00000005". Будь-які інші ідеї про те, як кодувати програму А для прослуховування сигналу (будь-який зручний сигнал), і як тоді подавати його?
Девід І. Макінтош,

3
@ DavidI.McIntosh, схоже, ви хочете створити іменовану подію в обох процесах; тоді ви зможете сигналізувати одне від іншого. Перевірте документацію для CreateEvent
arolson101

58

Я провів деякі дослідження навколо цієї теми, які виявились більш популярними, ніж я передбачав. Відповідь КіндДрагона стала одним із ключових моментів.

Я написав довший допис у блозі на цю тему і створив робочу демонстраційну програму, яка демонструє використання цього типу системи для закриття програми командного рядка в декількох приємних модах. У цій публікації також перелічені зовнішні посилання, які я використав у своєму дослідженні.

Коротше кажучи, ці демо-програми роблять наступне:

  • Запустіть програму з видимим вікном за допомогою .Net, приховайте за допомогою pinvoke, запустіть протягом 6 секунд, покажіть за допомогою pinvoke, зупиніть за допомогою .Net.
  • Запустіть програму без вікна за допомогою .Net, запустіть протягом 6 секунд, зупиніть, прикріпивши консоль та видавши ConsoleCtrlEvent

Редагувати: Змінене рішення від KindDragon для тих, хто цікавиться кодом тут і зараз. Якщо ви плануєте запустити інші програми після зупинки першої, вам слід увімкнути обробку Ctrl-C, інакше наступний процес успадкує батьківський відключений стан і не відповість на Ctrl-C.

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);

delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
  CTRL_C_EVENT = 0,
  CTRL_BREAK_EVENT,
  CTRL_CLOSE_EVENT,
  CTRL_LOGOFF_EVENT = 5,
  CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public void StopProgram(Process proc)
{
  //This does not require the console window to be visible.
  if (AttachConsole((uint)proc.Id))
  {
    // Disable Ctrl-C handling for our program
    SetConsoleCtrlHandler(null, true); 
    GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

    //Moved this command up on suggestion from Timothy Jannace (see comments below)
    FreeConsole();

    // Must wait here. If we don't and re-enable Ctrl-C
    // handling below too fast, we might terminate ourselves.
    proc.WaitForExit(2000);

    //Re-enable Ctrl-C handling or any subsequently started
    //programs will inherit the disabled state.
    SetConsoleCtrlHandler(null, false); 
  }
}

Крім того, сплануйте рішення на випадок непередбачених ситуацій, якщо AttachConsole()або надісланий сигнал не спрацює, наприклад, сплячий, то це:

if (!proc.HasExited)
{
  try
  {
    proc.Kill();
  }
  catch (InvalidOperationException e){}
}

4
Натхненний цим, я створив "автономну" програму cpp, яка це робить: gist.github.com/rdp/f51fb274d69c5c31b6be на випадок, якщо це буде корисно. І так, цей метод, мабуть, може надіслати ctrl + c або ctrl + break до "будь-якого" pid.
rogerdpack

Відсутній один із імпорту DLL "SetConsoleCtrlHandler", але це працює.
Дерф Скрен

відмінна посада. відмінний блог. Я б запропонував закінчити функцію StopProgramWithInvisibleWindowUsingPinvoke у вашій експериментальній програмі. Я майже відмовився від думки, думаючи, що ти не знайшов рішення
Вільмер Сент

Щоб уникнути, wait()я видалив Повторно активувати Ctrl-C ( SetConsoleCtrlHandler(null, false);). Я провів кілька тестів (кілька викликів, також щодо вже завершених процесів), і я не виявив жодного побічного ефекту (ще?).
56ка

FreeConsole слід викликати відразу після надсилання GenerateConsoleCtrlEvent. Поки його не буде викликано, ваша програма буде приєднана до іншої консолі. Якщо іншу консоль буде вбито до завершення закриття, ваша програма також буде припинена!
Тімоті Джаннас

17

Здається, я трохи запізнився з цим питанням, але все одно напишу щось для тих, хто має таку ж проблему. Це та сама відповідь, яку я дав на це запитання.

Моя проблема полягала в тому, що я хотів би, щоб моя програма була програмою графічного інтерфейсу, але виконувані процеси повинні виконуватися у фоновому режимі без будь-якого вікна інтерактивної консолі. Я думаю, що це рішення також має працювати, коли батьківський процес є консольним процесом. Можливо, вам доведеться видалити прапор "CREATE_NO_WINDOW".

Мені вдалося вирішити це за допомогою GenerateConsoleCtrlEvent () за допомогою програми-обгортки. Хитра частина полягає лише в тому, що документація насправді не зовсім зрозуміла, як саме її можна використовувати та підводні камені.

Моє рішення базується на тому, що описано тут . Але це насправді не пояснило всіх деталей і з помилкою, тому ось деталі про те, як змусити його працювати.

Створіть нову додаткову програму "Helper.exe". Ця програма розміщуватиметься між вашою програмою (батьківською) та дочірнім процесом, який ви хочете мати можливість закрити. Це також створить власне дочірній процес. У вас повинен бути цей процес "середньої людини", інакше GenerateConsoleCtrlEvent () не вдасться.

Використовуйте якийсь механізм IPC, щоб повідомляти від батьків до допоміжного процесу, що помічник повинен закрити дочірній процес. Коли помічник отримує цю подію, він викликає "GenerateConsoleCtrlEvent (CTRL_BREAK, 0)", який закриває себе та дочірній процес. Я сам використовував для цього об’єкт події, який батько завершує, коли хоче скасувати дочірній процес.

Щоб створити свій Helper.exe, створіть його за допомогою CREATE_NO_WINDOW та CREATE_NEW_PROCESS_GROUP. І під час створення дочірнього процесу створіть його без прапорів (0), що означає, що він буде отримувати консоль від свого батька. Якщо цього не зробити, це призведе до ігнорування події.

Дуже важливо, щоб кожен крок робився таким чином. Я пробував всілякі різновиди комбінацій, але ця комбінація єдина, яка працює. Ви не можете надіслати подію CTRL_C. Це поверне успіх, але процес буде ігнорувати. CTRL_BREAK - це єдиний, який працює. Це насправді не має значення, оскільки в кінці вони обидва зателефонують до ExitProcess ().

Ви також не можете викликати GenerateConsoleCtrlEvent () з ідентифікованим груповим процесом ідентифікатором дочірнього процесу, що дозволяє допоміжному процесу продовжувати жити. Це теж не вдасться.

Я витратив цілий день, намагаючись це зробити. Це рішення працює для мене, але якщо хтось має щось ще додати, будь ласка. Я обходив всю мережу, знаходячи багато людей з подібними проблемами, але не маючи чіткого рішення проблеми. Принцип роботи GenerateConsoleCtrlEvent () також трохи дивний, тому, якщо хтось знає більше деталей про нього, будь ласка, поділіться ним.


6
Дякую за рішення! Це ще один приклад того, що API Windows є таким жахливим безладом.
vitaut

8

Редагувати:

Для графічного інтерфейсу користувача "звичайним" способом впоратись із цим під час розробки Windows було б надіслати повідомлення WM_CLOSE в головне вікно процесу.

Для консольної програми вам потрібно використовувати SetConsoleCtrlHandler, щоб додати файл CTRL_C_EVENT.

Якщо програма не відповідає цьому, ви можете зателефонувати TerminateProcess .


Я хочу зробити це в програмі командного рядка (без вікон). TerminateProcess - це механізм примусового вбивства (подібний до SIGKILL), тому не надає додатку, що завершує роботу, можливість очищення.
Метью Мердок,

@Matthew Murdoch: оновлено, щоб включити інформацію про те, як це робити в консольному додатку. Ви також можете зафіксувати інші події, зокрема вимкнення вікон, закриту консоль тощо за допомогою того ж механізму. Для отримання детальної інформації див. MSDN.
Рід Копсі,

@Reed Copsey: Але я хотів би розпочати це з окремого процесу. Я подивився GenerateConsoleCtrlEvent (на який посилається SetConsoleCtrlHandler), але, схоже, це працює лише в тому випадку, якщо процес, що завершується, знаходиться в тій самій "групі процесів", що і процес, що робить завершення ... Будь-які ідеї?
Метью Мердок,

1
Метью: Єдиною ідеєю, яка б у мене виникла, було б знайти процес, знайти його батьківським (консоль), отримати головний дескриптор головного вікна (консоль) і використати SendKeys для надсилання Ctrl + C безпосередньо до нього. Це має призвести до того, що ваш процес консолі отримає CTRL_C_EVENT. Хоча я ще не пробував - не впевнений, наскільки добре це спрацює.
Рід Копсі,

Ось еквівалент С ++ для SendKeys stackoverflow.com/questions/6838363/… див. Також stackoverflow.com/questions/10407769/…, хоча, мабуть, це не гарантовано працює навіть тоді ...
rogerdpack,

7

Якось GenerateConsoleCtrlEvent()повернути помилку, якщо ви викликаєте її для іншого процесу, але ви можете підключити до іншої консольної програми та надіслати подію до всіх дочірніх процесів.

void SendControlC(int pid)
{
    AttachConsole(pid); // attach to process console
    SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app
    GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event
}

і як повернути контроль? а потім я посилаю control-c, стопар програми, але я нічого не можу зробити своїми руками.
Ярослав Л.

Куди повернути контроль?
KindDragon

Я думаю, ви зателефонуєте SetConsoleCtrlHandler (NULL, FALSE), щоб видалити ваш обробник, перегляньте різні відповіді.
rogerdpack

6

Ось код, який я використовую у своєму додатку C ++.

Позитивні моменти:

  • Працює з консольного додатка
  • Працює від служби Windows
  • Затримка не потрібна
  • Не закриває поточний додаток

Негативні моменти:

  • Основна консоль втрачена і створена нова (див. FreeConsole )
  • Перемикання консолі дає дивні результати ...

// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
{
    bool success = false;
    DWORD thisConsoleId = GetCurrentProcessId();
    // Leave current console if it exists
    // (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
    bool consoleDetached = (FreeConsole() != FALSE);

    if (AttachConsole(dwProcessId) != FALSE)
    {
        // Add a fake Ctrl-C handler for avoid instant kill is this console
        // WARNING: do not revert it or current program will be also killed
        SetConsoleCtrlHandler(nullptr, true);
        success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
        FreeConsole();
    }

    if (consoleDetached)
    {
        // Create a new console if previous was deleted by OS
        if (AttachConsole(thisConsoleId) == FALSE)
        {
            int errorCode = GetLastError();
            if (errorCode == 31) // 31=ERROR_GEN_FAILURE
            {
                AllocConsole();
            }
        }
    }
    return success;
}

Приклад використання:

DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
    cout << "Signal sent" << endl;
}

4
        void SendSIGINT( HANDLE hProcess )
        {
            DWORD pid = GetProcessId(hProcess);
            FreeConsole();
            if (AttachConsole(pid))
            {
                // Disable Ctrl-C handling for our program
                SetConsoleCtrlHandler(NULL, true);

                GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT

                //Re-enable Ctrl-C handling or any subsequently started
                //programs will inherit the disabled state.
                SetConsoleCtrlHandler(NULL, false);

                WaitForSingleObject(hProcess, 10000);
            }
        }

2

Це повинно бути кристально чистим, оскільки на даний момент це не так. Існує модифікована та скомпільована версія SendSignal для надсилання Ctrl-C (за замовчуванням вона надсилає лише Ctrl + Break). Ось декілька бінарних файлів:

(2014-3-7): Я створив як 32-розрядну, так і 64-розрядну версію за допомогою Ctrl-C, вона називається SendSignalCtrlC.exe, і ви можете завантажити її за адресою: https://dl.dropboxusercontent.com/u/49065779/ sendignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe - Юрай Міхалак

Я також віддзеркалив ці файли на всякий випадок:
32-розрядна версія: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-розрядна версія: https://www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Застереження: я не створював ці файли. Складених оригінальних файлів не вносилось жодних змін. Єдиною протестованою платформою є 64-розрядна Windows 7. Рекомендується адаптувати джерело, доступне за адресою http://www.latenighthacking.com/projects/2003/sendSignal/, та скомпілювати його самостійно.


2

У Java використовується JNA з бібліотекою Kernel32.dll, подібно до рішення C ++. Запускає основний метод CtrlCSender як процес, який просто отримує консоль процесу для надсилання події Ctrl + C і генерує подію. Оскільки вона працює окремо без консолі, подію Ctrl + C не потрібно вимикати та вмикати знову.

CtrlCSender.java - Заснований на Nemo1024 та KindDragon .

Враховуючи відомий ідентифікатор процесу, ця безконсольна програма приєднає консоль цільового процесу та згенерує на ній подію CTRL + C.

import com.sun.jna.platform.win32.Kernel32;    

public class CtrlCSender {

    public static void main(String args[]) {
        int processId = Integer.parseInt(args[0]);
        Kernel32.INSTANCE.AttachConsole(processId);
        Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0);
    }
}

Основна програма - запускає CtrlCSender як окремий безконсольний процес

ProcessBuilder pb = new ProcessBuilder();
pb.command("javaw", "-cp", System.getProperty("java.class.path", "."), CtrlCSender.class.getName(), processId);
pb.redirectErrorStream();
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process ctrlCProcess = pb.start();
ctrlCProcess.waitFor();

хм, якщо я запускаю це проти процесу PowerShell, процес залишається в живих. З іншого боку, що цікаво, запущений обробний процес на JVM справді збиває ВМ! Ви уявляєте, чому процес PowerShell не реагує на цю техніку?
Гроостав,


1

Рішення, яке я знайшов звідси , досить просте, якщо у вашому командному рядку є python 3.x. Спочатку збережіть файл (ctrl_c.py) із вмістом:

import ctypes
import sys

kernel = ctypes.windll.kernel32

pid = int(sys.argv[1])
kernel.FreeConsole()
kernel.AttachConsole(pid)
kernel.SetConsoleCtrlHandler(None, 1)
kernel.GenerateConsoleCtrlEvent(0, 0)
sys.exit(0)

Тоді телефонуйте:

python ctrl_c.py 12345

Якщо це не спрацює, рекомендую спробувати проект windows-kill: https://github.com/alirdn/windows-kill


0

Мій друг запропонував зовсім інший спосіб вирішення проблеми, і це спрацювало для мене. Використовуйте vbscript, як показано нижче. Він запускається та додається, дайте йому запуститися протягом 7 секунд і закрийте його, використовуючи ctrl + c .

'Приклад VBScript

Set WshShell = WScript.CreateObject("WScript.Shell")

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Це працює, якщо у додатку є вікно, яке ви можете AppActive (висунути на перший план).
rogerdpack

0

Я знайшов все це занадто складно і використовувати SendKeys для відправки CTRL- Cнатискання клавіші у вікні командного рядка (тобто cmd.exe вікно) в якості обхідного шляху.


0
// Send [CTRL-C] to interrupt a batch file running in a Command Prompt window, even if the Command Prompt window is not visible,
// without bringing the Command Prompt window into focus.
// [CTRL-C] will have an effect on the batch file, but not on the Command Prompt  window itself -- in other words,
// [CTRL-C] will not have the same visible effect on a Command Prompt window that isn't running a batch file at the moment
// as bringing a Command Prompt window that isn't running a batch file into focus and pressing [CTRL-C] on the keyboard.
ulong ulProcessId = 0UL;
// hwC = Find Command Prompt window HWND
GetWindowThreadProcessId (hwC, (LPDWORD) &ulProcessId);
AttachConsole ((DWORD) ulProcessId);
SetConsoleCtrlHandler (NULL, TRUE);
GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0UL);
SetConsoleCtrlHandler (NULL, FALSE);
FreeConsole ();

0

SIGINT можна надіслати програмі за допомогою windows-kill , за допомогою синтаксису windows-kill -SIGINT PID, де PIDйого можна отримати за допомогою списку ps ps Microsoft .

Що стосується ловлі SIGINT, якщо ваша програма знаходиться на Python, ви можете реалізувати обробку / ловлю SIGINT, як у цьому рішенні .


-1

Виходячи з ідентифікатора процесу, ми можемо надіслати сигнал обробці, щоб закінчити його примусово або витончено, або будь-який інший сигнал.

Перелічити весь процес:

C:\>tasklist

Щоб убити процес:

C:\>Taskkill /IM firefox.exe /F
or
C:\>Taskkill /PID 26356 /F

Подробиці:

http://tweaks.com/windows/39559/kill-processes-from-command-prompt/


Імовірно, це надсилає sigterm?
teknopaul

Завдання завдання не надсилає CTRL-C. Перевірте сигнали Windows.
Джозі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.