2. Linker Plugin Examples

This section introduces and explains plugin framework APIs in a hands-on manner. The goal of these examples is to demonstrate how to use plugin framework APIs, rather than to present practical plugin use cases. Each plugin example introduces some new plugin framework APIs while keeping the example short and simple.

Each example will include the plugin, instructions on how to build and run it, and an analysis of the plugin’s run. To build and run the plugin, you will need access to the Hexagon toolchain. In the snippets below, the substitution ${HEXAGON} refers to the Hexagon toolchain installation path. Let’s get started.

2.1. Exclude symbols from the output image symbol table

This example plugin describes how to exclude symbols from the output image symbol table. Note that the plugin does not completely remove symbols from the link process, it only excludes symbols from the output image symbol table.

ExcludeSymbols plugin with self-contained documentation.

#include "ELD/PluginAPI/PluginVersion.h"
#include "ELD/PluginAPI/SectionIteratorPlugin.h"
#include <string>
#include <vector>

class ExcludeSymbols : public eld::plugin::SectionIteratorPlugin {
public:
  ExcludeSymbols() : eld::plugin::SectionIteratorPlugin("ExcludeSymbols") {}

  // 'Init' callback hook can be used for initialization and preparations.
  void Init(std::string Options) override {
    m_SymbolsToRemove = {"foo", "fooagain", "bar", "baragain"};
  }

  // 'processSection' callback hook is called for each input section that is not
  // garbage-collected.
  void processSection(eld::plugin::Section O) override {}

  // 'Run' callback hook is called after 'processSection' callback hook calls.
  // It is called once for each section iterator plugin run.
  eld::plugin::Plugin::Status Run(bool trace) override {
    for (auto symName : m_SymbolsToRemove) {
      eld::plugin::Symbol S = Linker->getSymbol(symName);
      if (S)
        Linker->removeSymbolTableEntry(S);
    }
    return eld::plugin::Plugin::Status::SUCCESS;
  }

  // 'Destroy' callback hook can be used for finalization and clean-up tasks.
  // It is called once for each section iterator plugin run.
  void Destroy() override {}

  uint32_t GetLastError() override { return 0; }

  std::string GetLastErrorAsString() override { return "Success"; }

  std::string GetName() override { return "ExcludeSymbols"; }

private:
  std::vector<std::string> m_SymbolsToRemove;
};

eld::plugin::Plugin *ThisPlugin = nullptr;

extern "C" {
bool RegisterAll() {
  ThisPlugin = new ExcludeSymbols{};
  return true;
}

eld::plugin::Plugin *getPlugin(const char *pluginName) { return ThisPlugin; }

void Cleanup() {
  if (!ThisPlugin)
    return;
  delete ThisPlugin;
  ThisPlugin = nullptr;
}
}

ExcludeSymbols plugin removes the symbols ‘foo’, ‘fooagain’, ‘bar’, and ‘baragain’, if they exist, from the output image symbol table.

To build the plugin, run the following command:

clang++-14 -o libExcludeSymbols.so ExcludeSymbols.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include -L${HEXAGON}/lib -lLW

Now, let’s see the effect of this plugin on a sample program.

1.c

int foo() { return 1; }
int fooagain() { return 2; }
int abc() { return 3; }

int bar = 3;
int baragain = 4;
int def = 5;

int main() { return 0; }

1.linker.script

PLUGIN_ITER_SECTIONS("ExcludeSymbols", "ExcludeSymbols");

Now, let’s build 1.c with the ExcludeSymbols plugin enabled.

export LD_LIBRARY_PATH="${HEXAGON}/lib:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.o 1.c -c -ffunction-sections -fdata-sections
hexagon-link -o 1.elf 1.o -T 1.linker.script

We can list the symbols present in an object file using hexagon-readelf.

hexagon-readelf -s 1.elf

You can observe in the symbol table output that ‘foo’, ‘fooagain’, ‘bar’, and ‘baragain’ symbols has been removed from the symbol table as directed by the plugin.

2.2. Add Linker Script Rules

This example plugin describes how to add and modify linker script rules. By doing so, you can perform section rule-matching at a more granular level than what is possible through linker scripts.

