Chapter 87: Static Libraries (.a): Creation Process (Archiver ar)

Chapter Objectives

Upon completing this chapter, you will be able to:

  • Understand the compilation pipeline from C source code to object files and executables.
  • Explain the concept of a static library and its role in modular software development.
  • Utilize the GNU ar utility to create, inspect, and manage static library archives (.a files).
  • Implement a modular project by separating code into a reusable static library and a main application.
  • Link an executable program against a custom-created static library to resolve symbols.
  • Debug common linker errors related to static library usage and symbol resolution.

Introduction

In the world of software engineering, efficiency, reusability, and organization are paramount. As projects grow in complexity, managing a single, monolithic codebase becomes untenable. The solution lies in modularity—breaking down a large system into smaller, self-contained, and reusable components. In the context of C and C++ development on Linux systems, one of the most fundamental tools for achieving this modularity is the static library.

This chapter delves into the creation and use of static libraries, which are essentially archives of pre-compiled object code. Think of a static library as a toolbox for a craftsman. Instead of building every tool from scratch for every new project, the craftsman maintains a collection of ready-to-use tools. Similarly, a developer can bundle a set of related, commonly used functions—such as mathematical operations, string manipulations, or custom data structures—into a static library. When building a new application, the developer can simply “link” this library, and the necessary code is copied directly into the final executable. This process not only saves compilation time but also promotes a clean, organized, and reusable code structure.

We will explore the journey from a simple .c source file to a fully linked executable that leverages a custom-built static library. You will learn the role of the compiler in generating intermediate object files and how the archiver (ar) utility bundles these files into a library. Finally, we will cover the crucial linking stage, where the application code is combined with the library code to produce a self-contained, runnable program. By the end of this chapter, you will have the practical skills to create and integrate your own static libraries, a foundational technique for any serious systems programmer.

Technical Background

To fully appreciate the role and function of static libraries, we must first understand the broader context of how a program is built from source code on a Linux system. This process is a multi-stage pipeline involving several key tools, each with a distinct responsibility.

The Compilation Pipeline: From Source to Object Code

When you invoke the GNU C Compiler (gcc) to build a program, it orchestrates a sequence of four distinct steps: preprocessing, compilation, assembly, and linking. For our discussion on libraries, the most critical outputs are the intermediate object files.

An object file, which typically has a .o extension, is a file containing machine code—the raw binary instructions that a CPU can execute. However, it is not yet a complete, runnable program. It is a relocatable format, meaning the code and data within it are not tied to absolute memory addresses. More importantly, if the source code made calls to functions defined in other files (like printf from the C standard library or a custom function from another part of your project), the object file will contain “unresolved symbols.” These are essentially placeholders or promises, noting that the code for a particular function needs to be found and inserted later.

To generate only the object file without proceeding to the final linking stage, you use the -c flag with the compiler. For instance, the command gcc -c my_function.c will produce an object file named my_function.o. This file contains the compiled machine code for the functions defined within my_function.c, along with a symbol table that lists the functions it provides (defines) and the functions it requires (the unresolved symbols).

This ability to stop the compilation process at the object file stage is the cornerstone of modular development. It allows us to compile individual components of a large project independently. If you change one source file, you only need to recompile that single file into a new object file, not the entire project. The real magic happens in the final step: linking, where all these individual object files are stitched together.

%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    subgraph "Compilation Pipeline"
    direction TB;
        A[<b>C Source File</b><br><i>my_function.c</i>] --> B{Preprocessor};
        B --> C["<b>Processed Source</b><br><i>(Expanded Macros/Includes)</i>"];
        C --> D{Compiler};
        D --> E[<b>Assembly Code</b><br><i>my_function.s</i>];
        E --> F{Assembler};
        F --> G["<b>Object File (.o)</b><br><i>my_function.o</i><br><br>Contains machine code<br><b>but has unresolved symbols</b>"];
    end

    %% Styling
    style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff
    style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style C fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937
    style D fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style E fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937
    style F fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style G fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937

