Dapatkah saya mengirim ctrl-C (SIGINT) ke aplikasi di Windows?


91

Saya telah (di masa lalu) yang ditulis cross-platform (Windows / Unix) aplikasi yang, ketika mulai dari baris perintah, menangani-diketik pengguna Ctrl- Ckombinasi dengan cara yang sama (yaitu untuk mengakhiri aplikasi bersih).

Apakah mungkin pada Windows untuk mengirim Ctrl- C/ SIGINT / setara dengan proses dari proses lain (tidak terkait) untuk meminta agar dihentikan dengan bersih (memberinya kesempatan untuk merapikan sumber daya, dll.)?


1
Saya tertarik untuk mengirim Ctrl-C ke proses layanan java hanya untuk mendapatkan threaddumps. Tampaknya jstackdapat digunakan dengan andal sebagai gantinya untuk masalah khusus ini: stackoverflow.com/a/47723393/603516
Vadzim

Jawaban:


31

Solusi terdekat yang saya temukan adalah aplikasi pihak ketiga SendSignal . Penulis mencantumkan kode sumber dan yang dapat dieksekusi. Saya telah memverifikasi bahwa itu berfungsi di bawah jendela 64-bit (berjalan sebagai program 32-bit, mematikan program 32-bit lainnya), tetapi saya belum menemukan cara untuk menyematkan kode ke dalam program windows (baik 32-bit atau 64-bit).

Bagaimana itu bekerja:

Setelah banyak menggali di dalam debugger, saya menemukan bahwa titik masuk yang sebenarnya melakukan perilaku yang terkait dengan sinyal seperti ctrl-break adalah kernel32! CtrlRoutine. Fungsi tersebut memiliki prototipe yang sama dengan ThreadProc, sehingga dapat digunakan dengan CreateRemoteThread secara langsung, tanpa harus memasukkan kode. Namun, itu bukan simbol yang diekspor! Ada di alamat yang berbeda (dan bahkan memiliki nama yang berbeda) pada versi Windows yang berbeda. Apa yang harus dilakukan?

Inilah solusi yang akhirnya saya dapatkan. Saya memasang penangan ctrl konsol untuk aplikasi saya, lalu menghasilkan sinyal ctrl-break untuk aplikasi saya. Ketika penangan saya dipanggil, saya melihat kembali ke bagian atas tumpukan untuk mengetahui parameter yang diteruskan ke kernel32! BaseThreadStart. Saya mengambil parameter pertama, yang merupakan alamat awal utas yang diinginkan, yang merupakan alamat kernel32! CtrlRoutine. Kemudian saya kembali dari penangan saya, menunjukkan bahwa saya telah menangani sinyal dan aplikasi saya tidak boleh dihentikan. Kembali ke utas utama, saya menunggu sampai alamat kernel32! CtrlRoutine telah diambil. Setelah saya mendapatkannya, saya membuat utas jarak jauh dalam proses target dengan alamat awal yang ditemukan. Hal ini menyebabkan penangan ctrl dalam proses target dievaluasi seolah-olah ctrl-break telah ditekan!

Hal yang menyenangkan adalah hanya proses target yang terpengaruh, dan proses apa pun (bahkan proses berjendela) dapat ditargetkan. Satu kelemahannya adalah bahwa aplikasi kecil saya tidak dapat digunakan dalam file batch, karena itu akan mematikannya ketika mengirimkan event ctrl-break untuk menemukan alamat kernel32! CtrlRoutine.

(Awali dengan startjika menjalankannya dalam file batch.)


2
+1 - Kode yang ditautkan menggunakan Ctrl-Break daripada Ctrl-C tetapi di depannya (melihat dokumentasi untuk GenerateConsoleCtrlEvent) dapat diadaptasi untuk Ctrl-C.
Matthew Murdoch

8
Sebenarnya ada fungsi untuk melakukan ini untuk Anda, lol, "GenerateConsoleCtrlEvent". Lihat: msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx Lihat juga balasan saya di sini: serverfault.com/a/501045/10023
Triynko

1
Tautan Github terkait dengan jawaban di atas: github.com/walware/statet/tree/master/…
meawoppl

3
Ini mengejutkan pikiran bahwa Windows sudah setua itu, namun Microsoft belum menyediakan mekanisme untuk aplikasi konsol A untuk mendengarkan sinyal dari aplikasi konsol B sehingga aplikasi konsol B dapat memberi tahu aplikasi konsol A untuk dimatikan dengan bersih. Bagaimanapun, selain MS-bashing, saya telah mencoba SendSignal dan mendapatkan "CreateRemoteThread gagal dengan 0x00000005". Ada gagasan lain tentang cara membuat kode aplikasi A untuk mendengarkan sinyal (sinyal apa pun yang nyaman), dan bagaimana cara memberi sinyal?
David I.McIntosh

