Pergunta

Resumo:

Periodicamente, recebo um erro de mecanismo de execução fatal .NET em um aplicativo que não consigo depurar. A caixa de diálogo que aparece apenas oferece para fechar o programa ou enviar informações sobre o erro para a Microsoft. Tentei olhar para as informações mais detalhadas, mas não sei como usar isso.

Erro:

O erro é visível no visualizador de eventos em aplicativos e é o seguinte:

.NET Time Runtless Versão 2.0.50727.3607 - Erro do motor de execução fatal (7A09795E) (80131506)

O computador em execução é o Windows XP Professional SP 3. (Intel Core2Quad Q6600 2,4GHz W/ 2,0 GB de RAM) Outros projetos baseados em .NET que não possuem downloads com vários threades (veja abaixo) parecem funcionar bem.

Inscrição:

O aplicativo é escrito no C#/. NET 3.5 usando o VS2008 e instalado através de um projeto de configuração.

O aplicativo é multi-thread e download dados de vários servidores da web usando System.Net.HttpWebRequest e seus métodos. Eu determinei que o erro .NET tem algo a ver com rosqueamento ou httpwebrequest, mas não consegui me aproximar mais, pois esse erro em particular parece impossível depurar.

Tentei lidar com erros em muitos níveis, incluindo o seguinte no programa.cs:

// handle UI thread exceptions
Application.ThreadException += Application_ThreadException;

// handle non-UI thread exceptions
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

// force all windows forms errors to go through our handler
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Mais notas e o que eu tentei ...

  • Instalou o Visual Studio 2008 na máquina de destino e tentou em execução no modo de depuração, mas o erro ainda ocorre, sem nenhuma dica sobre onde ocorreu o código -fonte.
  • Ao executar o programa a partir da versão instalada (versão), o erro ocorre com mais frequência, geralmente alguns minutos após o lançamento do aplicativo. Ao executar o programa no modo de depuração dentro do VS2008, ele pode ser executado por horas ou dias antes de gerar o erro.
  • Reinstalado .NET 3.5 e garantiu que todas as atualizações sejam aplicadas.
  • Quebrou objetos aleatórios de cubículo em frustração.
  • As partes reescritas de código que lidam com a rosca e o download nas tentativas de capturar e registrar exceções, embora o registro parecesse agravar o problema (e nunca fornecer dados).

Pergunta:

Que etapas posso tomar para solucionar ou depurar esse tipo de erro? Dumps de memória e similares parecem ser o próximo passo, mas não tenho experiência em interpretá -los. Talvez haja algo mais que eu possa fazer no código para tentar capturar erros ... seria bom se o "erro fatal do mecanismo de execução" fosse mais informativo, mas as pesquisas na Internet me disseram apenas que é um erro comum para muitos Itens relacionados a .NET.

Foi útil?

Solução

Bem, você tem um grande problema. Essa exceção é levantada pelo CLR quando detecta que o lixo coletado a integridade da pilha é comprometida. Corrupção de heap, a desgraça de qualquer programador que já escreveu código em um idioma não gerenciado como C ou C ++.

Essas línguas fazem isso muito Fácil de corromper a pilha, basta escrever além do fim de uma matriz que é alocada na pilha. Ou usando a memória depois de ser lançado. Ou ter um valor ruim para um ponteiro. O tipo de bugz que o código gerenciado foi inventado para resolver.

Mas você está usando o código gerenciado, a julgar pela sua pergunta. Bem, principalmente, sua O código é gerenciado. Mas você está executando grande quantidade de código não gerenciado. Todo o código de baixo nível que realmente torna um trabalho httpwebrequest não é gerenciado. E o CLR também foi escrito em C ++, então é tecnicamente a mesma probabilidade de corromper a pilha. Mas depois de mais de quatro mil revisões e milhões de programas usando, as chances de ainda sofrer com bobinas são muito pequena.

O mesmo não é verdadeiro para todo o outro código não gerenciado que deseja uma peça de httpwebrequest. O código que você não conhece porque não o escreveu e não está documentado pela Microsoft. Seu firewall. Seu scanner de vírus. Monitor de uso da Internet da sua empresa. Senhor sabe de quem "download acelerador".

Isole o problema, suponha que não seja o seu código nem o código da Microsoft que causa o problema. Suponha que seja ambiental primeiro e se livre do crapware.

Para uma história épica ambiental feee, leia este tópico.

Outras dicas

Como as sugestões anteriores são de natureza bastante genérica, pensei que poderia ser útil para publicar minha própria batalha contra essa exceção com exemplos de código específicos, as alterações de fundo que implementei para causar essa exceção e como a resolvi.

