segunda-feira, 9 de julho de 2012

Obtendo informações do stack


Muitas vezes durante o desenvolvimento de softwares temos problemas para identificar a origem de alguns erros que ocorrem em tempo de execução, esses erros podem ser mais facilmente encontrados se você pelo menos souber em que parte do seu software ele ocorreu. Uma maneira de identificar o problema é obtendo o nome dos métodos ou funções que ocorreu o erro, como toda vez que se entra em um novo método ocorre um PUSH no stack, é possível pegar esses endereços e identificar quais ainda estão ativos e assim encontrar mais facilmente o ponto que ocorre o erro.

Para simplificar a nossa vida, a Libc já possui as funções backtrace e backtrace_symbols que retornam os endereços contidos no stack e convertem os endereços em string, mas ainda é necessário o Demangling dessas strings para que seja legível o nome dos métodos ou funções obtidos.

Para exemplificar como é simples obter essas informações, criei um programa exemplo onde é executado todos o passos necessários para que seja obtido uma lista com os métodos que estão no stack.

QList<Stack::pFrames> Stack::Trace::ReadStack()
{
    QList<Stack::pFrames> out;
    void *stack[size];
    size_t stackSize = backtrace(stack, size);
    QScopedPointer<char*, QScopedPointerPodDeleter> strings(backtrace_symbols(stack, stackSize));
    for( size_t i=1; i<stackSize; ++i )
    {
        QByteArray line = strings.data()[i];
        int i = line.indexOf('(');
        if( i != -1 )
        {
            int status;
            QByteArray unit = line.mid( 0, i );
            i++;
            QByteArray demangle = line.mid( i, (line.indexOf('+') - i) );
            i = line.indexOf('+') + 1;
            QByteArray offset = line.mid( i, (line.indexOf(')') - i) );
            i = line.indexOf('[') + 1;
            QByteArray addres = line.mid( i, (line.indexOf(']') - i) );
            QByteArray method;
            {
                char* demangled = abi::__cxa_demangle(demangle.data(), 0, 0, &status);
                if (demangled != NULL)
                {
                    method = demangled;
                    free(demangled);
                }
            }
            if (status)
                continue;
            out.append( Stack::pFrames( new Stack::Frames(method, addres, offset, unit ) ) );
        }
    }
    return out;
}

A função backtrace_symbols retorna um array de string contendo o nome da função ou método com a classe; endereço em memória; offset da função e o arquivo em que se encontra podendo ser uma lib, dll ou o próprio executável.

Como nem tudo é perfeito para que o backtrace_symbols tenha sucesso, é necessário que o gcc receba o parâmetro - rdynamic, este parâmetro instrui o linker a adicionar todos os símbolos, métodos ou funções inline não vão para pilha.

quarta-feira, 15 de fevereiro de 2012

Qt mensagens de log


A Qt fornece algumas funções de log úteis como a qDebug(), qWarning(), qCritical() e qFatal() mas em alguns casos precisamos mais do que simplesmente exibir mensagens na saída padrão, precisamos gravar essas mensagens em arquivos para que posteriormente possamos analisar a sua origem.

A própria API já fornece uma maneira simples de fazer isso, pelo uso do manipulador qInstallMsgHandler, com ele é possível capturar essas mensagens e tratá-las da maneira que mais lhe agrada.


class ArquivoLog
{
    QFile arquivo;
    QTextStream stream;

public:
    explicit ArquivoLog( QString nome )
        : arquivo(nome)
        , stream(&arquivo)
    {
        if (!(arquivo.open(QIODevice::WriteOnly | QIODevice::Append)))
            std::cerr << "Falha ao abrir o arquivo " << nome.toUtf8().data();
    }

    ~ArquivoLog()
    {
        //qDebug() << "~ArquivoLog()";
        stream.flush();
        arquivo.close();
    }

    QTextStream &Stream()
    {
        return stream;
    }
} log("main.log");


void MessageHandler(QtMsgType type, const char *msg)
{
    QString txt;
    switch (type)
    {
    case QtDebugMsg:
        txt = QString("[Debug    : %1] %2").arg(QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"), msg);
        #ifdef QT_DEBUG
            std::cout << txt.toUtf8().data() << std::endl;
        #endif
        break;
    case QtWarningMsg:
        txt = QString("[Warning  : %1] %2").arg(QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"), msg);
        #ifdef QT_DEBUG
            std::cerr << txt.toUtf8().data() << std::endl;
        #endif
        break;
    case QtCriticalMsg:
        txt = QString("[Critical : %1] %2").arg(QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"), msg);
        #ifdef QT_DEBUG
            std::cerr << txt.toUtf8().data() << std::endl;
        #endif
        break;
    case QtFatalMsg:
        txt = QString("[Fatal    : %1] %2").arg(QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss"), msg);
        #ifdef QT_DEBUG
            std::cerr << txt.toUtf8().data() << std::endl;
        #endif
    }
    log.Stream() << txt << endl;
}

int main(int , char**)
{
    qInstallMsgHandler(MessageHandler); // registramos o manipulador

    for (long int x = 0; x < 10; x++ )
        qDebug() << "qInstallMsgHandler";

    return 0;
}

Repare que o manipulador que registramos recebe um parâmetro enumerado do tipo QtMsgType com ele podemos identificar o tipo de mensagem, nos permitindo gravar os erros em arquivos diferentes conforme o tipo ou até mesmo os enviar através de sockets ou usando um web service com REST.