3
@ DavidI.McIntosh sepertinya Anda ingin membuat acara bernama di kedua proses; Anda kemudian akan dapat memberi sinyal satu sama lain. Periksa dokumentasi untuk
CreateEvent

58

Saya telah melakukan beberapa penelitian seputar topik ini, yang ternyata lebih populer dari yang saya perkirakan. Balasan KindDragon adalah salah satu poin penting.

Saya menulis posting blog yang lebih panjang tentang topik tersebut dan membuat program demo yang berfungsi, yang mendemonstrasikan penggunaan jenis sistem ini untuk menutup aplikasi baris perintah dalam beberapa mode yang bagus. Posting itu juga mencantumkan tautan eksternal yang saya gunakan dalam penelitian saya.

Singkatnya, program demo tersebut melakukan hal berikut:

  • Mulai program dengan jendela yang terlihat menggunakan .Net, sembunyikan dengan pinvoke, jalankan selama 6 detik, tampilkan dengan pinvoke, hentikan dengan .Net.
  • Mulai program tanpa jendela menggunakan .Net, jalankan selama 6 detik, hentikan dengan memasang konsol dan mengeluarkan ConsoleCtrlEvent

Sunting: Solusi yang diubah dari KindDragon untuk mereka yang tertarik dengan kode di sini dan sekarang. Jika Anda berencana untuk memulai program lain setelah menghentikan yang pertama, Anda harus mengaktifkan kembali penanganan Ctrl-C, jika tidak, proses selanjutnya akan mewarisi status nonaktif induknya dan tidak akan merespons 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); 
  }
}

Juga, rencanakan solusi darurat jika AttachConsole()atau sinyal yang dikirim gagal, misalnya tidur maka ini:

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

4
Terinspirasi oleh ini, saya membuat aplikasi cpp "mandiri" yang melakukannya: gist.github.com/rdp/f51fb274d69c5c31b6be seandainya berguna. Dan ya, metode ini dapat mengirim ctrl + c atau ctrl + break ke pid "any".
rogerdpack

Tidak ada salah satu impor DLL "SetConsoleCtrlHandler", tetapi ini berhasil.
Derf Skren

posting yang bagus. blog yang luar biasa. Saya menyarankan untuk menyelesaikan fungsi StopProgramWithInvisibleWindowUsingPinvoke dalam program percobaan Anda. Saya hampir meninggalkan gagasan itu karena mengira Anda belum menemukan solusi
Wilmer Saint

Untuk menghindari wait()saya menghapus Re-enable Ctrl-C ( SetConsoleCtrlHandler(null, false);). Saya melakukan beberapa tes (beberapa panggilan, juga pada proses yang sudah dihentikan) dan saya tidak menemukan efek samping (belum?).
56ka

FreeConsole harus dipanggil segera setelah mengirim GenerateConsoleCtrlEvent. Hingga dipanggil, aplikasi Anda akan dipasang ke konsol lain. Jika konsol lain dimatikan sebelum selesai ditutup, aplikasi Anda juga akan dihentikan!
Timothy Jannace

17

Saya kira saya agak terlambat dalam pertanyaan ini tetapi saya akan tetap menulis sesuatu untuk siapa pun yang memiliki masalah yang sama. Ini adalah jawaban yang sama seperti yang saya berikan untuk pertanyaan ini .

Masalah saya adalah bahwa saya ingin aplikasi saya menjadi aplikasi GUI tetapi proses yang dijalankan harus dijalankan di latar belakang tanpa jendela konsol interaktif yang terpasang. Saya pikir solusi ini juga harus berfungsi ketika proses induk adalah proses konsol. Anda mungkin harus menghapus bendera "CREATE_NO_WINDOW".

Saya berhasil menyelesaikan ini menggunakan GenerateConsoleCtrlEvent () dengan aplikasi pembungkus. Bagian yang sulit adalah bahwa dokumentasinya tidak begitu jelas tentang bagaimana tepatnya itu dapat digunakan dan jebakannya.

Solusi saya didasarkan pada apa yang dijelaskan di sini . Tapi itu juga tidak benar-benar menjelaskan semua detail dan dengan kesalahan, jadi inilah detail tentang cara membuatnya berfungsi.

