Pergunta

Preciso de ajuda de um guru C real para analisar uma falha no meu código. Não para consertar o acidente; Eu posso consertá -lo facilmente, mas antes de fazê -lo, gostaria de entender como esse acidente é possível, pois parece totalmente impossível para mim.

Essa falha acontece apenas em uma máquina de clientes e não posso reproduzi -la localmente (para que eu não possa passar pelo código usando um depurador), pois não posso obter uma cópia do banco de dados deste usuário. Minha empresa também não me permite alterar algumas linhas no código e fazer uma construção personalizada para esse cliente (para que eu não possa adicionar algumas linhas de impressão e fazer com que ele execute o código novamente) e, é claro, o cliente tem uma compilação sem Símbolos de depuração. Em outras palavras, minhas habilidades de debbuging são muito limitadas. No entanto, eu poderia pregar o acidente e obter algumas informações de depuração. No entanto, quando olho para essas informações e, para o código, não consigo entender como o fluxo do programa poderia chegar à linha em questão. O código deveria ter travado muito tempo antes de chegar a essa linha. Estou totalmente perdido aqui.

Vamos começar com o código relevante. É muito pouco código:

// ... code above skipped, not relevant ...

if (data == NULL) return -1;

information = parseData(data);

if (information == NULL) return -1;

/* Check if name has been correctly \0 terminated */
if (information->kind.name->data[information->kind.name->length] != '\0') {
    freeParsedData(information);
    return -1;
}

/* Copy the name */
realLength = information->kind.name->length + 1;
*result = malloc(realLength);
if (*result == NULL) {
    freeParsedData(information);
    return -1;
}
strlcpy(*result, (char *)information->kind.name->data, realLength);

// ... code below skipped, not relevant ...

Já é isso. Ele cai em strlcpy. Eu posso te dizer que é o quão strlcpy é verdade chamado em tempo de execução. Strlcpy é realmente chamado com os seguintes parâmetros:

strlcpy ( 0x341000, 0x0, 0x1 );

Sabendo disso, é bastante óbvio por que o strlcpy falha. Ele tenta ler um personagem de um ponteiro nulo e que é claro que vai entrar. E como o último parâmetro possui um valor de 1, o comprimento original deve ter sido 0. Meu código claramente tem um bug aqui, ele não verifica os dados de nome nulos. Eu posso corrigir isso, sem problemas.

Minha pergunta é:
Como esse código pode chegar ao strlcpy em primeiro lugar?
Por que esse código não trava na estatura IF?

Eu tentei localmente na minha máquina:

int main (
    int argc,
    char ** argv
) {
    char * nullString = malloc(10);
    free(nullString);
    nullString = NULL;

    if (nullString[0] != '\0') {
        printf("Not terminated\n");
        exit(1);
    }
    printf("Can get past the if-clause\n");

    char xxx[10];
    strlcpy(xxx, nullString, 1);
    return 0;   
}

Este código nunca é aprovado na instrução IF. Ele trava na declaração IF e isso é definitivamente esperado.

Então, alguém pode pensar em algum motivo pelo qual o primeiro código pode ser aprovado nessa estatura se não trava se o nome-> Os dados são realmente nulos? Isso é totalmente misterioso para mim. Não parece determinístico.

Informações extras importantes:
O código entre os dois comentários é realmente completo, nada foi deixado de fora. Além disso, o aplicativo é Único rosqueado, portanto, não há outro thread que possa alterar inesperadamente qualquer memória em segundo plano. A plataforma onde isso acontece é uma CPU PPC (um G4, caso isso possa desempenhar qualquer papel). E caso alguém se pergunte sobre "Kind". Isso ocorre porque "informações" contém uma "união" chamada "tipo" e o nome é uma estrutura novamente (o tipo é uma união, todo valor possível da união é um tipo diferente de estrutura); Mas tudo isso não deve importar aqui.

Sou grato por alguma ideia aqui. Sou ainda mais grato se não for apenas uma teoria, mas se houver uma maneira de verificar se essa teoria realmente se aplica ao cliente.

Solução

Já aceitei a resposta certa, mas caso alguém encontre essa pergunta no Google, aqui está o que realmente aconteceu:

Os ponteiros estavam apontando para a memória, que já foram libertados. A libertação da memória não fará tudo zero ou fará com que o processo o devolva ao sistema de uma só vez. Portanto, mesmo que a memória tenha sido erroneamente liberada, ela estava contendo os valores corretos. O ponteiro em questão não é nulo no momento "Se verifique" é desempenhado.

