Posso enviar um ctrl-C (SIGINT) para um aplicativo no Windows?


91

Eu (no passado) escrito multi-plataforma (Windows / Unix) aplicações que, quando iniciado a partir da linha de comando, manipulados um digitado pelo usuário Ctrl- Ccombinação da mesma forma (ou seja, para encerrar o aplicativo limpa).

É possível no Windows enviar um Ctrl- C/ SIGINT / equivalente a um processo de outro processo (não relacionado) para solicitar que ele seja encerrado de forma limpa (dando-lhe a oportunidade de organizar recursos, etc.)?


1
Eu estava interessado em enviar Ctrl-C para processos de serviço java apenas para obter threaddumps. Parece que jstackpode ser usado de forma confiável para este assunto específico: stackoverflow.com/a/47723393/603516
Vadzim

Respostas:


31

O mais próximo que cheguei de uma solução é o aplicativo de terceiros SendSignal . O autor lista o código-fonte e um executável. Eu verifiquei que ele funciona em janelas de 64 bits (executando como um programa de 32 bits, eliminando outro programa de 32 bits), mas não descobri como incorporar o código em um programa do Windows (seja de 32 bits ou 64 bits).

Como funciona:

Depois de vasculhar muito o depurador, descobri que o ponto de entrada que realmente faz o comportamento associado a um sinal como ctrl-break é kernel32! CtrlRoutine. A função tinha o mesmo protótipo de ThreadProc, portanto, pode ser usada com CreateRemoteThread diretamente, sem a necessidade de injetar código. No entanto, esse não é um símbolo exportado! Ele está em endereços diferentes (e até tem nomes diferentes) em diferentes versões do Windows. O que fazer?

Aqui está a solução que finalmente encontrei. Eu instalo um manipulador de controle de console para meu aplicativo e, em seguida, gere um sinal ctrl-break para meu aplicativo. Quando meu manipulador é chamado, eu olho novamente para o topo da pilha para descobrir os parâmetros passados ​​para kernel32! BaseThreadStart. Eu pego o primeiro parâmetro, que é o endereço inicial desejado do thread, que é o endereço do kernel32! CtrlRoutine. Em seguida, volto do meu manipulador, indicando que manipulei o sinal e meu aplicativo não deve ser encerrado. De volta ao thread principal, espero até que o endereço de kernel32! CtrlRoutine seja recuperado. Depois de obtê-lo, crio um thread remoto no processo de destino com o endereço inicial descoberto. Isso faz com que os manipuladores de ctrl no processo de destino sejam avaliados como se ctrl-break tivesse sido pressionado!

O bom é que apenas o processo de destino é afetado e qualquer processo (mesmo um processo em janela) pode ser direcionado. Uma desvantagem é que meu pequeno aplicativo não pode ser usado em um arquivo em lote, pois ele o matará quando enviar o evento ctrl-break para descobrir o endereço de kernel32! CtrlRoutine.

(Preceda-o startse estiver executando em um arquivo em lote.)


2
+1 - O código vinculado usa Ctrl-Break em vez de Ctrl-C, mas aparentemente (olhando a documentação para GenerateConsoleCtrlEvent) pode ser adaptado para Ctrl-C.
Matthew Murdoch

8
Na verdade, existe uma função para fazer isso por você, lol, "GenerateConsoleCtrlEvent". Veja: msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx Veja também minha resposta aqui: serverfault.com/a/501045/10023
Triynko

1
Link do Github relacionado à resposta acima: github.com/walware/statet/tree/master/…
meawoppl

3
É espantoso que o Windows seja tão antigo quanto é e ainda assim a Microsoft não forneceu um mecanismo para o aplicativo de console A escutar um sinal do aplicativo de console B para que o aplicativo de console B possa dizer ao aplicativo de console A para desligar corretamente. Em qualquer caso, tirando o MS-bashing, tentei SendSignal e obtivemos "CreateRemoteThread falhou com 0x00000005". Alguma outra ideia de como codificar o aplicativo A para ouvir um sinal (qualquer sinal que seja conveniente) e como sinalizá-lo?
David I. McIntosh

