DEV Community

Cristiano Rodrigues for Unhacked

Posted on

NullReferenceException, como ele acontece?

Um dos erros mais comuns em aplicações .NET é a NullReferenceException. Esse erro ocorre quando tentamos acessar uma propriedade ou método de um objeto que ainda não foi instanciado e, portanto, tem um valor "null" na memória.

Mas como o .NET reconhece esse erro e gera a exceção? Vamos analisar um código simples e verificar a IL gerada.

using System;

public class Program
{
    private static void Main(string[] args)
    {
        string nome = null;
        ImprimirTamanho(nome);
    }

    private static void ImprimirTamanho(string s)
    {
        Console.WriteLine(s.Length);
    }
}
Enter fullscreen mode Exit fullscreen mode

IL do código (parte)

  // Methods
    .method private hidebysig static 
        void Main (
            string[] args
        ) cil managed 
    {
        // Method begins at RVA 0x209d
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldnull
        IL_0001: call void Program::ImprimirTamanho(string)
        IL_0006: ret
    } // end of method Program::Main

    .method private hidebysig static 
        void ImprimirTamanho (
            string s
        ) cil managed 
    {
        // Method begins at RVA 0x20a5
        // Code size 12 (0xc)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: callvirt instance int32 [System.Runtime]System.String::get_Length()
        IL_0006: call void [System.Console]System.Console::WriteLine(int32)
        IL_000b: ret
    } // end of method Program::ImprimirTamanho
Enter fullscreen mode Exit fullscreen mode

Para entender como o .NET reconhece o erro, vamos analisar a IL gerada pelo compilador. Na linha IL_0000, do método Program::Main, o comando ldnull coloca uma referência nula na pilha de avaliação.

Em seguida, o método Program::ImprimirTamanho é invocado e o comando ldarg.0 é responsável por carregar o valor do argumento na pilha de avaliação.

O erro ocorre na linha IL_0001, quando o comando callvirt tenta chamar o método get_Length() de uma referência nula que está na pilha. Isso gera a NullReferenceException.

Para entender melhor como o código está funcionando, vamos analisar o assembly gerado. Mais especificamente, vamos focar na linha:

L0000: mov ecx, [rcx+8]:

Program.ImprimirTamanho(System.String)
    L0000: mov ecx, [rcx+8]
    L0003: mov rax, 0x7ffed9667b70
    L000d: jmp qword ptr [rax]
Enter fullscreen mode Exit fullscreen mode

Em termos simples, a instrução está dizendo: "pegue o valor na posição de memória que está localizada 8 bytes depois do endereço contido em rcx e coloque esse valor no registrador ecx".

É importante observar que a sintaxe [rcx+8] significa que estamos referenciando um endereço de memória que é calculado a partir do valor armazenado em rcx. O valor 8 representa o deslocamento (offset) em relação ao endereço apontado por rcx.

Registradores

Agora, vamos analisar o valor do registrador rcx no momento em que o método ImprimirTamanho é executado. Como pode ser visto na imagem acima, ele está zerado!

Levando todos os pontos apresentados em consideração, podemos concluir que a instrução assembly "mov ecx, [rcx+8]" tenta acessar o endereço de memória 0x0000000000000008 (64 Bits). No entanto, esse endereço não está alocado para o processo em questão e, portanto, essa tentativa de acesso resultará em uma violação de acesso à memória.

No Windows e no Linux, os primeiros 4KB de memória virtual de um processo são reservados e protegidos pelo sistema operacional. Essa região de memória é conhecida como "página zero" e é protegida para evitar que programas maliciosos explorem vulnerabilidades de segurança.

A página zero é uma área crítica da memória e contém informações importantes do sistema, como a tabela de vetores de interrupção e a tabela de páginas do kernel. A proteção da página zero é fundamental para garantir a integridade do sistema.

Além disso, essa região de memória é protegida para garantir que o sistema operacional possa detectar e tratar erros comuns em programas, como tentativas de acessar endereços de memória não alocados ou não inicializados. Se um programa tentar acessar um endereço de memória dentro dessa região, o sistema operacional geralmente responderá gerando uma exceção de violação de acesso à memória. Essa exceção pode ser traduzida em um NullReferenceException pela CLR.

Agora que entendemos como ocorre o NullReferenceException, podemos olhar de forma mais aprofundada para a representação do valor NULL em um endereço de memória. A palavra NULL é utilizada para indicar que não existe uma instância alocada naquele endereço de memória, mas ela não é armazenada na memória com muitos pensam.

Até a próxima!

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay