Search notes:

EXE: entry point

The entry point of an exe (as well as of a dll) is specified with the linker option /entry.
With the GNU linker, the option to specify the entry point is --entry or -e.

Signature and first (and only?) argument: PEB

As per RbMm, the entry point function has one parameter: a pointer to the PEB.
Further, this user suggests that the entry point has the following signature
ULONG __stdcall(*)(void* PEB)
The following example tries to demonstrate that the first argument (which is passed in rcx in Win-X64) does actually point to the process environment block (PEB).

entry-point.c

The function printAddress prints the value of the first parameter of the entry-point function, the value of [gs]:60h, the same value as returned by the Microsoft C-Compiler intrinsic function __readgsqword (see also peb.c) and the value of the PEB as returned by NtQueryInformationProcess. These four values should all be equal when the program is run.
#include <windows.h>
#include <winternl.h>

void* get_gs_60();

HANDLE stdOut;

void* get_peb_address() {

   typedef NTSTATUS (NTAPI *ptrNtQueryInformationProcess)
   (
       HANDLE           ProcessHandle,
       PROCESSINFOCLASS ProcessInformationClass,
       PVOID            ProcessInformation,
       ULONG            ProcessInformationLength,
       PULONG           ReturnLength
    );

    ptrNtQueryInformationProcess qry = (ptrNtQueryInformationProcess) GetProcAddress(
       GetModuleHandleA("ntdll.dll"),
      "NtQueryInformationProcess"
    );

    HANDLE proc = GetCurrentProcess();
    PROCESS_BASIC_INFORMATION pbi;

    qry(proc, 0, &pbi, sizeof(pbi), NULL);
    return pbi.PebBaseAddress;
}


void printAddress(const char* text, void* addr) {

  char buf[200];

  int len = wsprintfA(buf, "%-20s: %p\n", text, addr);

  DWORD charsWritten;
  WriteConsoleA(stdOut, buf, len, &charsWritten, 0);

}


int __stdcall entryPoint(void* first_arg) {

    stdOut = GetStdHandle(STD_OUTPUT_HANDLE);

 //
 // Use an compiler intrinsic function to get a pointer to
 // the PEB:
 //
    DWORD64 intr = __readgsqword(0x60);

 //
 // Get the same pointer with «ordinary» assembly:
 //
    void* gs_60  = get_gs_60();

 // 
 // Alternatively, use WinAPI functions:
 //
    void* PEB   = get_peb_address();

    printAddress("First argument", first_arg);
    printAddress("gs:[60h]"      , (void*) gs_60);
    printAddress("intrinsic"     , (void*) intr);
    printAddress("PEB"           , PEB);

    return 42;
}

go_60.asm

go_60.asm is an assembler source file whose only function get_gs_60 returns the value of [gs]:60h so that this value can be printed with printAddress.
_TEXT  SEGMENT

PUBLIC get_gs_60

get_gs_60 PROC

    mov rax, qword ptr gs:[60h]
    ret

get_gs_60 ENDP

_TEXT  ENDS
END

Compilation and linking

The two source files can be compiled and linked like so:
cl   /nologo /GS- /c entry-point.c
ml64 /nologo      /c gs_60.asm

link /nologo /entry:entryPoint /nodefaultlib /subsystem:console /machine:x64 entry-point.obj gs_60.obj kernel32.lib user32.lib /out:entry-point.exe

Executing the program

When the program is executed, it prints the same address four times, thus giving evidence that indeed the first parameter of the entry point function points to the PEB:
First argument      : 000000975583D000
gs:[60h]            : 000000975583D000
intrinsic:          : 000000975583D000
PEB                 : 000000975583D000

See also

In Debugging Tools for Windows, the entry point is referred to by the pseudo register $exentry.

Index