Depois disso, alquei uma nova memória, chamando Malloc. Não tenho certeza do que exatamente o Malloc faz aqui, mas todas as chamadas para Malloc ou gratuitas podem ter consequências de longo alcance para toda a memória dinâmica do espaço de endereço virtual de um processo. Após a ligação do Malloc, o ponteiro é de fato nulo. De alguma forma, o Malloc (ou algum sistema chama MaiC usa) Zeros A memória já liberada onde o próprio ponteiro está localizado (não os dados que apontam, o próprio ponteiro está em memória dinâmica). Zerando essa memória, o ponteiro agora tem um valor de 0x0, o que é igual a nulo no meu sistema e quando o strlcpy for chamado, é claro que ele entrará.

Portanto, o bug real que causa esse comportamento estranho estava em um local completamente diferente no meu código. Nunca se esqueça: a memória liberada mantém os valores, mas está além do seu controle por quanto tempo. Para verificar se o seu aplicativo possui um bug de memória de acessar a memória já liberada, apenas verifique se a memória liberada está sempre zerada antes de ser libertada. No OS X, você pode fazer isso definindo uma variável de ambiente em tempo de execução (não há necessidade de recompilar nada). É claro que isso diminui bastante o programa, mas você pega esses bugs muito antes.

Foi útil?

Solução

É possível que a estrutura esteja localizada na memória que tenha sido free()'D, ou a pilha está corrompida. Nesse caso, malloc() poderia estar modificando a memória, pensando que é gratuita.

Você pode tentar executar seu programa em um verificador de memória. Um verificador de memória que suporta o Mac OS X é Valgrind, embora suporça o Mac OS X apenas na Intel, não no PowerPC.

Outras dicas

Primeiro, desreferenciar um ponteiro nulo é um comportamento indefinido. Ele pode travar, não travar ou colocar seu papel de parede em uma foto de bob esponja quadrado.

Dito isto, a desreferência de um ponteiro nulo geralmente resulta em um acidente. Portanto, seu problema provavelmente está relacionado à corrupção da memória, por exemplo, de escrever após o fim de uma de suas cordas. Isso pode causar um acidente de efeito tardio. Estou particularmente suspeito porque é altamente improvável que malloc(1) falhará, a menos que seu programa esteja se acumulando contra o final de sua memória virtual disponível, e você provavelmente notaria se esse fosse o caso.

Editar: Op apontou que não é resultado que é nulo, mas information->kind.name->data. Aqui está uma questão em potencial então:

Não há verificação se information->kind.name->data é nulo. A única verificação que é