The Concept of a Library: A Collection of Object Files

Imagine a large software project, perhaps a web server or a data analysis tool, composed of hundreds of source files. Compiling each one into an object file is a good first step, but the final linking command would be unwieldy, requiring you to list every single .o file. Furthermore, if you wanted to share a set of utility functions (e.g., for logging or network communication) across multiple projects, you would have to copy and manage the relevant object files for each one.

This is the problem that libraries solve. A library is simply a single file that contains a collection of object files. It provides a convenient way to distribute and use pre-compiled code. By bundling related object files together, we create a single, manageable unit. When linking an application, instead of listing dozens of individual .o files, you can just reference the library file.

There are two main types of libraries in Linux: static and dynamic (or shared). This chapter focuses on static libraries. The key characteristic of a static library is that when you link your program against it, the linker identifies which object files from the library are needed to resolve the symbols in your main program, and then it copies the code from those object files directly into your final executable file. The result is a larger, but completely self-contained, application. It has no external library dependencies to run, which can simplify deployment. The library file itself, typically ending in .a (for “archive”), is only needed during compilation; it is not needed to run the final program.

The Archiver ar: The Tool for Building Static Libraries

The standard utility for creating and manipulating static libraries on Unix-like systems is ar, which stands for “archiver.” Despite its simple purpose, ar is a powerful and venerable tool with a long history, predating even the C language in its earliest forms. Its primary function is to create a single archive file from a set of input files. While it can be used for any type of file, its most common use by far is to create static libraries from object files.

A static library file created by ar is little more than a concatenation of the object files it contains, with a header at the beginning that acts as an index or table of contents. This index maps symbol names (like function names) to the specific object file within the archive where that symbol is defined. When the linker (ld, which is usually invoked by gcc) is given a static library to process, it consults this index. For each unresolved symbol in the main program, the linker scans the library’s index. If it finds the symbol, it extracts the entire corresponding object file from the archive and includes it in the final executable, just as if you had listed that .o file on the command line directly.

The most common ar command for creating a static library uses three flags: rc, and s.

  • The r flag instructs ar to insert the specified files into the archive, replacing any existing files with the same name.
  • The c flag tells ar to create the archive if it doesn’t already exist, suppressing the warning message it would otherwise produce.
  • The s flag is crucial for efficiency. It writes an object-file index into the archive, which is the very table of contents the linker uses to quickly look up symbols. Without this index, the linker would have to scan every object file inside the library sequentially, which would be much slower.

For example, to create a static library named libutils.a from log.o and network.o, you would use the command:

ar rcs libutils.a log.o network.o

This command creates libutils.a, a single file that packages the compiled code from both log.o and network.o, complete with an index for fast symbol lookup by the linker.

The Linking Process with a Static Library

Once you have your main program compiled into an object file (e.g., main.o) and your utility functions bundled into a static library (e.g., libutils.a), the final step is to link them together into a single executable.

You instruct gcc to use your library with two special flags: -L and -l.

  • The -L flag tells the linker which directories to search for library files. For example, -L/path/to/libs adds that directory to the search path. A common practice during development is to place the library in the current directory, specified with -L..
  • The -l flag specifies the name of the library to link against. There is a naming convention here: the linker expects library files to be named lib<name>.a. When you use the -l flag, you omit the lib prefix and the .a suffix. So, to link against libutils.a, you would use the flag -lutils.

Putting it all together, the command to build the final application would be:

gcc main.o -L. -lutils -o my_app

When this command is executed, the linker performs the following steps:

  1. It processes main.o and identifies its unresolved symbols (e.g., calls to functions that were defined in log.c or network.c).
  2. It looks at the -L. flag and adds the current directory to its list of places to search for libraries.
  3. It sees the -lutils flag and searches for a file named libutils.a in its search paths.
  4. Upon finding ./libutils.a, it reads the library’s symbol index.
  5. For each unresolved symbol from main.o, it finds the corresponding entry in the library’s index, identifies which object file (log.o or network.o) contains the definition, and copies that entire object file’s code and data into the final executable.
  6. Once all symbols are resolved, it generates the final, self-contained executable file, my_app.

