Análise Aprofundada do Código Fonte do Chrome V8: Desvendando os 'Builtins'

Análise Aprofundada do Código Fonte do Chrome V8: Desvendando os 'Builtins'

Este artigo explora os Built-in Functions do V8, essenciais para a execução de JavaScript, detalhando seus métodos de implementação, inicialização e estratégias de depuração. Essencial para entender a lógica interna do V8.

MundiX News·10 de julho de 2026·8 min de leitura·👁 1 views

Análise Aprofundada do Código Fonte do Chrome V8: Desvendando os 'Builtins'

Introdução

As próximas seções deste artigo se dedicarão a uma análise aprofundada dos Built-in Functions (Builtins) do V8. Os Builtins implementam uma vasta gama de funcionalidades centrais no V8, o que evidencia sua importância crucial. No entanto, a maioria dos Builtins é implementada utilizando CAS (CodeStubAssembler) e Torque, linguagens que se assemelham à linguagem de montagem (assembly), apresentando desafios significativos para a leitura do código fonte. Agravando a dificuldade, não é possível depurar Builtins durante a execução do V8, tornando o aprendizado ainda mais árduo. Portanto, este artigo visa apresentar métodos detalhados para o estudo e depuração de Builtins, na esperança de estimular discussões e aprendizado.

1. Resumo

Este é o primeiro artigo de uma série focada em Builtins, abordando o que são os Built-in Functions (Builtins) e como ocorre sua inicialização. Os Built-in Functions, como funcionalidades nativas do V8, são responsáveis pela implementação de muitas operações importantes, incluindo o motor Ignition, manipuladores de bytecode e APIs JavaScript. Dominar os Builtins é fundamental para compreender a lógica de execução do V8, permitindo, por exemplo, visualizar como o bytecode é executado ou como métodos de string como substring são implementados. O conteúdo principal deste artigo aborda os métodos de implementação de Builtins (Seção 2) e a inicialização de Builtins (Seção 3).

2. Métodos de Implementação de Builtins

Os Builtins podem ser implementados de cinco maneiras distintas: linguagem de montagem dependente da plataforma (Platform-dependent assembly language), C++, JavaScript, CodeStubAssembler (CAS) e Torque. Essas abordagens variam significativamente em termos de facilidade de uso e performance. Conforme a documentação oficial (v8.dev/docs/torque):

  1. Linguagem de Montagem Dependente da Plataforma: Pode ser altamente eficiente, mas requer portabilidade manual para todas as plataformas e é difícil de manter.
  2. C++: Similar em estilo às funções de runtime e com acesso às poderosas funcionalidades de runtime do V8, mas geralmente não é adequada para áreas sensíveis à performance.
  3. JavaScript: Código conciso e legível, com acesso a intrínsecos rápidos, mas com chamadas frequentes e lentas ao runtime, sujeito a performance imprevisível devido à poluição de tipos e questões sutis relacionadas à semântica complexa e não óbvia do JavaScript. Builtins em JavaScript estão obsoletos e não devem mais ser adicionados.
  4. CodeStubAssembler (CAS): Oferece funcionalidades eficientes de baixo nível, muito próximas à linguagem de montagem, mas mantendo a independência de plataforma e a legibilidade.
  5. V8 Torque: Uma linguagem de domínio específico do V8 que é traduzida para CodeStubAssembler. Como tal, estende o CAS, oferecendo tipagem estática e uma sintaxe legível e expressiva.

Torque é uma versão aprimorada do CodeStubAssembler, focada em reduzir a complexidade de uso sem sacrificar a performance, tornando o desenvolvimento de Builtins mais acessível. O diagrama oficial (Figura 1) ilustra o processo de criação de Builtins utilizando Torque. Inicialmente, o arquivo file.tq escrito pelo desenvolvedor é traduzido pelo compilador Torque para arquivos -tq-csa.cc/.h. Em seguida, esses arquivos são compilados no executável mksnapshot. Finalmente, mksnapshot gera o arquivo snapshot.bin, que armazena a representação binária serializada dos Builtins. É importante ressaltar que os arquivos -tq-csa.cc/.h são o código fonte dos Builtins gerado pelo compilador Torque a partir do arquivo file.tq. Ao carregar o arquivo snapshot.bin através de desserialização, o V8 não possui tabelas de símbolos, o que impede a visualização do código fonte dos Builtins Torque durante a depuração do código fonte do V8. Os Builtins do CodeStubAssembler também são armazenados em snapshot.bin, tornando-os invisíveis durante a depuração. Métodos de depuração serão abordados posteriormente.

