Why Do Windows Executables Fail to Run on Linux? A Deep Dive into Compiler Design and System Architecture

Introduction

Have you ever compiled a C program on Windows and tried to run the executable on a Linux machine, only to get an error? This happens because of fundamental differences in system architecture, executable formats, and compiler design.

I faced this issue when working on the Tiny Shell Lab (Tsh Lab), where I had to implement a small Unix shell. While testing, I realized that compiling and running the shell on Linux worked fine, but attempting to run it on Windows caused compatibility problems. This led me to explore why executables are platform-specific and how system architecture affects program execution.

In this article, we’ll break down the reasons why a Windows executable (.exe) won’t run on Linux, explore the underlying differences in executable formats, and discuss how this issue connects to system programming concepts like process creation and system calls.

How Does Compilation Work?

The Role of the Compiler and Linker

A compiler translates high-level code (C, C++, etc.) into machine code that the operating system can execute. The linker then resolves dependencies and generates an executable file. However, this file is OS-specific because different operating systems have different ways of managing executables, libraries, and system calls.

Two major executable formats are:

  • PE (Portable Executable) – Used by Windows

  • ELF (Executable and Linkable Format) – Used by Linux

Each OS provides its own set of system calls (APIs) for process execution, file handling, and memory management. Even if the compiled code is similar, it cannot run on a different OS because of these underlying differences.

A Simple Example: Compiling a C Program on Windows and Linux

Let’s take a simple C program:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

Now, let’s compile and execute it on different systems:

On Windows (Using MinGW or MSVC Compiler)

gcc hello.c -o hello.exe
  • This produces a .exe file, which is in PE format (Windows-specific).

On Linux (Using GCC or Clang)

gcc hello.c -o hello
  • This produces an ELF binary, which is only compatible with Linux.

If we try to run the Windows .exe file on Linux:

./hello.exe

We get:

bash: ./hello.exe: cannot execute binary file: Exec format error

This error occurs because Linux does not understand Windows’ PE format.

Why Can’t Windows Executables Run on Linux?

1. Different Executable Formats

FeatureWindows (PE)Linux (ELF)
Binary FormatPE (Portable Executable)ELF (Executable and Linkable Format)
System CallsUses Windows APIs (e.g., WriteConsole())Uses Linux syscalls (e.g., write())
Linking MechanismRelies on .dll (Dynamic Link Libraries)Uses .so (Shared Objects)

A Windows .exe expects Windows kernel functions (e.g., kernel32.dll), while a Linux ELF binary relies on Linux syscalls.

2. Different System Calls

A program running on Linux interacts with the OS using Linux system calls (e.g., write(), open()), while Windows uses Windows APIs (e.g., CreateFile()).

Even if the binary instructions are the same, the OS cannot recognize the system calls made by an executable built for another platform.

3. Different Process Management Systems

  • Linux uses fork(), execvp(), and waitpid() to create and manage processes.

  • Windows does not support fork(), instead relying on CreateProcess().

This difference is crucial in system programming and directly affected my experience while working on the Tsh Lab.

How This Problem Appeared in the Tsh Lab

In the Tiny Shell Lab (Tsh Lab), we built a small shell that executes commands like ls, echo, etc. The shell relied on process creation functions like fork() and execvp(), which are POSIX-only functions (Linux and macOS).

Here’s a snippet from the lab:

pid_t pid = fork();
if (pid == 0) { 
    execvp(argv[0], argv); 
    printf("Command not found\n"); 
}
  • fork() creates a child process.

  • execvp() replaces the child process with a new program (e.g., /bin/ls).

On Linux, this works perfectly. However, if you try to compile and run it on Windows, it fails because:

  • Windows does not support fork().

  • Windows does not have /bin/ls, so the paths are incompatible.

  • Signal handling (SIGCHLD) differs between Linux and Windows.

This real-world issue highlighted why system programming concepts like process creation and system calls are critical when writing cross-platform software.

How Can We Run Windows Executables on Linux?

1. Using Wine (Windows Compatibility Layer)

Wine is a compatibility layer that translates Windows API calls into Linux syscalls.

sudo apt install wine
wine hello.exe

This works for many GUI-based applications but has limitations for low-level system programs.

2. Cross-Compiling for Linux on Windows

You can use tools like Cygwin or WSL (Windows Subsystem for Linux) to compile Linux executables on a Windows machine.

For example, using Cygwin:

x86_64-w64-mingw32-gcc hello.c -o hello_linux

Key Takeaways and Conclusion

  1. Executables are OS-specific because of differences in system architecture, executable formats (PE vs. ELF), and system calls.

  2. Even if a C program compiles on Windows, it will not run on Linux without modification.

  3. In the Tsh Lab, the use of fork() and execvp() demonstrated how deeply OS-dependent system calls are.

  4. Solutions like Wine or cross-compilation can help, but true cross-platform compatibility requires careful design choices.

By understanding compiler internals and OS architectures, we can write more portable and efficient software. If you're diving into system programming, OS development, or cloud computing, mastering these concepts will be incredibly valuable!

0
Subscribe to my newsletter

Read articles from Md Khaled Bin Joha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Md Khaled Bin Joha
Md Khaled Bin Joha

A passionate sophomore student from BAIUST, Cumilla. Eager to learn about new things.