A critical detail in the linking process is the order of arguments. The linker processes its input files (object files and libraries) in the order they appear on the command line. It maintains a list of currently unresolved symbols. When it encounters a library, it only pulls in object files that satisfy a symbol on that current list. Therefore, you should always place your libraries after the object files that depend on them. The command gcc -L. -lutils main.o -o my_app would likely fail, because when the linker processes libutils.a, it doesn’t yet know about the unresolved symbols in main.o, so it pulls nothing from the library. When it later processes main.o, the symbols become unresolved, but it’s too late.

%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    A[<b>Start: Linker Command</b><br><i>gcc main.o -L. -lutils -o my_app</i>] --> B{Process main.o};
    B --> C{"Identify Unresolved Symbols<br><i>e.g., log(), network()</i>"};
    C --> D{Process -L. Flag};
    D --> E[Add current directory '.'<br>to library search path];
    E --> F{Process -lutils Flag};
    F --> G{Search for <b>libutils.a</b><br>in search paths};
    G --> H{Found?};
    H -- No --> I["<b>Linker Error:</b><br>"cannot find -lutils""];
    H -- Yes --> J[Read Symbol Index<br>from libutils.a];
    J --> K{For each unresolved symbol...};
    K --> L{Find symbol in library index?};
    L -- Yes --> M["Extract corresponding .o file<br><i>(e.g., log.o)</i> from archive"];
    M --> N[Copy code from .o file<br>into final executable];
    N --> K;
    L -- No --> O[Leave symbol unresolved];
    O --> K;
    K -- All symbols checked --> P{All symbols now resolved?};
    P -- No --> Q["<b>Linker Error:</b><br>"undefined reference to symbol_name""];
    P -- Yes --> R[<b>Success!</b><br>Generate final executable file: <i>my_app</i>];

    %% Styling
    style A fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff
    style B fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style C fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff
    style D fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style E fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937
    style F fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style G fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style H fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff
    style I fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff
    style J fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style K fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style L fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff
    style M fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style N fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff
    style O fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937
    style P fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff
    style Q fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff
    style R fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff

Practical Examples

Theory provides the foundation, but true understanding comes from hands-on practice. In this section, we will walk through the complete process of creating a simple modular application. The project will consist of a small math library providing basic arithmetic functions, and a main program that uses this library to perform a calculation.

Project Structure

First, let’s define the file structure for our project. Create a main directory, math_project, and organize your files as follows. This separation keeps the library source code distinct from the application source code.

Plaintext
math_project/
├── app/
│   └── main.c
├── lib/
│   ├── include/
│   │   └── mymath.h
│   └── src/
│       ├── add.c
│       ├── subtract.c
└── Makefile

This structure is a common pattern in C projects. The lib directory contains everything related to our static library: public headers in include and source code in src. The app directory contains the source for the final executable. The Makefile at the root will automate the entire build process.

Step 1: Creating the Library Source Files

Let’s create the source files for our math library.

Header File (lib/include/mymath.h)

This public header file declares the functions that our library will provide to the outside world. Any application wanting to use our library will include this file.

C
// lib/include/mymath.h

#ifndef MYMATH_H
#define MYMATH_H

/**
 * @brief Adds two integers.
 * @param a The first integer.
 * @param b The second integer.
 * @return The sum of a and b.
 */
int add(int a, int b);

/**
 * @brief Subtracts the second integer from the first.
 * @param a The first integer.
 * @param b The second integer.
 * @return The result of a - b.
 */
int subtract(int a, int b);

#endif // MYMATH_H

Source Files (lib/src/add.c and lib/src/subtract.c)

These files contain the actual implementation of our library functions.

C
// lib/src/add.c

#include "mymath.h"

int add(int a, int b) {
    return a + b;
}
```c
// lib/src/subtract.c

#include "mymath.h"

int subtract(int a, int b) {
    return b - a; // A deliberate, easy-to-spot bug for later.
}

Note: We have intentionally introduced a bug in subtract.c. It calculates b - a instead of a - b. This will be useful later when we discuss updating libraries.

Step 2: Creating the Main Application

Now, let’s write the main program that will use our library.

Application Source (app/main.c)

C
// app/main.c

#include <stdio.h>
#include "mymath.h" // Include the library's header

int main() {
    int x = 20;
    int y = 10;

    printf("Starting calculations...\n");

    int sum = add(x, y);
    printf("Sum of %d and %d is: %d\n", x, y, sum);

    int diff = subtract(x, y);
    printf("Difference of %d and %d is: %d\n", x, y, diff);
    
    printf("Calculations finished.\n");

    return 0;
}

Notice that main.c includes mymath.h. This gives it access to the function declarations, allowing the compiler to type-check the calls to add() and subtract().

Step 3: Compiling the Library into Object Files

Before we can create the library, we must compile its source files (add.csubtract.c) into object files (add.osubtract.o). We use the -c flag to stop after the compilation stage. It’s also good practice to use the -I flag to tell gcc where to find include files, in this case, our lib/include directory.

Execute these commands from the math_project root directory:

Bash
# Compile add.c into add.o
gcc -c lib/src/add.c -Ilib/include -o add.o

# Compile subtract.c into subtract.o
gcc -c lib/src/subtract.c -Ilib/include -o subtract.o

After running these commands, you will have add.o and subtract.o in your root directory.

Step 4: Creating the Static Library with ar

With our object files ready, we can now use the ar tool to bundle them into a static library. We will name our library libmymath.a, following the standard lib<name>.a convention.

Bash
# Create the static library from the object files
ar rcs libmymath.a add.o subtract.o

Let’s break down the ar command:

Flag Description
r Replace: Inserts the specified object files into the archive. If an object file with the same name already exists, it is replaced.
c Create: Creates the archive file if it does not already exist. This suppresses the warning that `ar` would normally show.
s Symbol Index: Creates or updates the archive’s symbol index. This is crucial for the linker to efficiently find symbols.

You now have a libmymath.a file. This is your complete static library.

You can inspect its contents using the -t flag (to list files) and the nm utility (to list symbols):

Bash
# List the object files contained within the library
$ ar -t libmymath.a
add.o
subtract.o

# List the symbols from the object files in the library
$ nm libmymath.a

add.o:
0000000000000000 T add

subtract.o:
0000000000000000 T subtract

The T in the nm output indicates that add and subtract are defined in the text (code) section of their respective object files.

Step 5: Compiling and Linking the Main Application

The final step is to compile our main.c and link it with libmymath.a.

Bash
# Compile main.c and link against libmymath.a
gcc app/main.c -Ilib/include -L. -lmymath -o my_app

Let’s dissect this crucial command:

Flag Purpose Example
-I<dir> Tells the preprocessor where to search for header files (e.g., mymath.h). -Ilib/include
-L<dir> Tells the linker which additional directories to search for library files. -L. (for current directory)
-l<name> Tells the linker to link with a specific library. The linker looks for a file named lib<name>.a. -lmymath (for libmymath.a)
-o <file> Specifies the name for the output file (the final executable). -o my_app

Step 6: Running and Verifying the Application

If all commands were successful, you will have a final executable named my_app. Run it:

Bash
$ ./my_app
Starting calculations...
Sum of 20 and 10 is: 30
Difference of 20 and 10 is: -10
Calculations finished.

The sum is correct (20 + 10 = 30). However, the difference is -10, which exposes the bug we planted earlier (10 - 20). This provides a perfect opportunity to demonstrate how to update a library.

Step 7: Updating the Library

Let’s fix the bug in subtract.c.

Corrected lib/src/subtract.c

C
// lib/src/subtract.c

#include "mymath.h"

int subtract(int a, int b) {
    return a - b; // Corrected logic
}

Now, we only need to recompile subtract.c and update the library. We don’t need to touch add.c or main.c.

1. Recompile the changed file:

Bash
gcc -c lib/src/subtract.c -Ilib/include -o subtract.o

2. Update the library with the new object file:

Bash
ar rcs libmymath.a subtract.o


The r flag ensures that the existing subtract.o inside libmymath.a is replaced with this new version.

3. Re-link the application:

Bash
gcc app/main.c -Ilib/include -L. -lmymath -o my_app

4. Run the updated application:

Bash
$ ./my_app Starting calculations... Sum of 20 and 10 is: 30 Difference of 20 and 10 is: 10 Calculations finished.

The output is now correct. This workflow demonstrates the power of modularity: a change in one component required only a partial rebuild, saving time and reducing complexity.

Common Mistakes & Troubleshooting

When working with static libraries, developers often encounter a few common pitfalls, most of which result in linker errors. Understanding these issues will help you debug your projects more effectively.

Mistake / Issue Symptom(s) Troubleshooting / Solution
Undefined Reference Linker error:
undefined reference to `function_name'`
This is the most common error. Check for:
  1. Incorrect Linking Order: The library must come after the object file that uses it.
    Mistake: gcc -lmymath main.o
    Solution: gcc main.o -lmymath
  2. Missing Library Flag: Forgetting to include the -l<name> flag entirely.
  3. Missing Library Path: Forgetting to tell the linker where to look with -L<dir>.