3. Inicialização de Builtins

Antes de mergulharmos no código fonte, é crucial notar algumas considerações. Para depurar a inicialização de Builtins, utilizaremos a versão 7.9 do V8 com a opção v8_use_snapshot ativada. Versões mais recentes não suportam mais v8_use_snapshot = false, o que nos impede de depurar a inicialização de Builtins. A configuração v8_use_snapshot = false desabilita o arquivo snapshot.bin, forçando o V8 a criar e inicializar os Builtins a partir do código fonte C++ durante a inicialização, o que é exatamente o que desejamos observar. Consideramos que os Builtins implementados em C++, CodeStubAssembler e Torque são os mais importantes, pois funcionalidades centrais como o Ignition, manipuladores de bytecode e APIs JavaScript são majoritariamente implementadas por eles. Abaixo, detalhamos essas três categorias de Builtins. O ponto de entrada para a inicialização de Builtins é o seguinte código:

cpp
bool Isolate::InitWithoutSnapshot() {
  return Init(nullptr, nullptr);
}

Como o nome sugere, InitWithoutSnapshot() desabilita o uso do arquivo snapshot.bin. A função InitWithoutSnapshot() executa o seguinte código:

cpp
bool Isolate::Init(ReadOnlyDeserializer* read_only_deserializer,
                     StartupDeserializer* startup_deserializer) {
  // ... (código omitido) ...
  bootstrapper_->Initialize(create_heap_objects);
  if (FLAG_embedded_builtins && create_heap_objects) {
    builtins_constants_table_builder_ = new BuiltinsConstantsTableBuilder(this);
  }
  setup_delegate_->SetupBuiltins(this);
  if (FLAG_embedded_builtins && create_heap_objects) {
    builtins_constants_table_builder_->Finalize();
    delete builtins_constants_table_builder_;
    builtins_constants_table_builder_ = nullptr;
    CreateAndSetEmbeddedBlob();
  }
  // ... (código omitido) ...
  return true;
}

A linha 8, setup_delegate_->SetupBuiltins(this);, direciona a execução para SetupBuiltins(), que por sua vez chama SetupBuiltinsInternal() para completar a inicialização dos Builtins. O código fonte de SetupBuiltinsInternal() é o seguinte:

cpp
void SetupIsolateDelegate::SetupBuiltinsInternal(Isolate* isolate) {
  Builtins* builtins = isolate->builtins();
  // ... (código omitido) ...
  int index = 0;
  Code code;
#define BUILD_CPP(Name)
    code = BuildAdaptor(isolate, index, FUNCTION_ADDR(Builtin_##Name), #Name);
    AddBuiltin(builtins, index++, code);
#define BUILD_TFJ(Name, Argc, ...)
    code = BuildWithCodeStubAssemblerJS(
        isolate, index, &Builtins::Generate_##Name, Argc, #Name);
    AddBuiltin(builtins, index++, code);
#define BUILD_TFC(Name, InterfaceDescriptor)
    /* Return size is from the provided CallInterfaceDescriptor. */
    code = BuildWithCodeStubAssemblerCS(
        isolate, index, &Builtins::Generate_##Name,
        CallDescriptors::InterfaceDescriptor, #Name);
    AddBuiltin(builtins, index++, code);
#define BUILD_TFS(Name, ...)
    /* Return size for generic TF builtins (stub linkage) is always 1. */
    code =
        BuildWithCodeStubAssemblerCS(isolate, index, &Builtins::Generate_##Name,
                                     CallDescriptors::Name, #Name);
    AddBuiltin(builtins, index++, code);
#define BUILD_TFH(Name, InterfaceDescriptor)
    /* Return size for IC builtins/handlers is always 1. */
    code = BuildWithCodeStubAssemblerCS(
        isolate, index, &Builtins::Generate_##Name,
        CallDescriptors::InterfaceDescriptor, #Name);
    AddBuiltin(builtins, index++, code);
#define BUILD_BCH(Name, OperandScale, Bytecode)
    code = GenerateBytecodeHandler(isolate, index, OperandScale, Bytecode);
    AddBuiltin(builtins, index++, code);
#define BUILD_ASM(Name, InterfaceDescriptor)
    code = BuildWithMacroAssembler(isolate, index, Builtins::Generate_##Name,
                                   #Name);
    AddBuiltin(builtins, index++, code);
    BUILTIN_LIST(BUILD_CPP, BUILD_TFJ, BUILD_TFC, BUILD_TFS, BUILD_TFH, BUILD_BCH,
                 BUILD_ASM);
  // ... (código omitido) ...
}

As três funcionalidades centrais de SetupBuiltinsInternal() são explicadas a seguir:

  1. Diferenciação de Builtins: Os macros BUILD_CPP, BUILD_TFJ, BUILD_TFC, BUILD_TFS, BUILD_TFH, BUILD_BCH e BUILD_ASM diferenciam os Builtins por sua funcionalidade, conforme os comentários:

    • CPP: Builtin em C++. Acessado via frame BUILTIN_EXIT. Argumentos: nome.
    • TFJ: Builtin em Turbofan, com linkage JS (chamável como função JavaScript). Argumentos: nome, número de argumentos, nomes explícitos de argumentos...
    • TFS: Builtin em Turbofan, com linkage CodeStub. Argumentos: nome, nomes explícitos de argumentos...
    • TFC: Builtin em Turbofan, com linkage CodeStub e descritor customizado. Argumentos: nome, descritor de interface.
    • TFH: Handlers em Turbofan, com linkage CodeStub. Argumentos: nome, descritor de interface.
    • BCH: Bytecode Handlers, com linkage de dispatch de bytecode. Argumentos: nome, escala de operando, bytecode.
    • ASM: Builtin em montagem dependente da plataforma. Argumentos: nome, descritor de interface.
  2. Definição de Builtins: A linha 38 de SetupBuiltinsInternal(), BUILTIN_LIST, define todos os Builtins. O código fonte é:

    cpp
    #define BUILTIN_LIST(CPP, TFJ, TFC, TFS, TFH, BCH, ASM) \
      BUILTIN_LIST_BASE(CPP, TFJ, TFC, TFS, TFH, ASM) \
      BUILTIN_LIST_FROM_TORQUE(CPP, TFJ, TFC, TFS, TFH, ASM) \
      BUILTIN_LIST_INTL(CPP, TFJ, TFS) \
      BUILTIN_LIST_BYTECODE_HANDLERS(BCH)
    //================分隔线=================================
    #define BUILTIN_LIST_FROM_TORQUE(CPP, TFJ, TFC, TFS, TFH, ASM)
    //............... (código omitido) ............................
    TFJ(StringPrototypeToString, 0, kReceiver)
    TFJ(StringPrototypeValueOf, 0, kReceiver)
    TFS(StringToList, kString)
    TFJ(StringPrototypeCharAt, 1, kReceiver, kPosition)
    TFJ(StringPrototypeCharCodeAt, 1, kReceiver, kPosition)
    TFJ(StringPrototypeCodePointAt, 1, kReceiver, kPosition)
    TFJ(StringPrototypeConcat, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFS(StringAddConvertLeft, kLeft, kRight)
    TFS(StringAddConvertRight, kLeft, kRight)
    TFJ(StringPrototypeEndsWith, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFS(CreateHTML, kReceiver, kMethodName, kTagName, kAttr, kAttrValue)
    TFJ(StringPrototypeAnchor, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringPrototypeBig, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringPrototypeIterator, 0, kReceiver)
    TFJ(StringIteratorPrototypeNext, 0, kReceiver)
    TFJ(StringPrototypePadStart, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringPrototypePadEnd, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFS(StringRepeat, kString, kCount)
    TFJ(StringPrototypeRepeat, 1, kReceiver, kCount)
    TFJ(StringPrototypeSlice, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringPrototypeStartsWith, SharedFunctionInfo::kDontAdaptArgumentsSentinel)
    TFJ(StringPrototypeSubstring, SharedFunctionInfo::kDontAdaptArgumentsSentinel)

A combinação de BUILTIN_LIST e BUILTIN_LIST_FROM_TORQUE revela os nomes de todos os Builtins. As linhas 9-31 mostram os nomes dos Builtins que implementam métodos de string, como StringPrototypeSubstring para a função substring.

  1. Inicialização de Builtins: Os sete macros (BUILD_CPP, BUILD_TFJ, etc.) em conjunto com BUILTIN_LIST completam a inicialização de todos os Builtins. Analisando o macro BUILD_CPP em SetupBuiltinsInternal():

    cpp
    int index = 0;
    Code code;

#define BUILD_CPP(Name) code = BuildAdaptor(isolate, index, FUNCTION_ADDR(Builtin_##Name), #Name); AddBuiltin(builtins, index++, code); //................... (linha separadora) ................. // FUNCTION_ADDR(f) gets the address of a C function f. #define FUNCTION_ADDR(f) (reinterpret_castv8::internal::Address(f)) ```

`index` é inicializado em 0, e `code` é um ponteiro de endereço baseado em `HeapObject`, usado para armazenar o endereço do Builtin gerado. `FUNCTION_ADDR(Builtin_##Name)` cria o ponteiro de endereço do Builtin, que será utilizado na criação do Builtin dentro de `BuildAdaptor()`. O código fonte de `BuildAdaptor()` é:

```cpp
Code BuildAdaptor(Isolate* isolate, int32_t builtin_index,
                  Address builtin_address, const char* name) {
  HandleScope scope(isolate);
  // Canonicalize handles, so that we can share constant pool entries pointing
  // to code targets without dereferencing their handles.
  CanonicalHandleScope canonical(isolate);
  constexpr int kBufferSize = 32 * KB;
  byte buffer[kBufferSize];
  MacroAssembler masm(isolate, BuiltinAssemblerOptions(isolate, builtin_index),
                      CodeObjectRequired::kYes,
                      ExternalAssemblerBuffer(buffer, kBufferSize));
  masm.set_builtin_index(builtin_index);
  DCHECK(!masm.has_frame());
  Builtins::Generate_Adaptor(&masm, builtin_address);
  CodeDesc desc;
  masm.GetCode(isolate, &desc);
  Handle<Code> code = Factory::CodeBuilder(isolate, desc, Code::BUILTIN)
                          .set_self_reference(masm.CodeObject())
                          .set_builtin_index(builtin_index)
                          .Build();
  return *code;
}
```

Neste código, a criação do Builtin é realizada através de `Generate_Adaptor` e `Factory::CodeBuilder`, onde `code` representa o endereço do Builtin. Retornando a `#define BUILD_CPP(Name)`, a execução prossegue para `AddBuiltin()`:

```cpp
void SetupIsolateDelegate::AddBuiltin(Builtins* builtins, int index,
                                      Code code) {
  DCHECK_EQ(index, code.builtin_index());
  builtins->set_builtin(index, code);
}
//.............. (linha separadora) ........................
void Builtins::set_builtin(int index, Code builtin) {
  isolate_->heap()->set_builtin(index, builtin);
}
//............. (linha separadora) ...........................
void Heap::set_builtin(int index, Code builtin) {
  DCHECK(Builtins::IsBuiltinId(index));
  DCHECK(Internals::HasHeapObjectTag(builtin.ptr()));
  // The given builtin may be completely uninitialized thus we cannot check its
  // type here.
  isolate()->builtins_table()[index] = builtin.ptr();
}
```

Neste trecho, `Builtins::set_builtin()` chama `Heap::set_builtin()` para armazenar o Builtin na tabela `isolate()->builtins_table()`. `builtins_table` é um array do tipo `V8_INLINE Address*`, onde `index` é o índice do array. Este array armazena todos os Builtins. Com isso, a inicialização dos Builtins é concluída. A Figura 2 ilustra a pilha de chamadas de função.

Resumo dos Métodos de Depuração de Builtins:

  1. Expansão de Macros: Expanda o macro BUILTIN_LIST para obter o número de índice (index) de cada Builtin. Ferramentas como o pré-processador do VS2019 podem auxiliar nessa expansão.
  2. Pontos de Interrupção Condicionais: Utilize o index para definir pontos de interrupção condicionais. A Figura 3 demonstra como rastrear o Builtin de número 12. Definir um ponto de interrupção diretamente no código fonte do Builtin é o método mais direto. Se você não tem certeza de como um Builtin é implementado (por exemplo, BUILD_CPP ou BUILD_TFS), defina pontos de interrupção condicionais em cada método. A Figura 4 mostra um ponto de interrupção definido no código fonte de Substring.

Resumo Técnico:

  1. Ao depurar Builtins, utilize a versão 7.x do V8, pois as versões mais recentes não possuem mais a opção v8_use_snapshot.
  2. Ao compilar o V8, defina v8_optimized_debug = false para desativar as otimizações do compilador.
  3. Como builtin_index é do tipo int32_t, ao definir pontos de interrupção condicionais, utilize (int)builtin_index.

Por hoje é só. Até a próxima!


🛡️⚡

Pare de pesquisar. Comece a hackear.

O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.

Testar grátis por 7 dias →

Sem cartão para começar · Planos a partir de R$49/mês

📤 Compartilhar & Baixar

🧰 Ferramentas recomendadas

Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Aprendendo Kali Linux: Teste de segurança, pentest e hacking ético

Com centenas de ferramentas pré-instaladas, a distribuição Kali Linux facilita o trabalho de os profissionais de segurança começarem a fazer testes de segurança rapidamente. No entanto, com mais de 600 ferramentas em seu arsenal, o Kali Linux também pode ser desafiador. A nova edição deste prático livro abrange as atualizações nas ferramentas e inclui uma melhor abordagem da análise forense e da engenharia reversa. Ric Messier, autor, não fica apenas no teste de segurança, mas também faz uma abordagem sobre a execução de análise forense, incluindo a análise em disco e na memória, assim como alguma análise básica de malware. • Explore as diversas ferramentas disponíveis no Kali Linux • Entenda o valor do teste de segurança e examine os tipos de teste disponíveis • Aprenda os aspectos básicos do pentest em todo o ciclo de vida do ataque • Instale o Kali Linux em vários sistemas, tanto físicos quanto virtuais • Descubra como usar diferentes ferramentas destinadas à segurança • Estruture um teste de segurança baseado nas ferramentas do Kali Linux • Estenda as ferramentas do Kali para criar técnicas de ataque avançadas • Use o Kali Linux para ajudar a criar relatórios quando o teste terminar “A abordagem concisa, clara e baseada na experiência adotada por Ric Messier para a introdução do Kali Linux e dos testes de cibersegurança é incomparável. Este livro é uma leitura excelente e acessível para iniciantes e um recurso valioso para qualquer pessoa.” —Alexander Arlt, Consultor sênior de segurança, Google

Ver na Amazon
Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Gshield 2 em 1 Hub Extensor Conector USB-C + USB-A e Adaptador de Rede Ethernet LAN RJ45 com 3 Entradas USB 3.0 até 5 Gbps em Liga de Alumínio para Computador e Notebook, Cinza

Compatível com portas USB-C e USB-A, ideal para ampliar a conectividade de dispositivos como MacBook Pro e outros com portas USB-C. Inclui um adaptador USB-A extra, proporcionando uma conexão Ethernet estável e veloz de até 1 Gbps, perfeita para filmes, jogos online e videoconferências. Oferece três portas USB 3.0 com velocidades de transferência de até 5 Gbps, permitindo conectar mouse, teclado, discos rígidos e outros periféricos. Fabricado em alumínio durável, garantindo longa vida útil e resistência ao uso diário. Design compacto e leve, ideal para viagens de negócios e uso diário, facilitando o transporte e armazenamento. Funciona com Windows 10/8.1/8, Mac OS e Chrome OS, oferecendo versatilidade incomparável para diversas necessidades de conectividade. Assegura uma conectividade estável e rápida, perfeita para tarefas exigentes como transferência de dados, streaming e mais.

Ver na Amazon
Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs: Breaking Web Application Programming Interfaces

Hacking APIs is a crash course on web API security testing that will prepare you to penetration-test APIs, reap high rewards on bug bounty programs, and make your own APIs more secure. You'll learn how REST and GraphQL APIs work in the wild and set up a streamlined API testing lab with Burp Suite and Postman. Then you'll master tools useful for reconnaissance, endpoint analysis, and fuzzing, such as Kiterunner and OWASP Amass. Next, you'll learn to perform common attacks, like those targeting an API's authentication mechanisms and the injection vulnerabilities commonly found in web applications. You'll also learn techniques for bypassing protections against these attacks. In the book's nine guided labs, which target intentionally vulnerable APIs, you'll practice: Enumerating APIs users and endpoints using fuzzing techniques Using Postman to discover an excessive data exposure vulnerability Performing a JSON Web Token attack against an API authentication process Combining multiple API attack techniques to perform a NoSQL injection Attacking a GraphQL API to uncover a broken object level authorization vulnerability

Ver oferta
Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition

Up-to-date strategies for thwarting the latest, most insidious network attacks This fully updated, industry-standard security resource shows, step by step, how to fortify computer networks by learning and applying effective ethical hacking techniques. Based on curricula developed by the authors at major security conferences and colleges, the book features actionable planning and analysis methods as well as practical steps for identifying and combating both targeted and opportunistic attacks. Gray Hat Hacking: The Ethical Hacker's Handbook, Sixth Edition clearly explains the enemy's devious weapons, skills, and tactics and offers field-tested remedies, case studies, and testing labs. You will get complete coverage of Internet of Things, mobile, and Cloud security along with penetration testing, malware analysis, and reverse engineering techniques. State-of-the-art malware, ransomware, and system exploits are thoroughly explained. Fully revised content includes 7 new chapters covering the latest threats Includes proof-of-concept code stored on the GitHub repository Authors train attendees at major security conferences, including RSA, Black Hat, Defcon, and B-Sides

Ver na Amazon
Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Bloqueador USB de privacidade de porta USB para PC, notebook, bloco de laptop,

Proteção de privacidade aprimorada: protege o link de transmissão de dados para evitar roubo de informações, fornecendo proteção de segurança robusta que protege a privacidade do usuário durante transferências de arquivos e garante uma conexão segura para interações de dispositivos sem preocupações em vários ambientes Uso a longo prazo: a camada protetora resistente ao desgaste, combinada com um corpo de metal resistente, oferece gerenciamento de calor confiável e qualidade duradoura durante o uso diário Entrega eficiente de energia: a tecnologia de chip inteligente garante a identificação automática dos requisitos de energia, fornecendo carregamento eficiente alinhando-se com vários protocolos de carregamento rápido para maior conveniência Proteção contra sobrecarga: evitando riscos de sobrecarga, este bloqueador de dados USB protege a vida útil da bateria e garante um desempenho estável, mantendo um fluxo estável de energia para melhorar a longevidade do dispositivo de forma eficaz Prático de transportar: com atenção à portabilidade, este bloqueador de dados USB oferece um design compacto que é leve e fácil de transportar, melhorando a conveniência do usuário e operação eficiente

Ver na Amazon

📩 Newsletter MundiX

Receba novidades de cibersegurança + um checklist de pentest grátis. Sem spam.

Ao assinar você concorda em receber e-mails. Cancele quando quiser.