LinkerScriptRule is similar to an input section description in that it has a corresponding output section. However, unlike an input section description, it does not match input sections by pattern matching. Instead, a plugin must manually match chunks to a LinkerScriptRule object. This generally involves moving chunks from one LinkerScriptRule object to another. It is important to remove a chunk from the old LinkerScriptRule object once it has been added to a new one. It is an undefined behavior for a chunk to be part of multiple LinkerScriptRule objects.

Also, you must only move chunks from one LinkerScriptRule object to another in the CreatingSections link state. It is an undefined behavior to move chunks in other link states.

The AddRule plugin moves chunks from foo and bar output sections to the var output section. Let’s see how to create this plugin.

AddRule plugin with self-contained documentation.

#include "ELD/PluginAPI/OutputSectionIteratorPlugin.h"
#include "ELD/PluginAPI/PluginVersion.h"

class AddRule : public eld::plugin::OutputSectionIteratorPlugin {
public:
  AddRule() : eld::plugin::OutputSectionIteratorPlugin("AddRule") {}

  // 'Init' callback hook can be used for initialization and preparations.
  // This plugin does not need any initialization or preparation.
  void Init(std::string cfg) override {}

  // 'processOutputSection' callback hook is called once for each output
  // section. In this function, the plugin stores 'var, 'foo' and 'bar' output
  // sections in member variables.
  void processOutputSection(eld::plugin::OutputSection O) override {
    // OutputSectionIterator plugin essentially runs three times.
    // It is run once for each of the following three link states: BeforeLayout,
    // CreatingSections and AfterLayout.
    // We are only interested in one link state, CreatingSections, as chunks can
    // only be moved from one LinkerScriptRule to another in the
    // CreatingSections link state. Thus, we simply return for the other link
    // states. We will do this for each callback hook function.
    if (Linker->getState() != eld::plugin::LinkerWrapper::State::CreatingSections)
      return;
    if (O.getName() == "var") {
      m_Var = O;
    } else if (O.getName() == "foo") {
      m_Foo = O;
    } else if (O.getName() == "bar") {
      m_Bar = O;
    }
  }

  // 'Run' callback hook is called after all the 'processSection' callback hook
  // calls.
  eld::plugin::Plugin::Status Run(bool trace) override {
    if (Linker->getState() != eld::plugin::LinkerWrapper::State::CreatingSections)
      return eld::plugin::Plugin::Status::SUCCESS;

    auto lastRule = m_Var.getLinkerScriptRules().back();
    // Create a new rule for m_Var output section.
    // Annotation is used to name the linker sript rule, and is useful
    // for diagnostic purposes.
    eld::plugin::LinkerScriptRule newRule = Linker->createLinkerScriptRule(
        m_Var, /*Annotation=*/"Move foo and bar chunks to var");
    // Insert the newly created linker script rule in the m_Var output section.
    // We can also insert the newly created rule before some already existing
    // rule using LinkerWrapper::insertBeforeRule API.
    Linker->insertAfterRule(m_Var, lastRule, newRule);
    moveChunks(m_Foo, newRule);
    moveChunks(m_Bar, newRule);

    return eld::plugin::Plugin::Status::SUCCESS;
  }

  // 'Destroy' callback hook can be used for finalization and clean-up tasks.
  // It is called once for each section iterator plugin run.
  void Destroy() override {}

  uint32_t GetLastError() override { return 0; }

  std::string GetLastErrorAsString() override { return "Success"; }

  std::string GetName() override { return "AddRule"; }

private:
  void moveChunks(eld::plugin::LinkerScriptRule oldRule,
                  eld::plugin::LinkerScriptRule newRule) {
    for (eld::plugin::Chunk C : oldRule.getChunks()) {
      // It is crucial to maintain that no two LinkerScriptRule objects contain
      // the same chunk. It is an undefined behavior for a chunk to be contained
      // by multiple linker script rules.
      Linker->addChunk(newRule, C);
      Linker->removeChunk(oldRule, C);
    }
  }

  void moveChunks(eld::plugin::OutputSection oldSection,
                  eld::plugin::LinkerScriptRule newRule) {
    for (eld::plugin::LinkerScriptRule rule : oldSection.getLinkerScriptRules())
      moveChunks(rule, newRule);
  }

private:
  eld::plugin::OutputSection m_Var = eld::plugin::OutputSection(nullptr);
  eld::plugin::OutputSection m_Foo = eld::plugin::OutputSection(nullptr);
  eld::plugin::OutputSection m_Bar = eld::plugin::OutputSection(nullptr);
};

eld::plugin::Plugin *ThisPlugin = nullptr;

extern "C" {
// RegisterAll should initialize all the plugins that a plugin library aims
// to provide. Linker calls this function before running any plugins provided
// by the library.
bool RegisterAll() {
  ThisPlugin = new AddRule{};
  return true;
}

// Linker calls this function to request an instance of the plugin
// with the plugin name pluginName. pluginName is provided in the plugin
// invocation command.
eld::plugin::Plugin *getPlugin(const char *pluginName) { return ThisPlugin; }

// Cleanup should free all the resources owned by a plugin library.
// Linker calls this function after all runs of the plugins provided
// by the library have completed.
void Cleanup() {
  if (!ThisPlugin)
    return;
  delete ThisPlugin;
  ThisPlugin = nullptr;
}
}

To build the plugin, run the following command:

clang++-14 -o libAddRule.so AddRule.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include -L${HEXAGON}/lib -lLW

Now, let’s see the effect of this plugin on a sample program.

1.c

int foo() { return 1; }
int bar() { return 2; }
int baz() { return 3; }

int int_var = 1;
long long_var = 2;
double double_var = 3;

int main() {
  return 0;
}

1.linker.script

SECTIONS {
  foo : { *(.text.foo) }
  bar : { *(.text.bar) }
  baz : { *(.text.baz) }
  var : { *(*var) }
}

PLUGIN_OUTPUT_SECTION_ITER("AddRule", "AddRule");

Now, let’s build 1.c with the AddRule plugin enabled.

export LD_LIBRARY_PATH="${HEXAGON}/lib:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.o 1.c -c -ffunction-sections -fdata-sections
hexagon-link -o 1.elf 1.o -T 1.linker.script -Map 1.map.txt

We can see the symbol to section mapping using hexagon-readelf.

hexagon-readelf -Ss 1.elf

readelf output:

There are 8 section headers, starting at offset 0x12a0:

Section Headers:
  [Nr] Name              Type            Address  Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] baz               PROGBITS        00000000 001000 00000c 00  AX  0   0 16
  [ 2] .text.main        PROGBITS        00000010 001010 000014 00  AX  0   0 16
  [ 3] var               PROGBITS        00000030 001030 00002c 00 WAXp  0   0 16
  [ 4] .comment          PROGBITS        00000000 00105c 0000d2 01  MS  0   0  1
  [ 5] .shstrtab         STRTAB          00000000 00112e 000037 00      0   0  1
  [ 6] .symtab           SYMTAB          00000000 001168 0000e0 10      7   6  4
  [ 7] .strtab           STRTAB          00000000 001248 000054 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), p (processor specific)

Symbol table '.symtab' contains 14 entries:
   Num:    Value  Size Type    Bind   Vis       Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT   UND
     1: 00000000     0 SECTION LOCAL  DEFAULT     1 baz
     2: 00000000     0 SECTION LOCAL  DEFAULT     4 .comment
     3: 00000010     0 SECTION LOCAL  DEFAULT     2 .text.main
     4: 00000030     0 SECTION LOCAL  DEFAULT     3 var
     5: 00000000     0 FILE    LOCAL  DEFAULT   ABS 1.c
     6: 00000000    12 FUNC    GLOBAL DEFAULT     1 baz
     7: 00000010    20 FUNC    GLOBAL DEFAULT     2 main
     8: 00000030     4 OBJECT  GLOBAL DEFAULT     3 int_var
     9: 00000034     4 OBJECT  GLOBAL DEFAULT     3 long_var
    10: 00000038     8 OBJECT  GLOBAL DEFAULT     3 double_var
    11: 00000040    12 FUNC    GLOBAL DEFAULT     3 foo
    12: 00000050    12 FUNC    GLOBAL DEFAULT     3 bar
    13: 00000061     0 NOTYPE  GLOBAL DEFAULT   ABS __end

You can observe in the readelf output that the section var contains the foo and the bar symbols, which is exactly what we expected from the plugin.

2.3. Reading INI Files Functionality

The plugin framework provides built-in support for the input/output operations of INI files. Linker plugins, like any other tool, may require external options and configurations. Traditionally, linker plugins use INI files for this purpose. This plugin demo demonstrates how to use INI files to provide options and configurations to a plugin. You can also use JSON, YAML, plain text, or any other format you prefer. However, for consistency with other linker plugins, I suggest using INI files for the plugin options and configurations.

