Analysis of C++ Features from the Perspective of Assembly Language: Starting with the New Operator

The “Readability Crisis” of C++ Compilation Results

The assembly code generated from C++ is indeed more complex than that of C, but this complexity follows certain rules:

; C language malloc call
push    16                  ; allocate size
call    malloc
add     esp, 4

; C++ new operator call
push    16                  ; allocate size
call    ??2@YAPAXI@Z        ; operator new(uint)
add     esp, 4
mov     [ebp+var_4], eax
call    ??0MyClass@@QAE@XZ  ; constructor

Analysis of the New Operator in 32-bit Debug Driver

Basic Implementation Analysis

Given the implementation of operator new in the 32-bit debug version:

text:00011000 operator_new proc near
text:00011000 NumberOfBytes = dword ptr 8
text:11000     push    ebp
text:11001     mov     ebp, esp
text:11003     push    ecx                ; save register
text:11004     push    esi
text:11005     mov     esi, [ebp+NumberOfBytes]
text:11008     push    esi                ; size
text:11009     push    0                  ; PoolType (assumed NonPagedPool)
text:1100B     call    _ExAllocatePool@8  ; kernel allocation function
text:11010     test    eax, eax           ; check NULL
text:11012     jz      short loc_11022
text:11014     push    esi                ; size
text:11015     push    0                  ; fill value
text:11017     push    eax                ; address
text:11018     call    _memset            ; zero initialization
text:1101D     add     esp, 0Ch
text:11020     pop     esi
text:11021     pop     ecx
text:11022 loc_11022:
text:11022     pop     ebp
text:11023     retn
text:11023 operator_new endp

Key Instruction Interpretation

  1. Function Prologue:

  • <span>push ebp</span> / <span>mov ebp, esp</span>: Standard stack frame establishment
  • <span>push ecx</span> / <span>push esi</span>: Save callee-saved registers
  • Memory Allocation:

    • <span>push esi</span>: Pass allocation size
    • <span>push 0</span>: PoolType parameter (debug version may not optimize constants)
  • Memory Initialization:

    • <span>test eax, eax</span>: Check if allocation was successful
    • Triple<span>push</span> prepare memset parameters

    Assembly Representation Patterns of C++ Features

    Name Mangling

    C++ uses complex name encoding to preserve type information:

    ??2@YAPAXI@Z => void * __cdecl operator new(unsigned int)
    

    Decoding rules:

    • <span>??2</span>: operator new
    • <span>@YA</span>: __cdecl calling convention
    • <span>PAX</span>: return type void*
    • <span>I</span>: parameter type unsigned int

    Object Lifecycle Management

    Automatic insertion of constructors/destructors:

    ; Object creation sequence
    call    ??2@YAPAXI@Z        ; operator new
    test    eax, eax
    jz      fail
    mov     ecx, eax            ; this pointer
    call    ??0MyClass@@QAE@XZ  ; constructor
    
    ; Object destruction sequence
    mov     ecx, [ebp+var_4]    ; this pointer
    call    ??1MyClass@@QAE@XZ  ; destructor
    push    eax
    call    ??3@YAXPAX@Z        ; operator delete
    

    Comparison of Memory Allocation in C and C++

    C-style allocation:

    push    16
    call    _malloc
    add     esp, 4
    mov     [ebp+ptr], eax
    

    C++ new expression:

    push    16
    call    ??2@YAPAXI@Z        ; operator new
    add     esp, 4
    mov     [ebp+ptr], eax
    mov     ecx, eax            ; this pointer preparation
    call    ??0MyClass@@QAE@XZ  ; constructor
    

    Low-Level Support for Exception Handling

    The stack unwinding mechanism for C++ exception handling:

    __try_block:
        push    ebp
        mov     ebp, esp
        push    -1              ; try level
        push    offset __sehtable
        push    offset __except_handler3
        mov     eax, large fs:0
        push    eax             ; install SEH
        mov     large fs:0, esp
    
        ; Code that may throw exceptions
        mov     ecx, [ebp+this]
        call    ?may_throw@@QAEXXZ
    
        ; Clean up SEH
        mov     ecx, [ebp+prev_SEH]
        mov     large fs:0, ecx
        add     esp, 0Ch
        pop     ebp
        retn
    

    Practical: Handwritten Operator New/Delete

    Optimized 64-bit Implementation

    ; Optimized operator new
    ??2@YAPEAX_K@Z proc
        mov     r8d, 'MyDr'     ; Pool label
        mov     edx, 200h       ; POOL_FLAG_NON_PAGED
        jmp     ExAllocatePool2 ; tail call optimization
    ??2@YAPEAX_K@Z endp
    
    ; Optimized operator delete
    ??3@YAXPEAX@Z proc
        test    rcx, rcx
        jz      @F
        jmp     ExFreePool      ; tail call optimization
    @@: retn
    ??3@YAXPEAX@Z endp
    

    Debug Enhanced Version

    ; Debug version operator new
    debug_new proc
        push    rbx
        sub     rsp, 20h
        mov     rbx, rcx        ; save size
        lea     rcx, [rcx+10h]  ; extra allocation for debug header
        mov     edx, 200h       ; NonPagedPool
        mov     r8d, 'DbgN'
        call    ExAllocatePool2
        test    rax, rax
        jz      @F
        mov     [rax], rbx      ; store original size
        mov     qword ptr [rax+8], 0BAD0BAD0h ; debug marker
        add     rax, 10h        ; return user-available address
    @@: add     rsp, 20h
        pop     rbx
        retn
    debug_new endp
    

    Understanding Advanced Techniques in C++ Assembly

    1. Virtual Function Call Recognition:

      ; Virtual function call trilogy
      mov     rax, [rcx]      ; get vtable pointer
      mov     rax, [rax+28h]  ; get address of the 5th virtual function
      call    rax             ; call virtual function
      
    2. RTTI Information Location:

      ; Type conversion check
      mov     rax, [rcx]      ; vtable pointer
      mov     rax, [rax-8]    ; RTTI information
      cmp     dword ptr [rax], 'MyCl'
      
    3. Template Instantiation Recognition:

      ; Template instantiation function
      ??$min@H@@YAHHH@Z proc  ; int min<int>(int,int)
      cmp     ecx, edx
      mov     eax, edx
      cmovl   eax, ecx
      retn
      ??$min@H@@YAHHH@Z endp
      

    By systematically analyzing these patterns, the assembly code generated by C++ will no longer be mysterious. Just like learning assembly representation for C language back in the day, the key lies in understanding how the compiler works and common idiomatic patterns. Once these rules are mastered, the efficiency of reading C++ reverse-engineered code will significantly improve.

    Leave a Comment