1. Linker Plugins

1.1. Introduction

Linker plugins make it possible to run user-defined code during the link process. Implemented as a C++ class, they are a programmatical way to add new functionalities and modify default linker behavior.

Plugins provide more control and flexibility over the output image layout as compared to the linker scripts. For complicated rules, linker scripts are often very cumbersome to write, sometimes desired rule matching behavior might even be impossible from just linker script. For example, a plugin can move around chunks from one output section to another. Linker plugins also allow the user to alter program layout dynamically which is not possible using linker script.

Plugins add custom behavior to the linker by defining behavior for the hooks that the linker runs in various stages of the link process. These hooks allows plugins to modify the default linker behavior, provide a fine-grained control over the output image layout, add new functionalities to the linker.

This guide will describe all that you need to know about linker plugins, and how to effectively write one yourself. This guide assumes that the reader has a basic understanding of linkers, linker relocations and linker scripts.

1.3. Plugin Interface Types

ELD has different plugin interfaces. Each plugin interface provides different functionalities. Each plugin interface provides hooks at specific points in the linker and designed for specific use cases. For example, SectionMatcher plugin interface type provide hooks to process input sections, whereas OutputSectionIterator provides hooks to process output sections. Currently, there are 6 plugin interfaces, more interfaces will be added based on new use-cases.

For each plugin interface, there is a specific plugin interface class. To create a plugin of a specific plugin interface, users have to create a class that inherits the corresponding plugin interface class. The user plugin class needs to provide implementation for the hooks by overriding virtual member functions.

A plugin can have functionalities of multiple plugin interfaces by inheriting from multiple plugin interface classes.

The 6 plugin interfaces along with their corresponding C++ class and header file are listed below.

Plugin interface

Plugin interface class

Header file

LinkerPlugin

LinkerPlugin

ELD/PluginAPI/LinkerPlugin.h

SectionMatcher

SectionMatcherPlugin

ELD/PluginAPI/SectionMatcherPlugin.h

SectionIterator

SectionIteratorPlugin

ELD/PluginAPI/SectionIteratorPlugin.h

ControlFileSize

ControlFileSizePlugin

ELD/PluginAPI/ControlFileSizePlugin.h

ControlMemorySize

ControlMemorySizePlugin

ELD/PluginAPI/ControlMemorySizePlugin.h

OutputSectionIterator

OutputSectionIteratorPlugin.h

ELD/PluginAPI/OutputSectionIteratorPlugin.h

1.5. Running a linker plugin

There are two ways to run linker plugins:

  • Adding a plugin invocation command in a linker script.

  • Specifying plugin configuration file using –plugin-config option.

These ways are also called plugin load specification, as they specify how to load a plugin by the linker.

1.5.1. Adding a plugin invocation command in a linker script.

<PluginInterfaceKeyword>("LibraryName", "PluginName" [, "PluginOption"])

For output section plugin types, ControlMemorySizePlugin and ControlFileSizePlugin, users should add the plugin invocation command to the output section description of the output section on which these plugins should run. For example:

SECTIONS {
  output_section <PluginInterfaceKeyword>("LibraryName", "PluginName" [, "PluginOption"]) : {
    *(.text)
  }
}

Note

Only one output section plugin can be attached to an output section.

  • PluginInterfaceKeyword

    Each plugin interface type has a corresponding linker script plugin interface keyword.

    Plugin interface type

    Linker script keyword

    SectionMatcher

    PLUGIN_SECTION_MATCHER

    SectionIterator

    PLUGIN_ITER_SECTIONS

    ControlFileSize

    PLUGIN_CONTROL_FILESZ

    ControlMemorySize

    PLUGIN_CONTROL_MEMSZ

    OutputSectionIterator

    PLUGIN_OUTPUT_SECTION_ITER

    LinkerPlugin

    LINKER_PLUGIN

  • LibraryName

    • Name of the dynamic library that contains the plugin for the linker to load.

    • Finds the library in the same search paths as if the library was passed as an input to the linker.

    • Uses the name of the library without the lib prefix on Linux and without the .so/.dll suffix on Linux/Windows, respectively

  • PluginName

    Name of the plugin. The linker queries the dynamic library to provide an implementation of the plugin with the name PluginName for the specified interface type.

  • PluginOption

    It can be used to pass an option to the plugin.