3
@ DavidI.McIntosh parece que você deseja criar um evento nomeado em ambos os processos; você seria então capaz de sinalizar um do outro. Verifique a documentação para CreateEvent
arolson101

58

Fiz algumas pesquisas em torno desse tópico, que se tornaram mais populares do que eu esperava. A resposta de KindDragon foi um dos pontos principais.

Escrevi uma postagem mais longa no blog sobre o assunto e criei um programa de demonstração funcional, que demonstra o uso desse tipo de sistema para fechar um aplicativo de linha de comando em algumas maneiras legais. Esse post também lista links externos que usei em minha pesquisa.

Resumindo, esses programas de demonstração fazem o seguinte:

  • Inicie um programa com uma janela visível usando .Net, esconda-se com pinvoke, execute por 6 segundos, mostre com pinvoke, pare com .Net.
  • Inicie um programa sem janela usando .Net, execute por 6 segundos, pare anexando o console e emitindo ConsoleCtrlEvent

Edit: A solução alterada da KindDragon para aqueles que estão interessados ​​no código aqui e agora. Se você planeja iniciar outros programas após interromper o primeiro, deve reativar o manuseio de Ctrl-C, caso contrário, o próximo processo herdará o estado desativado do pai e não responderá a 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); 
  }
}

Além disso, planeje uma solução de contingência se AttachConsole()ou o sinal enviado falhar, por exemplo, dormindo, então:

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

4
Inspirado nisso, criei um aplicativo cpp "autônomo" que faz isso: gist.github.com/rdp/f51fb274d69c5c31b6be , caso seja útil. E sim, este método pode enviar ctrl + c ou ctrl + break para "qualquer" pid, aparentemente.
rogerdpack de

Faltando uma das importações de DLL "SetConsoleCtrlHandler", mas isso funciona.
Derf Skren

excelente postagem. excelente blog. Eu sugeriria terminar a função StopProgramWithInvisibleWindowUsingPinvoke em seu programa de experimentos. Quase abandonei a ideia pensando que você não tinha encontrado uma solução
Wilmer Saint

Para evitar o, wait()eu removi o Re-enable Ctrl-C ( SetConsoleCtrlHandler(null, false);). Fiz alguns testes (várias chamadas, também em processos já encerrados) e não encontrei nenhum efeito colateral (ainda?).
56ka

O FreeConsole deve ser chamado imediatamente após o envio de GenerateConsoleCtrlEvent. Até que seja chamado, seu aplicativo será anexado ao outro console. Se o outro console for encerrado antes de encerrar o encerramento, seu aplicativo também será encerrado!
Timothy Jannace de

17

Acho que estou um pouco atrasado nessa questão, mas vou escrever algo de qualquer maneira para qualquer pessoa com o mesmo problema. Esta é a mesma resposta que dei a esta pergunta.

Meu problema era que eu gostaria que meu aplicativo fosse um aplicativo GUI, mas os processos executados deveriam ser executados em segundo plano, sem nenhuma janela de console interativo anexada. Acho que essa solução também deve funcionar quando o processo pai é um processo de console. Você pode ter que remover o sinalizador "CREATE_NO_WINDOW".

Consegui resolver isso usando GenerateConsoleCtrlEvent () com um aplicativo wrapper. A parte complicada é apenas que a documentação não é realmente clara sobre como exatamente pode ser usado e as armadilhas com ele.

Minha solução é baseada no que está descrito aqui . Mas isso também não explicava realmente todos os detalhes e com um erro, então aqui estão os detalhes de como fazer isso funcionar.

Crie um novo aplicativo auxiliar "Helper.exe". Este aplicativo ficará entre o seu aplicativo (pai) e o processo filho que você deseja fechar. Ele também criará o processo filho real. Você deve ter este processo de "intermediário" ou GenerateConsoleCtrlEvent () falhará.

Use algum tipo de mecanismo IPC para comunicar do pai para o processo auxiliar que o auxiliar deve fechar o processo filho. Quando o auxiliar obtém este evento, ele chama "GenerateConsoleCtrlEvent (CTRL_BREAK, 0)" que fecha a si mesmo e ao processo filho. Eu mesmo usei um objeto de evento para isso, que o pai conclui quando deseja cancelar o processo filho.