Cannot Find Library Linker error:
/usr/bin/ld: cannot find -l<name>
The linker can’t find the lib<name>.a file.
  • Verify the library file (e.g., libmymath.a) actually exists.
  • Ensure you provided the correct path with the -L flag. Use -L. if the library is in the current directory.
Stale Executable You fixed a bug in the library, rebuilt it with ar, but the old buggy behavior persists when you run the program. Updating the .a file does not automatically update the executable. The library code was copied during the initial linking.

Solution: You must always re-link your application after any change to the static library to include the updated code.
gcc main.o -L. -lmymath -o my_app
Mismatched Prototypes Program links successfully but crashes at runtime or gives nonsensical results (e.g., wrong values, corrupted data). The function declaration in the .h file doesn’t match the definition in the .c file (e.g., different argument types or return types).

Solution:
  • Always compile with warnings enabled: -Wall -Wextra. The compiler is very good at catching these issues.
  • Ensure every source file includes the relevant headers.

Exercises

These exercises are designed to reinforce the concepts covered in this chapter.

  1. Create a “Greeter” Library
    • Objective: Build a simple static library with a single function.
    • Steps:
      1. Create a header file greeter.h that declares a function void say_hello(const char* name);.
      2. Create a source file greeter.c that implements this function to print “Hello, [name]!”.
      3. Compile greeter.c into greeter.o.
      4. Create a static library libgreeter.a containing greeter.o.
      5. Write a main.c that calls say_hello("World");.
      6. Compile and link main.c with your libgreeter.a to create an executable.
    • Verification: Run the executable. It should print “Hello, World!”.
  2. String Utilities Library
    • Objective: Create a library with multiple functions and use it.
    • Steps:
      1. Design a small string utility library (libstrutil.a).
      2. Implement two functions: int string_length(const char* str); and void string_reverse(char* str); in separate .c files.
      3. Compile them into object files and create the static library.
      4. Write a main program that uses both functions on a test string and prints the results.
    • Verification: The program should correctly report the string’s length and then print its reversed form.
  3. Refactoring a Monolithic Program
    • Objective: Practice modularizing an existing codebase.
    • Steps:
      1. Start with a single main.c file that contains main() and three helper functions (e.g., parse_configvalidate_inputprocess_data).
      2. Refactor the code: move the three helper functions into their own .c files and declare them in a new header file, helpers.h.
      3. Create a static library, libhelpers.a, from the helper object files.
      4. Modify the original main.c to include helpers.h and remove the helper function implementations.
      5. Link the modified main.o with libhelpers.a.
    • Verification: The final executable should have the exact same behavior as the original monolithic program.
  4. Investigating Executable Size
    • Objective: Observe the effect of static linking on file size.
    • Steps:
      1. Use the math_project from the chapter examples.
      2. Compile and link the application with the static library as before. Note the size of the final my_app executable using ls -l my_app.
      3. Now, compile the program as a single unit without using a library: gcc app/main.c lib/src/add.c lib/src/subtract.c -Ilib/include -o my_app_mono.
      4. Note the size of my_app_mono.
    • Verification: Compare the file sizes. They should be identical or extremely close. This demonstrates that static linking effectively copies the needed object code directly into the executable, resulting in a file size equivalent to compiling all sources together.
  5. Handling Library Dependencies
    • Objective: Understand how to link when one static library depends on another.
    • Steps:
      1. Create a liblog.a with a log_message(const char* msg) function.
      2. Create a libcore.a with a function do_work() that internally calls log_message().
      3. Write a main() that calls do_work().
      4. Attempt to link in this order: gcc main.o -lcore -llog. Note the result.
      5. Now, try the reverse order: gcc main.o -llog -lcore. Does it work?
      6. The standard solution is to list dependencies after the library that needs them: gcc main.o -lcore -llog.
    • Verification: The link should only succeed when the library providing the symbol (liblog.a) is listed after the library that requires it (libcore.a).

