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.