Para criar seu Helper.exe, crie-o com CREATE_NO_WINDOW e CREATE_NEW_PROCESS_GROUP. E ao criar o processo filho, crie-o sem sinalizadores (0), o que significa que ele derivará o console de seu pai. Deixar de fazer isso fará com que ele ignore o evento.

É muito importante que cada etapa seja executada assim. Tenho tentado todos os tipos de combinações, mas essa combinação é a única que funciona. Você não pode enviar um evento CTRL_C. Ele retornará sucesso, mas será ignorado pelo processo. CTRL_BREAK é o único que funciona. Realmente não importa, pois ambos chamarão ExitProcess () no final.

Você também não pode chamar GenerateConsoleCtrlEvent () com um ID de grupo de processo do ID do processo filho, permitindo diretamente que o processo auxiliar continue ativo. Isso também falhará.

Passei um dia inteiro tentando fazer isso funcionar. Esta solução funciona para mim, mas se alguém tiver mais alguma coisa a acrescentar, faça-o. Eu procurei por toda a rede muitas pessoas com problemas semelhantes, mas nenhuma solução definitiva para o problema. Como GenerateConsoleCtrlEvent () funciona também é um pouco estranho, então se alguém souber mais detalhes sobre ele, compartilhe.


6
Obrigado pela solução! Este é mais um exemplo da API do Windows sendo uma bagunça terrível.
vitaut de

8

Editar:

Para um aplicativo GUI, a maneira "normal" de lidar com isso no desenvolvimento do Windows seria enviar uma mensagem WM_CLOSE para a janela principal do processo.

Para um aplicativo de console, você precisa usar SetConsoleCtrlHandler para adicionar um CTRL_C_EVENT.

Se o aplicativo não respeitar isso, você pode chamar TerminateProcess .


Desejo fazer isso em um aplicativo de linha de comando (sem janelas). TerminateProcess é um mecanismo de encerramento forçado (semelhante ao SIGKILL), portanto, não dá ao aplicativo de encerramento qualquer oportunidade de limpeza.
Matthew Murdoch

@Matthew Murdoch: Atualizado para incluir informações sobre como lidar com isso em um aplicativo de console. Você também pode capturar outros eventos, incluindo desligamento de janelas ou console fechado, etc, através do mesmo mecanismo. Consulte MSDN para obter detalhes completos.
Reed Copsey

@Reed Copsey: Mas eu gostaria de iniciar isso a partir de um processo separado. Eu dei uma olhada em GenerateConsoleCtrlEvent (referenciado em SetConsoleCtrlHandler), mas isso parece funcionar apenas se o processo que está sendo encerrado estiver no mesmo 'grupo de processos' que o processo que está encerrando ... Alguma idéia?
Matthew Murdoch

1
Matthew: A única ideia que eu teria seria encontrar o processo, encontrar seu pai (o console), obter o identificador da janela principal do pai (o console) e usar SendKeys para enviar um Ctrl + C diretamente para ele. Isso deve fazer com que o processo do console receba um CTRL_C_EVENT. Ainda não tentei - não tenho certeza se funcionaria bem.
Reed Copsey

Aqui está um c ++ equivalente a SendKeys stackoverflow.com/questions/6838363/… veja também stackoverflow.com/questions/10407769/… embora aparentemente não funcione garantido mesmo então ...
rogerdpack

7

De alguma forma, GenerateConsoleCtrlEvent()retorne um erro se você chamá-lo para outro processo, mas você pode anexar a outro aplicativo de console e enviar o evento para todos os processos filho.

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
}

e como devolver o controle? e então eu envio control-c, programa stopar, mas não consigo fazer nada com as mãos.
Yaroslav L.

Para onde devolver o controle?
KindDragon

Acho que você chama SetConsoleCtrlHandler (NULL, FALSE) para remover seu manipulador, veja as várias outras respostas.
rogerdpack de

6

Aqui está o código que uso em meu aplicativo C ++.

Pontos positivos:

  • Funciona a partir do aplicativo de console
  • Funciona a partir do serviço Windows
  • Sem necessidade de atraso
  • Não fecha o aplicativo atual

Pontos negativos:

  • O console principal é perdido e um novo é criado (ver FreeConsole )
  • A troca de console deu resultados estranhos ...

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