Summary

This chapter provided a comprehensive introduction to the creation and use of static libraries in a Linux environment. By mastering these concepts, you gain a fundamental skill for writing modular, reusable, and maintainable code.

  • Object Files (.o) are the output of the compiler and contain relocatable machine code and a symbol table. They are the basic building blocks of programs.
  • Static Libraries (.a) are archives of object files, created with the ar utility. They serve as a convenient way to package and distribute related, pre-compiled code.
  • The Archiver (ar rcs ...) is the command-line tool used to create and manage these archives. The rcs flags are standard practice for creating a library with a symbol index.
  • Linking is the final stage of compilation where object files and library code are combined to create an executable. The linker resolves symbols by pulling needed object files from static libraries and copying their code into the final program.
  • Linker Flags are essential for this process: -L<dir> tells the linker where to search for libraries, and -l<name> specifies the name of the library to link against.
  • Modularity is the key benefit. Static libraries allow you to organize code into logical units, share code across projects, and reduce compilation times by only rebuilding what has changed.

Further Reading

  1. ar Manual Page: The definitive source for all options and behaviors of the archiver. Access it on your system with man ar. https://man7.org/linux/man-pages/man1/ar.1.html
  2. gcc Manual Page: Contains detailed information on the compiler and linker flags, including -c-I-L, and -l. Access with man gcc.
  3. https://man7.org/linux/man-pages/man1/gcc.1.html
  4. An Introduction to GCC – Chapter 4: Libraries for Programmers: A well-written, accessible guide to using libraries with GCC. (Available online, search for “An Introduction to GCC Brian Gough”).
  5. Linkers and Loaders by John R. Levine: A classic, in-depth book covering all aspects of how linkers work. While highly detailed, the early chapters provide an unparalleled foundation on the topic.
  6. How To Write Shared Libraries by Ulrich Drepper: Although this paper focuses on shared (dynamic) libraries, its initial sections provide one of the best explanations of the linking process, symbols, and relocation, which is fundamental to understanding static libraries as well.
  7. The System V Application Binary Interface (ABI): For the truly adventurous, this specification defines the formats for object files (ELF) and the conventions for linking on many Unix-like systems, including Linux. https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top