1.5.2. Specifying plugin configuration file using --plugin-config option

--plugin-config option takes a plugin configuration yaml file. Plugin configuration file should contain a GlobalPlugins list. Elements of the GlobalPlugins list defines which plugins should be loaded. Each value of the list is an object containing all the information required to load and initialize a specific plugin.

Plugin configuration file format should be as follows::

---
GlobalPlugins:
  - Type: PluginInterfaceType
    Name: PluginName
    Library: LibraryName
    Options: PluginOption

OutputSectionPlugins:
  - OutputSection : OutputSectionName
    Type : PluginInterfaceType
    Name : PluginName
    Library : LibraryName
    Options : PluginOption

GlobalPlugins list can specify any number of elements. Options member is optional.

Library name should be specified without the lib prefix on Linux and without the .so/.dll suffix on Linux/Windows

ControlMemorySizePlugin and ControlFileSizePlugin are output section plugins. Therefore, in the plugin configuration file, they need to be added to OutputSectionPlugins list.

Note

Only one output section plugin can be attached to an output section.

1.5.3. Running plugins which inherits multiple plugin interface classes

Plugins can inherit from multiple plugin interface classes to have functionalities of multiple plugin interfaces. For such plugins, the functionalities of each plugin interface, from which the plugin inherits, need to be selectively enabled. This means:

  • In the case of a linker script, there should be a separate plugin invocation command for each plugin interface, from which the plugin inherits.

  • In the case of plugin configuration file, there should be an entry in the GlobalPlugins list for each plugin interface type, from which the plugin inherits.

1.6. User Plugin Workflow

The following steps describe how to develop a plugin:

  1. Determine the appropriate plugin interfaces for your plugin.

A plugin type can be one of LinkerPlugin, SectionIterator, SectionMatcher, OutputSectionIterator, ControlMemorySize, and ControlFileSize.

A plugin should ideally follow the unix-philosophy of doing one thing well. You should generally try to avoid inheriting from multiple plugin interfaces if possible. It generally leads to more complicated code, and a plugin that does more than one thing. But that being said, if you do think functionalities of multiple plugin interfaces will improve your plugin’s design and readability, then you should go ahead with inheriting from multiple plugins.

You can also create and chain multiple plugins for your task, similar in ideology to how unix utilities operate, where action of one plugin depends on the action and result of the previous one.

  1. Create a C++ source file that will contain your plugin(s).

Plugins source file needs to include the headers of the plugin interface(s) that the plugin(s) inherits from. Plugin interfaces header files are contained in, ${HEXAGON_TOOLCHAIN}/Tools/include/ELD/PluginAPI.

  1. Create a C++ class that inherits from the appropriate plugin interface(s) for each plugin.

  2. Override virtual functions.

Virtual functions are the means the linker works with the plugins. Each plugin interface provides hooks at various place in the linker codebase, the hooks functionalities are implemented by overriding virtual functions. There are also other virtual functions that do not implement functionalities for the hooks, but are required to work with the plugin infrastructure. Example of these virtual functions are: Plugin::GetName, Plugin::GetLastError, Plugin::GetLastErrorAsString etc.

  1. Associate the plugins with unique names.

  2. Build a shared library for the source file.

In a unix environment, shared library for the plugins can be created as:

# Compile the plugins source file
clang++ -c -I${HEXAGON_TOOLCHAIN_ROOT}/Tools/include ${SOURCE_BASENAME}.cpp -fPIC -stdlib=libc++

