OpenWrt: Adding Patches to a Static Library

This document explains:

  • How to build a static library (libfoo.a)

  • How to patch the library source before compilation

  • How OpenWrt applies patches automatically using Build/Patch

  • How to export the library + headers to staging_dir and rootfs

  • How to build a test application linking with the patched library

  • How to install and run the patched system on OpenWrt

This mirrors OpenWrt’s standard build architecture for static libraries.

In this section, you are going to learn

How to create a simple static library (libfoo.a) inside the OpenWrt build system?

How to patch the library sources using OpenWrt’s automatic Build/Patch mechanism?

How to export the library archive and headers to staging_dir and into the target root filesystem?

How to build a test application that links statically against the patched libfoo.a?

How to install and run the patched library and test application on OpenWrt?

1. Creating a Static Library (libfoo.a)

A static library in OpenWrt:

  • uses TARGET_CC for cross compilation

  • archives its object files using TARGET_AR

  • exports .a and .h files to staging_dir for dependent packages

  • installs files into the target root filesystem

Patching allows modifying the library’s functionality without editing the original source tree directly.

1.1 Directory Layout

Create the package skeleton:

package/my/libfoo/
├── Makefile
├── patches/
│   └── 0001-change-add-behavior.patch
└── src/
    ├── foo.c
    └── foo.h

A helper command (not part of OpenWrt build):

$ mkdir -p libfoo/patches libfoo/src

1.2 Source Files

src/foo.h

#ifndef FOO_H
#define FOO_H

int foo_add(int a, int b);

#endif

src/foo.c

#include "foo.h"

int foo_add(int a, int b) {
    return a + b;
}

1.3 Patch File

patches/0001-change-add-behavior.patch

--- a/foo.c
+++ b/foo.c
@@ -1,5 +1,5 @@
 #include "foo.h"

 int foo_add(int a, int b) {
-    return a + b;
+    return a + b + 1; /* patched behavior */
 }

1.4 Makefile (Static Library)

package/my/libfoo/Makefile

include $(TOPDIR)/rules.mk

PKG_NAME:=libfoo
PKG_RELEASE:=1

PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

define Package/libfoo
  SECTION:=libs
  CATEGORY:=Libraries
  TITLE:=Simple static foo library
endef

define Build/InstallDev
    $(INSTALL_DIR) $(STAGING_DIR)/usr/include
    $(INSTALL_DATA) $(PKG_BUILD_DIR)/foo.h $(STAGING_DIR)/usr/include/

    $(INSTALL_DIR) $(STAGING_DIR)/usr/lib
    $(INSTALL_DATA) $(PKG_BUILD_DIR)/libfoo.a $(STAGING_DIR)/usr/lib/
endef

