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);
}
}
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
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]
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.
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!
Top comments (0)