Pergunta

Eu estou escrevendo uma biblioteca que eu gostaria de ser portátil. Assim, não deve depender glibc ou extensões da Microsoft ou qualquer outra coisa que não está no padrão. Eu tenho uma hierarquia agradável de classes derivadas de std :: exceção que eu uso para erros punho na lógica e na entrada. Sabendo que um determinado tipo de exceção foi lançada em um determinado arquivo eo número da linha é útil, mas saber como a execução chegou lá seria potencialmente muito mais valioso, por isso tenho estado a olhar para formas de adquirir o rastreamento de pilha.

Estou ciente de que esses dados estão disponíveis ao construir contra glibc usando as funções em execinfo.h (veja questão 76822 ) e através da interface StackWalk em Microsoft C ++ implementação (ver questão 126450 ), mas eu gostaria muito de evitar qualquer coisa que não é portátil.

Eu estava pensando em implementar essa funcionalidade me desta forma:

class myException : public std::exception
{
public:
  ...
  void AddCall( std::string s )
  { m_vCallStack.push_back( s ); }
  std::string ToStr() const
  {
    std::string l_sRet = "";
    ...
    l_sRet += "Call stack:\n";
    for( int i = 0; i < m_vCallStack.size(); i++ )
      l_sRet += "  " + m_vCallStack[i] + "\n";
    ...
    return l_sRet;
  }
private:
  ...
  std::vector< std::string > m_vCallStack;
};

ret_type some_function( param_1, param_2, param_3 )
{
  try
  {
    ...
  }
  catch( myException e )
  {
    e.AddCall( "some_function( " + param_1 + ", " + param_2 + ", " + param_3 + " )" );
    throw e;
  }
}

int main( int argc, char * argv[] )
{
  try
  {
    ...
  }
  catch ( myException e )
  {
    std::cerr << "Caught exception: \n" << e.ToStr();
    return 1;
  }
  return 0;
}

Esta é uma idéia terrível? Isso significaria um monte de trabalho adicionando blocos try / catch para cada função, mas eu posso viver com isso. Que não iria funcionar quando a causa da exceção é a corrupção de memória ou falta de memória, mas nesse ponto você está praticamente parafusado qualquer maneira. Pode fornecer informações enganosas, se algumas funções na pilha não capturar exceções, adicionar-se à lista, e relançar, mas pelo menos posso fornecer uma garantia de que todas as minhas funções de biblioteca fazê-lo. Ao contrário de um "real" rastreamento de pilha que não terá o número da linha na chamada de funções, mas pelo menos eu teria algo.

A minha principal preocupação é a possibilidade de que isso irá causar uma desaceleração, mesmo quando nenhuma exceção é realmente lançada. Fazer todas essas blocos try / catch requerem um set-up e tear-down adicionais sobre cada chamada de função, ou é de alguma forma manipulada em tempo de compilação? Ou existem outras questões que eu não ter considerado?

Foi útil?

Solução

Eu acho que isso é uma péssima idéia.

A portabilidade é um objetivo muito digno, mas não quando resulta em uma solução que é intrusiva, desempenho de enfraquecimento, e uma implementação inferior.

Cada plataforma (Windows / Linux / PS2 / iPhone / etc) Eu trabalhei em ofereceu uma maneira de andar a pilha quando ocorre uma exceção e endereços de correspondência para nomes de função. Sim, nenhum destes são portáteis, mas a estrutura de relatório pode ser e, geralmente, leva menos de um dia ou dois para escrever uma versão específica da plataforma de código pilha curta.

Não só isto é menos tempo do que levaria a criação / manutenção de uma solução multi-plataforma, mas os resultados são muito melhores;

  • Não há necessidade de modificar as funções
  • Traps cai em bibliotecas de padrão ou terceiros
  • Não há necessidade de um try / catch em cada função (lento e intensivo de memória)

Outras dicas

Olhe para cima Nested Diagnostic Context uma vez. Aqui está uma pequena dica:

class NDC {
public:
    static NDC* getContextForCurrentThread();
    int addEntry(char const* file, unsigned lineNo);
    void removeEntry(int key);
    void dump(std::ostream& os);
    void clear();
};

class Scope {
public:
    Scope(char const *file, unsigned lineNo) {
       NDC *ctx = NDC::getContextForCurrentThread();
       myKey = ctx->addEntry(file,lineNo);
    }
    ~Scope() {
       if (!std::uncaught_exception()) {
           NDC *ctx = NDC::getContextForCurrentThread();
           ctx->removeEntry(myKey);
       }
    }
private:
    int myKey;
};
#define DECLARE_NDC() Scope s__(__FILE__,__LINE__)