Buat aplikasi pembantu baru "Helper.exe". Aplikasi ini akan berada di antara aplikasi Anda (induk) dan proses anak yang ingin Anda tutup. Ini juga akan menciptakan proses anak yang sebenarnya. Anda harus memiliki proses "orang tengah" ini atau GenerateConsoleCtrlEvent () akan gagal.

Gunakan beberapa jenis mekanisme IPC untuk berkomunikasi dari induk ke proses helper bahwa helper harus menutup proses anak. Saat helper mendapatkan acara ini, ia memanggil "GenerateConsoleCtrlEvent (CTRL_BREAK, 0)" yang menutup sendiri dan proses turunannya. Saya menggunakan objek acara untuk ini sendiri yang diselesaikan oleh orang tua ketika ingin membatalkan proses anak.

Untuk membuat Helper.exe Anda, buatlah dengan CREATE_NO_WINDOW dan CREATE_NEW_PROCESS_GROUP. Dan saat membuat proses anak, buatlah tanpa flag (0) yang berarti itu akan mendapatkan konsol dari induknya. Gagal melakukan ini akan menyebabkan acara diabaikan.

Sangat penting bahwa setiap langkah dilakukan seperti ini. Saya telah mencoba semua jenis kombinasi tetapi kombinasi ini adalah satu-satunya yang berhasil. Anda tidak dapat mengirim acara CTRL_C. Ini akan mengembalikan kesuksesan tetapi akan diabaikan oleh prosesnya. CTRL_BREAK adalah satu-satunya yang berhasil. Tidak terlalu penting karena keduanya akan memanggil ExitProcess () pada akhirnya.

Anda juga tidak dapat memanggil GenerateConsoleCtrlEvent () dengan id grup proses dari id proses anak secara langsung memungkinkan proses pembantu untuk terus hidup. Ini juga akan gagal.

Saya menghabiskan sepanjang hari mencoba untuk membuat ini bekerja. Solusi ini berfungsi untuk saya, tetapi jika ada yang ingin ditambahkan, harap lakukan. Saya mencari banyak orang dengan masalah serupa tetapi tidak ada solusi pasti untuk masalah tersebut. Cara kerja GenerateConsoleCtrlEvent () juga agak aneh jadi jika ada yang tahu lebih banyak detailnya, silakan bagikan.


6
Terima kasih atas solusinya! Ini adalah contoh lain dari Windows API yang sangat berantakan.
vitaut

8

Edit:

Untuk Aplikasi GUI, cara "normal" untuk menangani ini dalam pengembangan Windows adalah dengan mengirim pesan WM_CLOSE ke jendela utama proses.

Untuk aplikasi konsol, Anda perlu menggunakan SetConsoleCtrlHandler untuk menambahkan CTRL_C_EVENT.

Jika aplikasi tidak menghormati itu, Anda dapat menelepon TerminateProcess .


Saya ingin melakukan ini di aplikasi baris perintah (tanpa jendela). TerminateProcess adalah mekanisme force kill (mirip dengan SIGKILL) jadi tidak memberikan aplikasi penghentian kesempatan untuk membersihkan.
Matthew Murdoch

@Matthew Murdoch: Diperbarui untuk menyertakan informasi tentang cara menangani ini di aplikasi konsol. Anda juga dapat menangkap peristiwa lain, termasuk shutdown windows, atau konsol ditutup, dll, melalui mekanisme yang sama. Lihat MSDN untuk detail lengkapnya.
Reed Copsey

@Reed Copsey: Tapi saya ingin memulai ini dari proses terpisah. Saya telah melihat GenerateConsoleCtrlEvent (direferensikan dari SetConsoleCtrlHandler) tetapi ini tampaknya hanya berfungsi jika proses yang dihentikan berada dalam 'grup proses' yang sama dengan proses yang melakukan penghentian ... Ada ide?
Matthew Murdoch

1
Matthew: Satu-satunya ide yang saya miliki adalah menemukan prosesnya, menemukan induknya (konsol), mendapatkan pegangan jendela utama orang tua (konsol) dan menggunakan SendKeys untuk mengirim Ctrl + C langsung ke sana. Itu akan menyebabkan proses konsol Anda menerima CTRL_C_EVENT. Belum mencobanya - tidak yakin seberapa baik itu akan berhasil.
Reed Copsey

Berikut adalah c ++ yang setara dengan SendKeys stackoverflow.com/questions/6838363/… lihat juga stackoverflow.com/questions/10407769/… meskipun tampaknya itu tidak dijamin untuk berfungsi bahkan saat itu ...
rogerdpack

7

Entah bagaimana GenerateConsoleCtrlEvent()mengembalikan kesalahan jika Anda memanggilnya untuk proses lain, tetapi Anda dapat melampirkan ke aplikasi konsol lain dan mengirim acara ke semua proses anak.

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
}

dan bagaimana mengembalikan kendali? dan kemudian saya mengirim control-c, program stopar, tetapi saya tidak dapat melakukan apa pun dengan tangannya.
Yaroslav L.17

Di mana mengembalikan kendali?
KindDragon

Saya pikir Anda memanggil SetConsoleCtrlHandler (NULL, FALSE) untuk menghapus handler Anda, lihat berbagai tanggapan lainnya.
rogerdpack

6

Berikut adalah kode yang saya gunakan di aplikasi C ++ saya.

Poin positif:

  • Bekerja dari aplikasi konsol
  • Bekerja dari layanan Windows
  • Tidak perlu penundaan
  • Tidak menutup aplikasi saat ini

Poin negatif:

  • Konsol utama hilang dan yang baru dibuat (lihat FreeConsole )
  • Peralihan konsol memberikan hasil yang aneh ...

// 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;
}

Contoh penggunaan:

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

Ini harus dibuat sangat jelas karena saat ini belum. Ada versi SendSignal yang dimodifikasi dan dikompilasi untuk mengirim Ctrl-C (secara default hanya mengirim Ctrl + Break). Berikut beberapa binari:

(2014-3-7): Saya membuat versi 32-bit dan 64-bit dengan Ctrl-C, ini disebut SendSignalCtrlC.exe dan Anda dapat mengunduhnya di: https://dl.dropboxusercontent.com/u/49065779/ sendignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe - Juraj Michalak

Saya juga telah mencerminkan file-file itu untuk berjaga-jaga:
versi 32-bit: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-bit: https://www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Penafian: Saya tidak membuat file-file itu. Tidak ada modifikasi yang dilakukan pada file asli yang dikompilasi. Satu-satunya platform yang diuji adalah Windows 7 64-bit. Direkomendasikan untuk menyesuaikan sumber yang tersedia di http://www.latenighthacking.com/projects/2003/sendSignal/ dan mengkompilasinya sendiri.


2

Di Java, menggunakan JNA dengan pustaka Kernel32.dll, mirip dengan solusi C ++. Menjalankan metode utama CtrlCSender sebagai Proses yang hanya mendapatkan konsol dari proses untuk mengirim acara Ctrl + C dan menghasilkan acara tersebut. Karena berjalan secara terpisah tanpa konsol, acara Ctrl + C tidak perlu dinonaktifkan dan diaktifkan lagi.

CtrlCSender.java - Berdasarkan Nemo1024 dan KindDragon jawaban.

Diberikan ID proses yang diketahui, aplikasi penghibur ini akan melampirkan konsol dari proses yang ditargetkan dan menghasilkan Peristiwa CTRL + C di atasnya.

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);
    }
}

Aplikasi Utama - Menjalankan CtrlCSender sebagai proses penghiburan terpisah

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();

hmm, jika saya menjalankan ini dengan proses PowerShell, prosesnya tetap hidup. Di sisi lain, yang menarik, menjalankan dalam proses di JVM memang menurunkan VM! Apakah Anda tahu mengapa proses PowerShell tidak merespons teknik ini?
Groostav


1

Solusi yang saya temukan dari sini cukup sederhana jika Anda memiliki python 3.x tersedia di baris perintah Anda. Pertama, simpan file (ctrl_c.py) dengan isinya:

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)

Kemudian hubungi:

python ctrl_c.py 12345

Jika itu tidak berhasil, saya sarankan untuk mencoba proyek windows-kill: https://github.com/alirdn/windows-kill


0

Seorang teman saya menyarankan cara yang sama sekali berbeda untuk memecahkan masalah dan itu berhasil untuk saya. Gunakan vbscript seperti di bawah ini. Ini dimulai dan aplikasi, biarkan berjalan selama 7 detik dan tutup menggunakan ctrl + c .

Contoh VBScript

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Ini berfungsi jika aplikasi memiliki jendela yang dapat Anda AppActive (bawa ke latar depan).
rogerdpack

0

Saya menemukan semua ini terlalu rumit dan menggunakan SendKeys untuk mengirim CTRL- Ckeystroke ke jendela baris perintah (yaitu jendela cmd.exe) sebagai solusi.


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 dapat dikirim ke program menggunakan windows-kill , dengan sintaks windows-kill -SIGINT PID, di mana PIDdapat diperoleh dengan pslist Microsoft .

Mengenai menangkap SIGINT, jika program Anda menggunakan Python maka Anda dapat mengimplementasikan pemrosesan / penangkapan SIGINT seperti dalam solusi ini .


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.