Primeiro, a versão curta: Eu estava usando uma DLL interna que foi escrita em C ++ (não gerenciado). Passei em uma matriz de um tamanho específico do meu .NET executável. O código não gerenciado tentou escrever em um local de matriz que não foi alocado pelo código gerenciado. Isso causou uma corrupção na memória que posteriormente foi coletada de lixo. Quando o coletor de lixo se prepara para coletar memória, ele primeiro verifica o status da memória (e os limites). Quando encontra a corrupção, ESTRONDO.

Agora a versão tl; dr:

Estou usando uma DLL não gerenciada desenvolvida internamente, escrita em C ++. Meu próprio desenvolvimento da GUI está no C# .NET 4.0. Estou chamando uma variedade desses métodos não gerenciados. Essa DLL atua efetivamente como minha fonte de dados. Um exemplo de definição externa da DLL:

    [DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
        EntryPoint = "get_sel_list",
        CallingConvention = CallingConvention.Winapi)]
    private static extern int ExternGetSelectionList(
        uint parameterNumber,
        uint[] list,
        uint[] limits,
        ref int size);

Então envolvi os métodos em minha própria interface para uso ao longo do meu projeto:

    /// <summary>
    /// Get the data for a ComboBox (Drop down selection).
    /// </summary>
    /// <param name="parameterNumber"> The parameter number</param>
    /// <param name="messageList"> Message number </param>
    /// <param name="valueLimits"> The limits </param>
    /// <param name="size"> The maximum size of the memory buffer to 
    /// allocate for the data </param>
    /// <returns> 0 - If successful, something else otherwise. </returns>
    public int GetSelectionList(uint parameterNumber, 
           ref uint[] messageList, 
           ref uint[] valueLimits, 
           int size)
    {
        int returnValue = -1;
        returnValue = ExternGetSelectionList(parameterNumber,
                                         messageList, 
                                         valueLimits, 
                                         ref size);
        return returnValue;
    }

Um exemplo de chamada deste método:

            uint[] messageList = new uint[3];
            uint[] valueLimits = new uint[3];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                          dataReferenceParameter, 
                          ref messageList, 
                          ref valueLimits, 
                          BUFFERSIZE);

Na GUI, alguém navega através de diferentes páginas que contêm uma variedade de gráficos e entradas do usuário. O método anterior me permitiu que os dados preenchessem ComboBoxes. Um exemplo da minha configuração de navegação e chamada no momento antes desta exceção:

Na janela do meu host, configurei uma propriedade:

    /// <summary>
    /// Gets or sets the User interface page
    /// </summary>
    internal UserInterfacePage UserInterfacePageProperty
    {
        get
        {
            if (this.userInterfacePage == null)
            {
                this.userInterfacePage = new UserInterfacePage();
            }

            return this.userInterfacePage;
        }

        set { this.userInterfacePage = value; }
    }

Então, quando necessário, navego para a página:

MainNavigationWindow.MainNavigationProperty.Navigate(
        MainNavigation.MainNavigationProperty.UserInterfacePageProperty);

Tudo funcionou bem o suficiente, embora eu tenha tido alguns sérios problemas de rastejamento. Ao navegar usando o objeto (Navigationservice.navigate Método (objeto)), a configuração padrão para o IsKeepAlive propriedade é true. Mas a questão é mais nefasta do que isso. Mesmo se você definir o IsKeepAlive valor no construtor daquela página especificamente para false, ainda é deixado sozinho pelo coletor de lixo como se fosse true. Agora, para muitas das minhas páginas, isso não foi grande coisa. Eles tinham pequenas pegadas de memória, sem muita coisa acontecendo. Mas muitas outras páginas tinham alguns gráficos altamente detalhados para fins de ilustração. Não demorou muito para que o uso normal dessa interface pelos operadores de nossos equipamentos causasse enormes alocações de memória que nunca limparam e, eventualmente, entupiram todos os processos da máquina. Após a corrida do desenvolvimento inicial, diminuiu de um tsunami para mais um furo de maré, finalmente decidi enfrentar os vazamentos de memória de uma vez por todas. Não vou entrar nos detalhes de todos os truques que implementei para limpar a memória (Referência fracas para imagens, desconhecendo os manipuladores de eventos em UNLOAD (), usando um timer personalizado implementando o IweakeventListener interface, etc ...). A principal mudança que fiz foi navegar até as páginas usando o URI em vez do objeto (Navigationservice.navigate Método (URI)). Existem duas diferenças importantes ao usar esse tipo de navegação:

  1. IsKeepAlive está configurado para false por padrão.
  2. O coletor de lixo agora tentará limpar o objeto de navegação como se IsKeepAlive foi definido como false.

Então agora minha navegação parece:

MainNavigation.MainNavigationProperty.Navigate(
    new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));

Outra coisa a observar aqui: isso não apenas afeta como os objetos são limpos pelo coletor de lixo, isso afeta como eles são inicialmente alocado na memória, como eu logo descobriria.

Tudo parecia funcionar muito bem. Minha memória seria limpa rapidamente para perto do meu estado inicial, enquanto eu navegava pelas páginas intensivas em gráficos, até que eu atinja essa página específica com essa chamada específica para a DLL da DataSource para preencher alguns combustos. Então eu peguei isso desagradável FatalEngineExecutionError. Depois de dias de pesquisa e encontrar sugestões vagas, ou soluções altamente específicas que não se aplicaram a mim, além de liberar quase todas as armas de depuração do meu arsenal de programação pessoal, finalmente decidi que a única maneira de realmente acertar isso Down foi a medida extrema da reconstrução de uma cópia exata desta página em particular, elemento por elemento, método por método, linha por linha, até que finalmente me deparei com o código que lançou essa exceção. Foi tão tedioso e doloroso quanto estou implicando, mas finalmente o rastreei.

Acabou sendo a maneira como a DLL não gerenciada estava alocando a memória para escrever dados nas matrizes que eu estava enviando para preencher. Esse método específico analisaria o número do parâmetro e, a partir dessas informações, alocaria uma matriz de um tamanho específico com base na quantidade de dados que esperava gravar na matriz que enviei. O código que travou:

            uint[] messageList = new uint[2];
            uint[] valueLimits = new uint[2];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                           dataReferenceParameter, 
                           ref messageList, 
                           ref valueLimits, 
                           BUFFERSIZE);

Esse código pode parecer idêntico à amostra acima, mas tem uma pequena diferença. O tamanho da matriz que alquei é 2 não 3. Eu fiz isso porque sabia que esse combosbóx em particular teria apenas dois itens de seleção, em oposição aos outros ComboBoxes na página, que todos tinham três itens de seleção. No entanto, o código não gerenciado não viu as coisas do jeito que eu o vi. Ele recebeu a matriz que entreguei e tentei escrever uma matriz de tamanho [3] na minha alocação de tamanho [2], e foi isso. * Bang! * * batida! * Mudei o tamanho da alocação para 3 e o erro foi embora.

Agora, esse código em particular já estava em execução sem esse erro para pelo menos um ano. Mas o simples ato de navegar para esta página por meio de um Uri em oposição a um Object causou o acidente. Isso implica que o objeto inicial deve ser alocado de maneira diferente devido ao método de navegação que usei. Desde que com meu método antigo de navegação, a memória foi empilhada e deixada com o que eu via para a eternidade, não parecia importar se estivesse um pouco corrompido em um ou dois pequenos locais. Depois que o coletor de lixo teve que realmente fazer algo com essa memória (como limpá -la), ele detectou a corrupção da memória e lançou a exceção. Ironicamente, Meu principal vazamento de memória foi encobrir um erro fatal de memória!

Obviamente, vamos revisar essa interface para evitar suposições tão simples, causando tais falhas no futuro. Espero que isso ajude a orientar outros a descobrir o que está acontecendo em seu próprio código.

Uma apresentação que pode ser um bom tutorial sobre por onde começar com esse tipo de questão é a seguinte: Depuração da produção hardcore em .Net por Ingo Rammer.

Eu faço um pouco de codificação C ++/CLI, e a corrupção de heap geralmente não resulta nesse erro; Normalmente, a corrupção da pilha causa uma corrupção de dados e uma exceção normal subsequente ou um erro de proteção de memória - o que provavelmente não significa nada.

Além de tentar o .NET 4.0 (que carrega o código não gerenciado de maneira diferente), você deve comparar as edições x86 e x64 do CLR - se possível - a versão x64 tem um espaço de endereço maior e, portanto, um comportamento completamente diferente do MALLOC (+fragmentação) e, portanto, você apenas você Pode ter sorte e ter um erro diferente (mais degível) lá (se ocorrer alguma).

Além disso, você ativou a depuração de código não gerenciada no depurador (uma opção de projeto), quando você corre com o Visual Studio? E você gerenciou assistentes de depuração?

No meu caso, eu havia instalado um manipulador de exceção com AppDomain.CurrentDomain.FirstChanceException. Esse manipulador estava registrando algumas exceções e tudo estava bem por alguns anos (na verdade, esse código de depuração não deveria ter ficado em produção).

Mas após um erro de configuração, o logger começou a falhar, e o próprio manipulador estava jogando, o que aparentemente resultou em um FatalExecutionEngineError aparentemente vindo do nada.

Então, qualquer pessoa que encontre esse erro pode gastar alguns segundos procurando ocorrências de FirstChanceException Em qualquer lugar do código e talvez economize algumas horas de arranhões na cabeça :)

Se você estiver usando thread.sleep (), esse pode ser o motivo. O código não gerenciado só pode ser dormido na função Kernell.32 Sleep ().

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top