void f() {
    DECLARE_NDC(); // always declare the scope
    // only use try/catch when you want to handle an exception
    // and dump the stack
    try {
       // do stuff in here
    } catch (...) {
       NDC* ctx = NDC::getContextForCurrentThread();
       ctx->dump(std::cerr);
       ctx->clear();
    }
}

A sobrecarga é na implementação do NDC. Eu estava jogando com uma versão preguiçosamente avaliados, assim como aquele que só manteve um número fixo de entradas também. O ponto chave é que se você usar construtores e destruidores para lidar com a pilha de modo que você não precisa de todos esses try blocos desagradáveis ??/ catch e manipulação explícita em todos os lugares.

A única dor de cabeça plataforma específica é o método getContextForCurrentThread(). Você pode usar uma implementação de plataforma específica usando armazenamento local de thread para lidar com o trabalho na maioria, se não todos os casos.

Se você está mais orientado para o desempenho e ao vivo no mundo dos arquivos de log, em seguida, alterar o escopo para manter um ponteiro para o número de nome de arquivo e linha e omitir a coisa NDC por completo:

class Scope {
public:
    Scope(char const* f, unsigned l): fileName(f), lineNo(l) {}
    ~Scope() {
        if (std::uncaught_exception()) {
            log_error("%s(%u): stack unwind due to exception\n",
                      fileName, lineNo);
        }
    }
private:
    char const* fileName;
    unsigned lineNo;
};

Isto lhe dará uma boa rastreamento de pilha em seu arquivo de log quando uma exceção é lançada. Não há necessidade de qualquer curta pilha real, apenas uma pequena mensagem de log quando uma exceção está sendo jogado;)

Eu não acho que há uma "plataforma independente" maneira de fazer isso -. Afinal de contas, se houvesse, não haveria a necessidade de StackWalk ou o traçado especial pilha gcc características que você menciona

Seria um pouco confuso, mas a maneira que eu iria implementar esta seria a criação de uma classe que oferece uma interface consistente para acessar o rastreamento de pilha, em seguida, ter #ifdefs na implementação que usam os métodos específicos de plataforma apropriadas para realmente colocar o rastreamento de pilha juntos.

Dessa forma, o uso da classe é independente de plataforma, e apenas essa classe precisaria ser modificado se você queria atingir alguma outra plataforma.

No depurador:

Para obter o rastreamento de pilha de onde uma exceção é lance de eu só stcik o ponto de quebra no construtor std :: exceção.

Assim, quando a exceção é criada as paradas depurador e você pode então ver o rastreamento de pilha naquele ponto. Não perfeito, mas funciona na maioria das vezes.

gestão Stack é uma daquelas coisas simples que ficar complicado muito rapidamente. Melhor deixá-lo para as bibliotecas especializadas. Você já tentou libunwind? Funciona muito bem e AFAIK é portátil, embora eu nunca tentou isso no Windows.

Este será mais lento, mas parece que ele deve funcionar.

Pelo que eu entendo o problema em fazer, um rastreamento de pilha rápido, portátil é que a implementação da pilha é tanto OS e CPU específica, por isso é implicitamente um problema plataforma específica. Uma alternativa seria utilizar as funções / MS glibc e usar #ifdef e define de pré-processamento apropriadas (por exemplo _WIN32) para implementar as soluções plataforma específicas em diferentes versões.

Uma vez que uso da pilha é altamente plataforma e dependente da implementação, não há nenhuma maneira de fazê-lo diretamente, que é totalmente portátil. No entanto, você poderia construir uma interface portátil para a implementação da plataforma e compilador específico, localizar os problemas, tanto quanto possível. IMHO, esta seria a melhor abordagem.

A implementação tracer, então, conectar-se a qualquer plataforma bibliotecas específicas auxiliares estão disponíveis. Seria, então, operam apenas quando ocorre uma exceção, e mesmo assim somente se você chamou-o de um bloco catch. Sua API mínima seria simplesmente retornar uma string contendo todo o traço.

Exigindo o codificador para captura e processamento injetar rethrow na cadeia de chamada tem custos de execução significativos em algumas plataformas, e impõe um grande custo de manutenção futura.

Dito isto, se você optar por usar o mecanismo de catch / jogar, não se esqueça que, mesmo C ++ ainda tem o C pré-processador disponíveis, e que as macros __FILE__ e __LINE__ são definidos. Você pode usá-los para incluir o nome do arquivo de origem eo número da linha na sua informações de rastreamento.

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