The demo will feature the PrintSectionsInfo plugin. This plugin will print section information for all input sections that match any of the section name patterns specified in the plugin configuration file. The plugin will expect the configuration file to in INI format.

PrintSectionsInfo plugin with self-contained documentation.

To build the plugin, run:

clang++-14 -o libPrintSectionsInfo.so PrintSectionsInfo.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include \
-L${HEXAGON}/lib -lLW

Now, let’s see the effect of this plugin on a sample program.

1.c

int i[5] = {1, 2, 3, 4, 5};
int arr[100];

int foo() { return 1; }
int bar() { return 2; }

int main() { return 0; }

1.linker.script

# PluginConf.ini is the plugin configuration file name.
PLUGIN_SECTION_MATCHER("PrintSectionsInfo", "PrintSectionsInfo", "PluginConf.ini");

PluginConf.ini

[sections]
*i=1
*arr=1
*foo=0
*bar=1

Now, let’s build 1.c with PrintSectionsInfo plugin enabled.

export LD_LIBRARY_PATH="${HEXAGON}/lib:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.o 1.c -c -ffunction-sections -fdata-sections
hexagon-link -o 1.elf 1.o -T 1.linker.script

The plugin gives the below output:

.text.bar
Input file: 1.o
Section index: 4
Section alignment: 16

.data.i
Input file: 1.o
Section index: 6
Section alignment: 8

COMMON.arr
Input file: CommonSymbols
Section index: 0
Section alignment: 8

As expected, the plugin prints the section information of the sections whose names match a pattern specified in the plugin configuration file, PluginConf.ini.

2.4. Modify Relocations

The ModifyRelocations example plugin demonstrates how to inspect and modify relocations. This plugin modifies the relocation symbol from HelloWorld to HelloQualcomm. Additionally, it prints the relocation source section and symbol for each relocation it iterates over.

To inspect and modify relocations, ModifyRelocations uses LinkerPluginConfig. LinkerPluginConfig provides a callback hook for relocations. This callback hook function is called for each relocation that is of a registered relocation type. Relocation types can be registered using LinkerWrapper::registerReloc.

ModifyRelocations plugin with self-contained documentation.

#include "ELD/PluginAPI/PluginVersion.h"
#include "ELD/PluginAPI/SectionIteratorPlugin.h"
#include <iostream>

class ModifyRelocations : public eld::plugin::SectionIteratorPlugin {
public:
  ModifyRelocations() : SectionIteratorPlugin("ModifyRelocations") {}

  // 'Init' callback hook can be used for initialization and preparations.
  // This plugin does not need any initialization or preparation.
  void Init(std::string cfg) override {}

  // 'processSection' callback hook of SectionIteratorPlugin is called for
  // each non-garbage collected section.
  void processSection(eld::plugin::Section S) override {}

  // 'Run' callback hook is called after all the 'processSection' callback hook
  // calls. It is called once for each section iterator plugin run.
  // This plugin does not need to run anything.
  eld::plugin::Plugin::Status Run(bool trace) override {
    return eld::plugin::Plugin::Status::SUCCESS;
  }

  // 'Destroy' callback hook can be used for finalization and clean-up tasks.
  // It is called once for each section iterator plugin run.
  // This plugin does not need any finalization and clean-up.
  void Destroy() override {}

  uint32_t GetLastError() override { return 0; }

  std::string GetLastErrorAsString() override { return "Success"; }

  std::string GetName() override { return "ModifyRelocations"; }

  eld::plugin::LinkerWrapper *getLinker() { return Linker; }
};

// LinkerPluginConfig allows to inspect and modify relocations.
class ModifyRelocationsPluginConfig : public eld::plugin::LinkerPluginConfig {
public:
  ModifyRelocationsPluginConfig(ModifyRelocations *P)
      : LinkerPluginConfig(P), P(P) {}

  void Init() override {
    // Register R_HEX_B22_PCREL relocation type.
    // Linker will call RelocCallBack callback hook function on each relocation
    // that is of a registered relocation type.
    std::string b22pcrel = "R_HEX_B22_PCREL";
    uint32_t relocationType =
        P->getLinker()->getRelocationHandler().getRelocationType(b22pcrel);
    P->getLinker()->registerReloc(relocationType);
  }

  // Relocation callback hook function.
  void RelocCallBack(eld::plugin::Use U) override {
    // Print relocation source section name and symbol names.
    std::string sourceSectionName = U.getSourceChunk().getName();
    std::cout << "Relocation callback. Source section: " << sourceSectionName
              << ", symbol: " << U.getName() << "\n";
    // Change relocation symbol from HelloWorld to HelloQualcomm.
    if (U.getSymbol().getName() == "HelloWorld") {
      eld::Expected<eld::plugin::Symbol> expHelloQualcommSymbol =
          P->getLinker()->getSymbol("HelloQualcomm");
      ELDEXP_REPORT_AND_RETURN_VOID_IF_ERROR(
          P->getLinker(), expHelloQualcommSymbol);
      eld::plugin::Symbol helloQualcommSymbol =
          std::move(expHelloQualcommSymbol.value());
      U.resetSymbol(helloQualcommSymbol);
    }
  }

private:
  ModifyRelocations *P;
};

eld::plugin::Plugin *ThisPlugin = nullptr;
eld::plugin::LinkerPluginConfig *ThisPluginConfig = nullptr;

extern "C" {
// RegisterAll should initialize all the plugins and plugin configs that a
// plugin library aims to provide. Linker calls this function before running
// any plugins provided by the library.
bool DLL_A_EXPORT RegisterAll() {
  ThisPlugin = new ModifyRelocations();
  ThisPluginConfig = new ModifyRelocationsPluginConfig(
      dynamic_cast<ModifyRelocations *>(ThisPlugin));
  return true;
}

// Linker calls this function to request an instance of the plugin
// with the plugin name pluginName. pluginName is provided in the plugin
// invocation command.
eld::plugin::Plugin *getPlugin(const char *pluginName) { return ThisPlugin; }

// Linker calls this function to request an instance of the plugin
// configuration for the plugin with the plugin name pluginName.
// pluginName is provided in the plugin invocation command.
eld::plugin::LinkerPluginConfig DLL_A_EXPORT *getPluginConfig(const char *pluginName) {
  return ThisPluginConfig;
}

// Cleanup should free all the resources owned by a plugin library.
// Linker calls this function after all runs of the plugins provided
// by the library have completed.
void DLL_A_EXPORT Cleanup() {
  if (ThisPlugin)
    delete ThisPlugin;
  if (ThisPluginConfig)
    delete ThisPluginConfig;
}
}

To build the plugin, run:

clang++-14 -o libModifyRelocations.so ModifyRelocations.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include -L${HEXAGON}/lib -lLW

Now, let’s see the effect of this plugin on a sample program.

1.c

#include <stdio.h>

const char *HelloWorld() {
  return "Hello World!";
}

const char *HelloQualcomm() {
  return "Hello Qualcomm!";
}

int main() {
  printf("%s\n", HelloWorld());
}

1.linker.script

PLUGIN_ITER_SECTIONS("ModifyRelocations", "ModifyRelocations");

Now, let’s build 1.c with ModifyRelocations plugin enabled.

export LD_LIBRARY_PATH="/local/mnt/workspace/partaror/llvm-project-formal/obj/bin:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.elf 1.c -ffunction-sections -fdata-sections -Wl,-T,1.linker.script

Running the above commands gives the below output:

Relocation callback. Source section: .text, symbol: .start
...
...
Relocation callback. Source section: .text.main, symbol: HelloWorld
Relocation callback. Source section: .text.main, symbol: printf
...

This output is emitted by the plugin when it’s iterating over relocations of registered types.

Now, let’s try running the generated binary image, 1.elf:

$ hexagon-sim ./1.elf

hexagon-sim INFO: The rev_id used in the simulation is 0x00008d68 (v68n_1024)
Hello Qualcomm!

Done!
        T0: Insns=5479 Packets=2946
        T1: Insns=0 Packets=0
        T2: Insns=0 Packets=0
        T3: Insns=0 Packets=0
        T4: Insns=0 Packets=0
        T5: Insns=0 Packets=0
        Total: Insns=5479 Pcycles=8841

Hello World! has been replaced by Hello Qualcomm!. It’s the result of plugin changing the relocation symbol from HelloWorld to HelloQualcomm for all R_HEX_B22_PCREL relocations.

2.5. Section Rule-Matching