Exemplo de uso:

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

Deve ficar bem claro, porque no momento não é. Existe uma versão modificada e compilada de SendSignal para enviar Ctrl-C (por padrão, envia apenas Ctrl + Break). Aqui estão alguns binários:

(2014-3-7): Criei as versões de 32 e 64 bits com Ctrl-C, é chamado SendSignalCtrlC.exe e você pode baixá-lo em: https://dl.dropboxusercontent.com/u/49065779/ sendignalctrlc / x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe - Juraj Michalak

Também espelhei esses arquivos para o caso de:
versão de 32 bits: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0 versão de
64 bits: https://www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Isenção de responsabilidade: eu não construí esses arquivos. Nenhuma modificação foi feita nos arquivos originais compilados. A única plataforma testada é a Windows 7 de 64 bits. Recomenda-se adaptar a fonte disponível em http://www.latenighthacking.com/projects/2003/sendSignal/ e compilá-la você mesmo.


2

Em Java, usando JNA com a biblioteca Kernel32.dll, semelhante a uma solução C ++. Executa o método principal CtrlCSender como um Processo que apenas obtém o console do processo para enviar o evento Ctrl + C e gera o evento. Como é executado separadamente sem um console, o evento Ctrl + C não precisa ser desativado e ativado novamente.

CtrlCSender.java - Baseado nas respostas do Nemo1024 e KindDragon .

Dado um ID de processo conhecido, este aplicativo sem console anexará o console do processo de destino e gerará um evento CTRL + C nele.

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

Aplicativo principal - executa CtrlCSender como um processo sem console separado

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, se eu executar isso em um processo PowerShell, o processo permanece ativo. Por outro lado, curiosamente, a execução em processo na JVM realmente desativa a VM! Você tem alguma ideia de por que um processo PowerShell não responderia a essa técnica?
Groostav


1

Uma solução que encontrei aqui é muito simples se você tiver o python 3.x disponível em sua linha de comando. Primeiro, salve um arquivo (ctrl_c.py) com o conteúdo:

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)

Então ligue:

python ctrl_c.py 12345

Se isso não funcionar, recomendo experimentar o projeto windows-kill: https://github.com/alirdn/windows-kill


1

Graças à resposta de Jimhark e outras respostas aqui, encontrei uma maneira de fazer isso no PowerShell:

$ProcessID = 1234
$MemberDefinition = '
    [DllImport("kernel32.dll")]public static extern bool FreeConsole();
    [DllImport("kernel32.dll")]public static extern bool AttachConsole(uint p);
    [DllImport("kernel32.dll")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);
    public static void SendCtrlC(uint p) {
        FreeConsole();
        if (AttachConsole(p)) {
            GenerateConsoleCtrlEvent(0, p);
            FreeConsole();
        }
        AttachConsole(uint.MaxValue);
    }'
Add-Type -Name 'dummyName' -Namespace 'dummyNamespace' -MemberDefinition $MemberDefinition
[dummyNamespace.dummyName]::SendCtrlC($ProcessID) }

O que fez as coisas funcionarem foi enviar o GenerateConsoleCtrlEvent para o grupo de processos desejado em vez de all processes that share the console of the calling processe o AttachConsole de volta the console of the parent of the current process.


0

Um amigo meu sugeriu uma maneira completamente diferente de resolver o problema e funcionou para mim. Use um vbscript como abaixo. Ele inicia um aplicativo, deixe-o funcionar por 7 segundos e feche-o usando ctrl + c .

'Exemplo de VBScript

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Isso funciona se o aplicativo tiver uma janela que você possa AppActive (trazer para o primeiro plano).
rogerdpack

0

Eu achei tudo isso muito complicado e usado SendKeys para enviar uma CTRL- C(janela ou seja cmd.exe) de teclas para a janela de linha de comando como uma solução alternativa.


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

O SIGINT pode ser enviado para o programa usando o windows-kill , por sintaxe windows-kill -SIGINT PID, onde PIDpode ser obtido no pslist da Microsoft .

Com relação à captura de SIGINTs, se seu programa estiver em Python, você pode implementar o processamento / captura de SIGINT como nesta solução .


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.