# Link the plugin library with linker wrapper library, LW.
clang++ -shared ./${SOURCE_BASENAME}.o -L${HEXAGON_TOOLCHAIN_ROOT}/Tools/lib -lLW -stdlib=libc++ -o lib${SOURCE_BASENAME}.so
  1. Define RegisterAll function in C linkage.

RegisterAll function goal is to register the plugins contained in the file. Registering a plugin here simply means to initialize a plugin object.

Linker calls this function for each plugin library. This function should creates a plugin object for each plugin that the library contains.

  1. Define a getPlugin(const char *pluginName) function in C linkage.

getPlugin(const char* plugin) function goal is to return the requested plugin. Each plugin in the plugin source file should be uniquely identifiable by a name.

Linker calls this function at the time of loading plugins to get a plugin object of plugin named pluginName.

  1. Make the plugin report its API version

Linker verifies linker plugin compatibility. Linker will report an error and refuse to load a plugin if it determines that it is not compatible.

Linker plugin compatibility is determined using Linker API version. Plugin API version consists of major and minor version numbers. A plugin is compatible with the linker if plugin’s major API version is equal to the linker major API version and plugin’s minor API version is equal or lower to the linker’s minor API version. The major API version is incremented when a existing functionality in the API is changed in an incompatible way. Such changes are expected to be rare. The minor API version is incremented when new functionality is added to the Plugin API. Note that Plugin API versions are distinct from linker release version numbers. The current linker Plugin API version is reported by the --version command line option.

For the versioning mechanism to work, each plugin must report its API version by exporting the getPluginAPIVersion(unsigned int *Major, unsigned int *Minor) function (with C linkage). For convenience, this function is defined in the header file PluginVersion.h and including this file in one of the plugin’s C++ source files will automatically make the plugin report its correct API version.

Starting with version 19.0, each plugin must report its API version. Linker will refuse to load a plugin that does not report its version.

  1. (Optionally) Define a getPluginConfig(const char *pluginName) in C linkage to return the configuration object of the requested plugin.

  2. (Optionally) Define a Cleanup function in C linkage.

This function is called at the time unloading plugins. This function generally deletes any allocated memory that was required for the lifetime of the plugin library.

1.7. How the linker runs plugin

Linker performs the following operations to load, run and unload plugins.

  1. Finds all the plugins, from linker script and plugins configuration file, that needs to be attached to the link process.

  2. Loads all the specified plugin libraries.

    1. To find plugin libraries, LD_LIBRARY_PATH environment variable is used on unix environment.

    2. Standard method for searching dynamic libraries is used in Windows.

  3. Calls RegisterAll function from each plugin library. This function should ideally create the plugin objects for each plugin defined in the library.

  4. Calls getPlugin(const char *pluginName) function for each plugin.

    This function returns an appropriate plugin object for pluginName plugin. This object is used to run the plugin.

  5. Calls getPluginConfig(const char *pluginName) function, if available, for each plugin. This function returns an appropriate plugin configuration object for pluginName plugin.

  6. Inspects the plugin to verify that the plugin load specification and plugin type have same plugin interface type.

  7. Initializes each plugin at their Init hook point by calling Plugin::Init(std::string Option) virtual function. Option argument is optionally specified by the plugin user at the plugin load specification.

  8. Calls Cleanup function, if available, for each plugin library. This function should ideally delete any allocated memory that was required for the lifetime of the plugin library.

  9. Unload the plugins and the plugin libraries.

1.8. Tracing plugins

Plugin workflow can be traced using the option --trace=plugin.

This option informs the linker to emit detailed diagnostics for all the plugins.

A sample trace output is shown below:

...
...
Note: Registration function found RegisterAll in Library libDiagOpt.so
Note: Plugin handler getPlugin found in Library libDiagOpt.so
Note: Cleanup function found Cleanup in Library libDiagOpt.so
Note: Plugin Config function getPluginConfig found in library libDiagOpt.so
Note: Registering all plugin handlers for plugin types
Note: Found plugin handler for plugin type DIAGRELOCATION in Library libDiagOpt.so
Note: Initializing Plugin libDiagOpt.so, requested by Plugin DIAGRELOCATION having Name DIAGOPT
...
...
Note: Plugin DIAGRELOCATION Destroyed

<-------------------------> DiagOpt::Destroy() AfterLayout<------------------------->
Note: Unloaded Library libDiagOpt.so.16

1.10. Deep Dive into the Plugin Interfaces

1.10.1. Plugin class

This is the base plugin class for all the plugin interface classes. It provides common functionalities to all the plugin interfaces. This class can not be directly used by a user.

Warning

doxygenclass: Cannot find class “eld::plugin::Plugin” in doxygen xml output for project “ELD” from directory: /local/mnt/workspace/partaror/eld/formal/obj/tools/eld/docs/userguide/api_docs/xml

Please note, that even though the virtual functions – Plugin::Init, Plugin::Run and Plugin::Destroy are available in all the plugin interfaces, they map to different hooks in the linker for different plugin interfaces. For example, Run hook of SectionIteratorPlugin` is not equivalent to the Run hook of OutputSectionIteratorPlugin even though in both cases, the hooks functionality is defined by overriding the Run virtual function. For the base Plugin, these functions are not mapped to any hook in the linker.

1.10.2. Linker Plugin Interface

LinkerPlugin interface is the simplest plugin interface type that can be used to create plugins. It provides three hooks: Init, Run and Destroy in the linker. To define behavior of these hooks, the functions, Plugin::Init, Plugin::Run and Plugin::Destroy must be overridden.

It is called into action after linker performs garbage-collection of input sections and symbols, and before merging input sections. Merging input sections means merging all the input sections that are mapped to the same input section description. Link state throughout the plugin run is eld::plugin::LinkerWrapper::BeforeLayout.

LinkerPlugin flow

Linker script keyword for LinkerPlugin interface is LINKER_PLUGIN. Therefore, to run a LinkerPlugin using a linker script, add the following line to the linker script:

LINKER_PLUGIN("LibraryName", "PluginName" [, "PluginOption"])

1.10.3. Section Matcher Interface

SectionMatcherPlugin interface iterates input sections. It provides four hooks: Init, ProcessSection, Run and Destroy. To define the behavior of these hooks, the functions Plugin::Init, SectionMatcher::ProcessSection and Plugin::Destroy must be overridden.

ProcessSection(Section S) callback hook function is called for each input section.

SectionMatcherPlugin is called into action after the linker reads input files and performs section rule-matching, but before garbage collection. As a consequence, garbage-collection information cannot be accessed during SectionMatcherPlugin run. Link state throughout the plugin run is eld::plugin::LinkerWrapper::BeforeLayout.

SectionMatcherPlugin flow.

UserPlugin::Init is called before UserPlugin::ProcessSection, and UserPlugin::Run and UserPlugin::Destroy are called subsequently after processing input sections, as described in the figure above.

Linker script keyword for SectionMatcherPlugin interface is PLUGIN_SECTION_MATCHER. Therefore, to run a SectionMatcherPlugin plugin using a linker script, add the following line to the linker script:

PLUGIN_SECTION_MATCHER("LibraryName", "PluginName" [, "PluginOption"])

1.10.4. Section Iterator Interface

SectionIteratorPlugin interface iterates over input sections. It provides four hooks: Init, ProcessSection, Run and Destroy. To define the behavior of these hooks, the functions Plugin::Init, Plugin::ProcessSection, SectionIterator::ProcessSection and Plugin::Destroy must be overridden.

ProcessSection(Section S) callback hook function is not called for garbage collected input sections. However, it is called for discarded input sections.

SectionIteratorPlugin is called into action after the linker performs section rule-matching and garbage collection. As a consequence, section rule matching and garbage collection information is accessible during SectionIteratorPlugin run. Link state throughout the plugin run is eld::plugin::LinkerWrapper::BeforeLayout.

SectionIteratorPlugin flow

UserPlugin::Init is called before ProcessSection, and UserPlugin::Run and UserPlugin::Destroy are called after ProcessSection, as described in the figure above.

Linker script keyword for SectionIteratorPlugin interface is PLUGIN_ITER_SECTIONS. Therefore, to run a SectionIteratorPlugin plugin using a linker script, add the following line to the linker script:

PLUGIN_ITER_SECTIONS("LibraryName", "PluginName" [, "PluginOption"])

1.10.5. Output Section Iterator Interface

outputSectionIteratorPlugin interface iterates over all the output sections. It provides four hooks: Init, ProcessOutputSection, Run and Destroy. To define the behavior of these hooks, the functions Plugin::Init, Plugin::ProcessSection, OutputSectionIteratorPlugin::ProcessOutputSection and Plugin::Destroy must be overridden. OutputSectionIteratorPlugin is called at different link states:

  • BeforeLayout

  • CreatingSections

  • AfterLayout

All the plugin hooks are called for each of the link states. That means, each of Init, Run and Destory are called 3 times over the complete plugin run. And ProcessOutputSection is called three times for each output section over the complete plugin run.

The link state can be queried via LinkerWrapper::getState member function.

Linker script keyword for OutputSectionIteratorPlugin interface is PLUGIN_OUTPUT_SECTION_ITER. Therefore, to run a SectionIteratorPlugin using a linker script, add the following line to the linker script:

PLUGIN_OUTPUT_SECTION_ITER("LibraryName", "PluginName" [, "PluginOption"])

Different phases of OutputSectionIteratorPlugin are described below:

1.10.5.1. BeforeLayout – Phase 1

OutputSectionIteratorPlugin flow in BeforeLayout link state

This phase is called into action after the linker has performed section rule-matching and before the linker has performed section merging.

1.10.5.2. CreatingSections – Phase 2

OutputSectionIteratorPlugin flow in CreatingSections link state

This phase is called into action after the linker has performed section merging.

1.10.5.3. AfterLayout – Phase 3

OutputSectionIteratorPlugin flow in AfterLayout link state.

This phase is called into action after the linker has finalized the image layout and before the linker has written the output object file.

1.10.6. ControlMemorySizePlugin

This interface allows the plugin to add input sections to the output image. These newly added input sections are generally, but not necessary, based on some already existing output section.

ControlMemorySizePlugin is an output section plugin. Plugins of this type has to explicitly mark each output section that it needs to run on. Since this is an output section plugin, init and destroy functions are also called for each output section the plugin runs on. This plugin is called in the LinkerWrapper::CreatingSections step of the linker.

ControlMemorySizePlugin Flow

init, AddBlocks, Run, GetBlocks and Destroy functions are called for each output section.

Brief description of ControlMemorySizePlugin hooks:

1.10.6.1. init

This hook initializes the plugin for the output section.

1.10.6.2. AddBlocks

This hook gives access to the current output section to the plugin. Block parameter of the hook function contains the current output section. It is generally used in the creation of the new block which linker can use as an additional input section.

1.10.6.3. Run

This hook is generally used to process the current output section, and create any new sections that linker should use as additional input sections.

1.10.6.4. GetBlocks

This hook returns a vector of Block. These blocks are used by the linker as additional input sections.

1.10.6.5. Destroy

This hook is used to perform any required clean-up.

Linker script keyword for ControlMemorySize interface is PLUGIN_CONTROL_MEMSZ. Therefore, to run a SectionIterator plugin on an output section, .out using a linker script, add the following line to the output section description of .out in the linker script:

SECTIONS {
   .out PLUGIN_CONTROL_MEMSZ("LibraryName", "PluginName" [, "PluginOption"]) : { *(.out) }
 }