if (information->kind.name->data[information->kind.name->length] != '\0') {

Vamos supor que information->kind.name->data é nulo, mas informação-> tipo.

if (*(information->kind.name->data + 100) != '\0') {

O que não desrefere -se nulo, mas as desreferências abordam 100. Se isso não falhar e o endereço 100 conterá 0, esse teste passará.

O efeito de desreferenciar o ponteiro nulo é indefinido pelo padrão, tanto quanto eu sei.

De acordo com C Padrão 6.5.3.2/4:

Se um valor inválido foi atribuído ao ponteiro, o comportamento do operador unário * não será definido.

Portanto, pode haver um acidente ou não.

Você pode estar experimentando a corrupção da pilha. A linha de código a que você está se referindo pode não estar sendo executada.

Minha teoria é que information->kind.name->length é um valor muito grande para que information->kind.name->data[information->kind.name->length] na verdade está se referindo a um endereço de memória válido.

O ato de desreferenciar um ponteiro nulo é indefinido pelo padrão. Não é garantido falhar e muitas vezes não é, a menos que você realmente tente escrever para a memória.

Como FYI, quando vejo esta linha:

if (information->kind.name->data[information->kind.name->length] != '\0') {

Eu vejo até três diferente Dereferências de ponteiro:

  1. em formação
  2. nome
  3. dados (se for um ponteiro e não uma matriz fixa)

Você verifica as informações quanto a não-nula, mas não nome e não dados. O que te faz tão certo de que eles estão corretos?

Eu também ecoando outros sentimentos aqui sobre algo mais possivelmente danificando sua pilha anteriormente. Se você estiver executando no Windows, considere usar Gflags Para fazer coisas como a alocação de páginas, que podem ser usadas para detectar se você ou outra pessoa está escrevendo além do final de um buffer e pisando na sua pilha.

Vi que você está em um Mac - ignore o comentário do GFLAGS - isso pode ajudar alguém que lê isso. Se você estiver executando algo antes do OS X, existem várias ferramentas úteis para Macsbugs para estressar a pilha (como o comando Scramble, 'HS').

Estou interessado no char* elenco na chamada para strlcpy.

Os dados do tipo* poderiam ser diferentes em tamanho do char* no seu sistema? Se os ponteiros de Char forem menores, você poderá obter um subconjunto do ponteiro de dados que pode ser nulo.

Exemplo:

int a = 0xffff0000;
short b = (short) a; //b could be 0 if lower bits are used

Editar: Erros de ortografia corrigidos.

Aqui está uma maneira específica de superar o ponteiro de 'dados' sendo nulo

if (information->kind.name->data[information->kind.name->length] != '\0') {

Digamos informações-> Kind.name-> O comprimento é grande. Pelo menos maior que 4096, em uma plataforma específica com um compilador específico (digamos, a maioria dos *nixes com um compilador GCC de estoque), o código resultará em uma leitura de memória de "endereço do tipo.name-> dados + informações-> Kind.name -> comprimento].

Em um nível mais baixo, essa leitura é "Leia a memória no endereço (0 + 8653)" (ou qualquer que seja o comprimento). É comum em *nixes marcar a primeira página no espaço de endereço como "não acessível", o que significa que a desreferenciando um ponteiro nulo que lê o endereço de memória 0 a 4096 resultará na propagada de uma armadilha de hardware para o aplicativo e travá -la.

Lendo além da primeira página, você pode adotar a memória mapeada válida, por exemplo, uma biblioteca compartilhada ou algo mais que foi mapeado lá - e o acesso à memória não falhará. E tudo bem. Desreferenciando um ponteiro nulo é um comportamento indefinido, nada exige que ele falhe.

Falta '{' Após a última declaração IF significa que algo na seção "// ... acima ignorou, não relevante ..." está controlando o acesso a todo esse fragmento de código. De todo o código colado, apenas o strlcpy é executado. Solução: Nunca use instruções IF sem colchetes encaracolados para esclarecer o controle.

Considere isto...

if(false)
{
    if(something == stuff)
    {
        doStuff();

    .. snip ..

    if(monkey == blah)
        some->garbage= nothing;
        return -1;
    }
}
crash();

Apenas "frash ();" é executado.

Eu iria executar seu programa em Valgrind. Você já sabe que há um problema com indicadores nulos, então perfil desse código.

A vantagem que os seres de Valgrind aqui é que ele verifica cada referência de ponteiro e verifica se esse local de memória foi declarado anteriormente e informará o número da linha, a estrutura e qualquer outra coisa que você quiser saber sobre memória.

Como todos os mais mencionados, referenciar o local da memória 0 é uma coisa "Que Sera, Sera".

Meu senso de espidey tingido está me dizendo que você deve quebrar aquelas caminhadas na estrutura no

if (information->kind.name->data[information->kind.name->length] != '\0') {

linha como

    if (information == NULL) {
      return -1; 
    }
    if (information->kind == NULL) {
      return -1; 
    }

e assim por diante.

Uau, isso é estranho. Uma coisa parece um pouco suspeita para mim, embora possa não contribuir:

O que aconteceria se informações e dados fossem bons ponteiros (não nulos), mas a informação.kind.name fosse nula. Você não descreve esse ponteiro até a linha strlcpy; portanto, se for nulo, pode não travar até então. Obviamente, antes que você faça dados de desreferência [1] para defini -los como 0, que também devem travar, mas devido a qualquer acaso, seu programa pode ter acesso a gravação a 0x01, mas não 0x00.

Além disso, vejo que você usa informações-> name.length em um só lugar, mas informações-> gentil.name.length em outro, não tenho certeza se isso é um erro de digitação ou se for desejado.

Apesar do fato de a desreferência de um ponteiro nulo levar a um comportamento indefinido e não necessariamente a um acidente, você deve verificar o valor de information->kind.name->data e não o conteúdo de information->kind.name->data[1].

char * p = NULL;

P [i] é como

p += i;

que é uma operação válida, mesmo em um nullpointer. Em seguida, aponta para o local da memória 0x0000 [...] eu

Você deve sempre verificar se as informações-> tipo.

dentro

if (*result == NULL) 
    freeParsedData(information);
    return -1;
}

você perdeu um {

deveria ser

if (*result == NULL)
{ 
     freeParsedData(information);
     return -1;
}

Esta é uma boa razão para esse estilo de codificação, em vez de

if (*result == NULL) { 
    freeParsedData(information);
    return -1;
}

Onde você pode não detectar a cinta ausente, porque está acostumado com a forma do bloco de código sem que a cinta o separe da cláusula if.

*resultado = malloc (comprimento real); // ???

O endereço do segmento de memória recém -alocado é armazenado no local referenciado pelo endereço contido na variável "resultado".

Esta é a intenção? Nesse caso, o strlcpy pode precisar de modificação.

De acordo com o meu entendimento, o caso especial desse problema é o acesso inválido, resultando em uma tentativa de ler ou escrever, usando um ponteiro nulo. Aqui, a detecção do problema depende muito de hardware. Em algumas plataformas, acessar a memória para leitura ou gravação usando no ponteiro nulo resultará em uma exceção.

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