define Build/Prepare
    mkdir -p $(PKG_BUILD_DIR)
    $(CP) ./src/* $(PKG_BUILD_DIR)/
    $(call Build/Patch,$(PKG_BUILD_DIR))
endef

define Build/Compile
    $(TARGET_CC) $(TARGET_CFLAGS) -c                     $(PKG_BUILD_DIR)/foo.c                     -o $(PKG_BUILD_DIR)/foo.o

    $(TARGET_AR) rcs $(PKG_BUILD_DIR)/libfoo.a $(PKG_BUILD_DIR)/foo.o
endef

define Package/libfoo/install
    $(INSTALL_DIR) $(1)/usr/include
    $(INSTALL_DATA) $(PKG_BUILD_DIR)/foo.h $(1)/usr/include/

    $(INSTALL_DIR) $(1)/usr/lib
    $(INSTALL_DATA) $(PKG_BUILD_DIR)/libfoo.a $(1)/usr/lib/
endef

$(eval $(call BuildPackage,libfoo))

1.5 Build

Build the package inside the OpenWrt tree:

make package/my/libfoo/compile V=s

Resulting package is located at (path may vary by target):

bin/targets/x86/64/packages/libfoo*.ipk

1.6 Copy + Install

Copy the resulting package to the OpenWrt target (for example, a QEMU VM):

scp -P <QEMU_PORT> <path_to_ipk> root@127.0.0.1:/tmp/

Install on the target:

opkg install /tmp/libfoo*.ipk

Directory Layout

  • patches/ contains all source modifications applied by OpenWrt.

  • src/ holds the original upstream source.

  • The library is built entirely inside $(PKG_BUILD_DIR) to maintain isolation from your version-controlled source tree.

Source Files

  • foo_add is the public API exported from libfoo.a.

  • The header file (foo.h) is exported to staging_dir for dependent packages.

  • The object file (foo.o) is archived into a static library via TARGET_AR.

Patch File

  • The patch modifies the mathematical behavior of foo_add().

  • OpenWrt automatically applies all patches using Build/Patch during Build/Prepare.

  • Patch format uses standard kernel-style unified diff (git format-patch-style).

Makefile Sections

define Package/libfoo

  • Defines metadata for menuconfig and classification.

  • SECTION decides the top-level grouping.

  • CATEGORY controls where it appears in package lists.

  • TITLE provides a human-readable description.

  • It does not affect compilation directly.

define Build/InstallDev

  • Installs headers and library into:

    • $(STAGING_DIR)/usr/include

    • $(STAGING_DIR)/usr/lib

  • This is critical because:

    • Compilation of downstream packages requires these files.

    • Static linking with -lfoo will use libfoo.a from staging_dir.

define Build/Prepare

  • Performs three essential steps:

    1. Creates a clean build directory.

    2. Copies sources into $(PKG_BUILD_DIR).

    3. Applies patches using:

      $(call Build/Patch,$(PKG_BUILD_DIR))
      
  • This ensures patched sources are compiled, not upstream originals.

define Build/Compile

  • Builds the static library:

    • foo.cfoo.o (compiled with TARGET_CC and TARGET_CFLAGS)

    • foo.olibfoo.a via TARGET_AR

  • Static libraries contain compiled code but do not support dynamic symbol lookup (unlike shared libraries).

define Package/libfoo/install

  • Installs the library and header into the final root filesystem: - /usr/include/foo.h - /usr/lib/libfoo.a

  • These files are present on the OpenWrt target device after installation.

Build + Install

  • The resulting libfoo package is a standard OpenWrt .ipk.

  • Installation places libfoo.a inside /usr/lib on the target.

  • The header is also installed for optional on-target development or debugging.

  • The runtime behavior is now patched (+1 added to every sum).

2. Test Application for Static Library (foo-test)

This test application:

  • includes the patched header

  • links statically with libfoo.a

  • demonstrates the patched behavior of foo_add()

2.1 Directory Structure

Create the package skeleton:

package/my/foo-test/
├── Makefile
├── patches/
│   └── 0001-change-message.patch
└── src/
    └── main.c

src/main.c

#include <stdio.h>
#include "foo.h"

int main(void) {
    int result = foo_add(3, 4);
    printf("foo_add(3, 4) returned %d\n", result);
    return 0;
}

2.2 Patch File

patches/0001-change-message.patch

--- a/main.c
+++ b/main.c
@@ -4,6 +4,6 @@
 int main(void) {
     int result = foo_add(3, 4);
-    printf("foo_add(3, 4) returned %d\n", result);
+    printf("Patched: foo_add(3, 4) = %d\n", result);
     return 0;
 }

2.3 Makefile (foo-test)

package/my/foo-test/Makefile

include $(TOPDIR)/rules.mk

PKG_NAME:=foo-test
PKG_RELEASE:=1

PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(INCLUDE_DIR)/package.mk

define Package/foo-test
  SECTION:=utils
  CATEGORY:=Utilities
  TITLE:=Test program for libfoo (static library)
  DEPENDS:=+libfoo
endef

define Build/Prepare
    mkdir -p $(PKG_BUILD_DIR)
    $(CP) ./src/* $(PKG_BUILD_DIR)/
    $(call Build/Patch,$(PKG_BUILD_DIR))
endef

define Build/Compile
    $(TARGET_CC) $(TARGET_CFLAGS)                     -I$(STAGING_DIR)/usr/include                     -L$(STAGING_DIR)/usr/lib                     $(PKG_BUILD_DIR)/main.c                     -lfoo                     -o $(PKG_BUILD_DIR)/foo-test
endef

define Package/foo-test/install
    $(INSTALL_DIR) $(1)/usr/bin
    $(INSTALL_BIN) $(PKG_BUILD_DIR)/foo-test $(1)/usr/bin/
endef

$(eval $(call BuildPackage,foo-test))

2.4 Build + Install

Build the static library (if not already built):

make package/my/libfoo/compile V=s

Build the test application:

make package/my/foo-test/compile V=s

Copy via SCP:

scp -P <QEMU_PORT> <path_to_ipk> root@127.0.0.1:/tmp/

Install on the OpenWrt target:

opkg install /tmp/foo-test*.ipk

Run inside OpenWrt:

# foo-test

Expected Output:

Patched: foo_add(3, 4) = 8

Directory + Sources

  • foo_add in main.c resolves to the static library’s patched version.

  • The entire library code is embedded directly into the final binary (static linking).

  • The patches/ directory demonstrates that you can patch consumer applications in OpenWrt in the same way you patch libraries.

Patch File

  • The patch modifies only the printed output, not the calculation logic.

  • This illustrates that behavior changes can live either in the library or in the consuming application—or both.

Makefile

  • DEPENDS:=+libfoo ensures:

    • libfoo is built before foo-test.

    • libfoo is installed when you install foo-test (dependency resolution).

  • -I$(STAGING_DIR)/usr/include exposes foo.h to the compiler.

  • -L$(STAGING_DIR)/usr/lib allows -lfoo to find libfoo.a.

  • Static linking embeds the code from libfoo.a into the final binary:

    • The resulting /usr/bin/foo-test does not require libfoo.a at runtime to execute the logic (though the package dependency still brings it in).

Runtime Behavior

  • Unpatched logic:

    foo_add(3, 4) = 7
    
  • Patched library logic:

    • Internal computation becomes a + b + 1, so:

      foo_add(3, 4) = 8
      
  • Because of static linking:

    • Even if libfoo.a is later replaced or removed in the filesystem, the already-installed foo-test binary continues to use the embedded patched logic.

    • Rebuilding foo-test against a different version of libfoo.a will bake the new behavior into a fresh binary, again via static linking.

End-to-End Summary

  • libfoo demonstrates:

    • Source → patched source → object → static library.

    • Export to staging_dir and the target root filesystem.

  • foo-test demonstrates:

    • A consumer package that picks up the patched library via Build/Patch + staging_dir.

    • How OpenWrt’s build system composes libraries and applications with clear, reproducible patching and dependency management.