This example plugin describes how to modify section rule-matching using a plugin. A plugin can create section overrides using the LinkerWrapper::setOutputSection API. Section overrides created by a plugin override linker script section rule-matching. Plugins must call LinkerWrapper::finishAssignOutputSections after all section overrides have been created. LinkerWrapper::finishAssignOutputSections brings the section override change into effect.

Section overrides must only be used in the BeforeLayout link state. After the BeforeLayout state, chunks from input sections get merged into output sections, making section overrides meaningless.

The ChangeOutputSection plugin sets the output section of .text.foo to bar. That’s it. Let’s see how to create this plugin.

ChangeOutputSection plugin with self-contained documentation.

#include "ELD/PluginAPI/PluginVersion.h"
#include "ELD/PluginAPI/SectionIteratorPlugin.h"
#include <iostream>

class ChangeOutputSection : public eld::plugin::SectionIteratorPlugin {
public:
  ChangeOutputSection() : eld::plugin::SectionIteratorPlugin("ChangeOutputSection") {}

  // 'Init' callback hook can be used for initialization and preparations.
  // This plugin does not need any initialization or preparation.
  void Init(std::string cfg) override {}

  // 'processSection' callback hook of SectionIteratorPlugin is called for
  // each non-garbage collected section.
  void processSection(eld::plugin::Section S) override {
    if (S.matchPattern("*foo")) {
      // Changes the output section of the section S to
      // bar. LinkerWrapper::setOutputSection must only be
      // called in BeforeLayout link state. Section overrides created
      // after BeforeLayout link state do not work and can result in
      // undefined behavior.
      //
      // Annotation is useful for diagnostic purposes. Later, we will see where
      // to find these annotations.
      Linker->setOutputSection(
          S, "bar",
          /*Annotation=*/"Setting output section of '.text.foo' to 'bar'");
    }
  }

  // 'Run' callback hook is called after all the 'processSection' callback hook
  // calls. It is called once for each section iterator plugin run.
  eld::plugin::Plugin::Status Run(bool trace) override {
    return eld::plugin::Plugin::Status::SUCCESS;
  }

  // 'Destroy' callback hook can be used for finalization and clean-up tasks.
  // It is called once for each section iterator plugin run.
  void Destroy() override {
    // LinkerWrapper::finishAssignOutputSections must be called
    // after all section overrides have been created by the plugin.
    // It brings the created section overrides into effect.
    Linker->finishAssignOutputSections();
  }

  uint32_t GetLastError() override { return 0; }

  std::string GetLastErrorAsString() override { return "Success"; }

  std::string GetName() override { return "ChangeOutputSection"; }
};

eld::plugin::Plugin *ThisPlugin = nullptr;

extern "C" {
// RegisterAll should initialize all the plugins that a plugin library aims
// to provide. Linker calls this function before running any plugins provided
// by the library.
bool RegisterAll() {
  ThisPlugin = new ChangeOutputSection{};
  return true;
}

// Linker calls this function to request an instance of the plugin
// with the plugin name pluginName. pluginName is provided in the plugin
// invocation command.
eld::plugin::Plugin *getPlugin(const char *pluginName) { return ThisPlugin; }

// Cleanup should free all the resources owned by a plugin library.
// Linker calls this function after all runs of the plugins provided
// by the library have completed.
void Cleanup() {
  if (!ThisPlugin)
    return;
  delete ThisPlugin;
  ThisPlugin = nullptr;
}
}

To build the plugin, run the following command:

clang++-14 -o libChangeOutputSection.so ChangeOutputSection.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include -L${HEXAGON}/lib -lLW

1.c

int foo() { return 1; }
int bar() { return 2; }

int main() { return 0; }

1.linker.script

PLUGIN_ITER_SECTIONS("ChangeOutputSection", "ChangeOutputSection");

SECTIONS {
  foo : { *(.text.foo) }
  bar : { *(.text.bar) }
  text : { *(.text*) }
}

Now, let’s build 1.c with the ChangeOutputSection plugin enabled.

export LD_LIBRARY_PATH="${HEXAGON}/lib:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.o 1.c -c -ffunction-sections -fdata-sections
hexagon-link -o 1.elf 1.o -T 1.linker.script -Map 1.map.txt

Now, let’s see the output section of foo.

hexagon-readelf -Ss 1.elf

hexagon-readelf output:

There are 7 section headers, starting at offset 0x1200:

Section Headers:
  [Nr] Name              Type            Address  Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] bar               PROGBITS        00000000 001000 00001c 00  AX  0   0 16
  [ 2] text              PROGBITS        00000020 001020 000014 00  AX  0   0 16
  [ 3] .comment          PROGBITS        00000000 001034 0000d2 01  MS  0   0  1
  [ 4] .shstrtab         STRTAB          00000000 001106 00002d 00      0   0  1
  [ 5] .symtab           SYMTAB          00000000 001134 000090 10      6   5  4
  [ 6] .strtab           STRTAB          00000000 0011c4 00002a 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), p (processor specific)

Symbol table '.symtab' contains 9 entries:
   Num:    Value  Size Type    Bind   Vis       Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT   UND
     1: 00000000     0 SECTION LOCAL  DEFAULT     1 bar
     2: 00000000     0 SECTION LOCAL  DEFAULT     3 .comment
     3: 00000020     0 SECTION LOCAL  DEFAULT     2 text
     4: 00000000     0 FILE    LOCAL  DEFAULT   ABS 1.c
     5: 00000000    12 FUNC    GLOBAL DEFAULT     1 bar
     6: 00000010    12 FUNC    GLOBAL DEFAULT     1 foo
     7: 00000020    20 FUNC    GLOBAL DEFAULT     2 main
     8: 00000039     0 NOTYPE  GLOBAL DEFAULT   ABS __end

You can observe from the readelf output that the bar section contains the foo symbol. This means the output section of .text.foo was indeed changed to bar.

That’s all well and good, but how do we see the nice annotation we added when calling LinkerWrapper::setOutputSection? All plugin actions, along with their annotations, are recorded in the map file. The text map file can be used to quickly view plugin actions and the annotations.

To see all the plugin action information, you can either view the text map file and find the “Detailed Plugin information” component, or run the command below to directly view the information from the text map file.

less +/"Detailed Plugin information" 1.map.txt

Output

# Detailed Plugin information
# Plugin #0     ChangeOutputSection
#       Modification #1 {C, Comment : Setting output section of '.text.foo' to 'bar'}
#       Section :.text.foo      1.o
#       Original Rule : *(.text.foo) #Rule 1, 1.linker.script
#       Modified Rule : *(bar) #Rule 4, Internal-LinkerScript (Implicit rule inserted by Linker)

2.6. Change Symbol Value to Another Symbol

The ChangeSymbolValue plugin demonstrates how to change the value of a symbol to another symbol. Specifically, it changes the value of the HelloWorld symbol to the value of the HelloQualcomm symbol. It also attempts (unsuccessfully) to change the value of the HelloWorldAgain symbol to the value of the HelloQualcommAgain symbol. We will see why this happens.

Now, let’s see how to create the ChangeSymbolValue plugin.

The ChangeSymbolValue plugin with self-contained documentation.

#include "ELD/PluginAPI/OutputSectionIteratorPlugin.h"
#include "ELD/PluginAPI/PluginVersion.h"
#include <iostream>

class ChangeSymbolValue : public eld::plugin::OutputSectionIteratorPlugin {
public:
  ChangeSymbolValue()
      : eld::plugin::OutputSectionIteratorPlugin("ChangeSymbolValue") {}

  // 'Init' callback hook can be used for initialization and preparations.
  // This plugin does not need any initialization or preparation.
  void Init(std::string cfg) override {}

  // 'processOutputSection' callback hook is called once for each output
  // section.
  void processOutputSection(eld::plugin::OutputSection O) override {}

  // 'Run' callback hook is called after all the 'processSection' callback hook
  // calls.
  eld::plugin::Plugin::Status Run(bool trace) override {
    if (Linker->getState() != eld::plugin::LinkerWrapper::State::AfterLayout)
      return eld::plugin::Plugin::Status::SUCCESS;
    // Try to reset the 'HelloWorld' symbol value to the value of
    // 'HelloQualcomm' symbol.
    eld::plugin::Symbol helloWorldSymbol = Linker->getSymbol("HelloWorld");
    eld::plugin::Symbol helloQualcommSymbol = Linker->getSymbol("HelloQualcomm");
    bool resetSym =
        Linker->resetSymbol(helloWorldSymbol, helloQualcommSymbol.getChunk());
    if (resetSym)
      std::cout
          << "'HelloWorld' symbol value has been successfully reset to the "
          << "value of 'HelloQualcomm' symbol.\n";
    else
      std::cout << "Symbol value resetting failed for 'HelloWorld'.\n";

    // Try to reset the 'HelloWorldAgain' symbol value to the value of
    // 'HelloQualcommAgain' symbol.
    eld::plugin::Symbol helloWorldAgainSymbol = Linker->getSymbol("HelloWorldAgain");
    eld::plugin::Symbol helloQualcommAgainSymbol =
        Linker->getSymbol("HelloQualcommAgain");
    resetSym = Linker->resetSymbol(helloWorldAgainSymbol,
                                   helloQualcommAgainSymbol.getChunk());
    if (resetSym)
      std::cout << "'HelloWorldAgain' symbol value has been successfully reset "
                << "to the "
                << "value of 'HelloQualcommAgain' symbol.\n";
    else
      std::cout << "Symbol value resetting failed for 'HelloWorldAgain'.\n";
    return eld::plugin::Plugin::Status::SUCCESS;
  }

  void Destroy() override {}

  uint32_t GetLastError() override { return 0; }

  std::string GetLastErrorAsString() override { return "Success"; }

  std::string GetName() override { return "ChangeSymbol"; }
};

eld::plugin::Plugin *ThisPlugin = nullptr;

extern "C" {
// RegisterAll should initialize all the plugins that a plugin library aims
// to provide. Linker calls this function before running any plugins provided
// by the library.
bool RegisterAll() {
  ThisPlugin = new ChangeSymbolValue{};
  return true;
}

// Linker calls this function to request an instance of the plugin
// with the plugin name pluginName. pluginName is provided in the plugin
// invocation command.
eld::plugin::Plugin *getPlugin(const char *pluginName) { return ThisPlugin; }

// Cleanup should free all the resources owned by a plugin library.
// Linker calls this function after all runs of the plugins provided
// by the library have completed.
void Cleanup() {
  if (!ThisPlugin)
    return;
  delete ThisPlugin;
  ThisPlugin = nullptr;
}
}

To build the plugin, run:

clang++-14 -o libChangeSymbolValue.so ChangeSymbolValue.cpp -std=c++17 -stdlib=libc++ -fPIC -shared \
-I${HEXAGON}/include -L${HEXAGON}/lib -lLW

Now, let’s see the effect of this plugin on a sample program.

1.c

#include <stdio.h>

const char *HelloWorld;
const char *HelloQualcomm = "Hello Qualcomm!";

const char *HelloWorldAgain = "Hello again World!";
const char *HelloQualcommAgain = "Hello again Qualcomm!";

int main() {
  printf("%s\n", HelloWorld);
  printf("%s\n", HelloWorldAgain);
}

1.linker.script

PLUGIN_OUTPUT_SECTION_ITER("ChangeSymbolValue", "ChangeSymbolValue");

Now, let’s build 1.c with ChangeSymbolValue plugin enabled.

export LD_LIBRARY_PATH="${HEXAGON}/lib:${LD_LIBRARY_PATH}"
hexagon-clang -o 1.elf 1.c -ffunction-sections -fdata-sections -Wl,-T,1.linker.script

Running the above commands produces the following output:

'HelloWorld' symbol value has been successfully reset to the value of 'HelloQualcomm' symbol.
Symbol value resetting failed for 'HelloWorldAgain'.

The above output is printed by the plugin. It indicates whether the resetting of symbol values was successful or not. So, why was the resetting of ‘HelloWorldAgain’ not successful? LinkerWrapper::resetSymbol can only resets the value of the symbols that do not already have a value. LinkerWrapper::resetSymbol silently fails and returns false if the symbol requested to be reset already has a value.

Now, let’s run the generated 1.elf binary:

$ hexagon-sim ./1.elf

hexagon-sim INFO: The rev_id used in the simulation is 0x00008d68 (v68n_1024)
Hello Qualcomm!
Hello again World!

Done!
        T0: Insns=6810 Packets=3558
        T1: Insns=0 Packets=0
        T2: Insns=0 Packets=0
        T3: Insns=0 Packets=0
        T4: Insns=0 Packets=0
        T5: Insns=0 Packets=0
        Total: Insns=6810 Pcycles=10677

As expected, the output first prints, ‘Hello Qualcomm!’, as the value of the HelloWorld` symbol successfully got reset to the value of HelloQualcomm. The output then prints, ‘Hello again World!’, as the resetting of HelloWorldAgain` to HelloQualcommAgain` was unsuccessful.