Sitemap

Lesson 5: SDK, Toolchain, Public APIs, and Shared Libraries

11 min readOct 17, 2026

--

How can two different teams develop applications for the same Qualcomm board without sharing all their source code?

Imagine this situation:

  • The LCM team develops a library that provides platform functionality.
  • The IDS team needs to call functions from that library.
  • Both applications must run on the same Qualcomm Linux board.
  • The IDS team doesn’t have the LCM implementation’s source code.

How can the IDS team compile, link, and run its application?

That’s exactly where the SDK, toolchain, headers, and shared libraries come into the picture.

Part 1: Understanding the toolchain and SDK

5.1 What happens when you compile a C++ program?

Let’s start with something familiar.

You write:

#include <iostream>
int main() {
std::cout << "Hello Qualcomm Board!\n";
return 0;
}

On a normal Linux laptop, you might compile it using:

g++ hello.cpp -o hello

The compiler creates an executable for the laptop’s architecture.

But here’s the problem.

Your laptop may be x86–64, while your Qualcomm board may use a 32-bit ARM or 64-bit ARM Linux environment.

The executable produced for your laptop will not normally run on the Qualcomm board.

That’s why we need cross-compilation.

5.2 What is a cross-compiler?

A cross-compiler runs on one system but generates software for another system.

The compiler must match your board’s userspace ABI, not just the processor family.

5.3 What is a toolchain?

A toolchain is a collection of tools required to turn source code into a working program.

Think of a carpenter’s toolbox. A hammer alone isn’t enough to build furniture.

Similarly, a compiler alone isn’t the complete software-building environment.

The cross-toolchain targets the architecture and ABI expected by your device.

5.4 What is an SDK?

SDK stands for Software Development Kit.

An SDK is a prepared development environment for building software for a particular platform.

A Yocto-generated SDK typically contains:

Three pieces are particularly important:

  1. Cross-toolchain: Builds executables for your target.
  2. Target sysroot: Provides the target’s development headers and libraries.
  3. Environment setup script: Configures the compiler and paths used during development.

A sysroot is like a miniature view of the target Linux development environment, stored on the build machine.

It’s not a complete running copy of your board. It contains the files needed by the compiler and linker.

This is also the structure described in Yocto’s official SDK documentation.

5.5 Why can’t we just use the laptop’s libraries?

Suppose IDS depends on OpenSSL.

Your laptop might have one version of libcrypto, while the board has another.

They may differ in:

  • CPU architecture.
  • Library version and exported functions.
  • C library implementation.
  • Binary compatibility rules, called the ABI.

If you compile with the wrong headers or link against incompatible libraries, your program may fail at build time or runtime.

The SDK helps align the software you build with the Linux system you intend to run it on.

An SDK should ideally come from the same platform build or release as your target image.

Part 2: Public APIs, headers, and shared libraries

This is where things become particularly relevant to LCM and IDS.

5.6 What is a public API?

Imagine the LCM team provides this functionality:

bool isPlatformReady();

The IDS team needs to know whether the platform is ready before starting event processing.

But IDS shouldn’t need to understand LCM’s internal implementation.

LCM might internally check processes, IPC connections, configuration, or some other system state.

The IDS team only needs a clearly defined interface.

That interface is called a public API.

Think of an ATM.

You can use the ATM to withdraw money without knowing the bank’s internal transaction-processing code.

Similarly, IDS should be able to call a stable platform API without knowing its implementation details.

5.7 What is a header file?

A header tells the compiler which declarations are available.

For example, the LCM team provides:

lcm_api.hpp

#pragma once
namespace lcm {
bool isPlatformReady();
}

This header tells the IDS compiler:

  • There is a namespace named lcm.
  • There is a function named isPlatformReady.
  • It returns a bool.
  • It takes no arguments.

But the header doesn’t contain the implementation.

Now suppose the LCM team implements the function:

lcm_api.cpp

#include "lcm_api.hpp"
namespace lcm {
bool isPlatformReady() {
return true;
}
}

For now, we’ll return true to keep the example simple.

The LCM implementation can be compiled into a shared library.

5.8 What is a shared library?

A shared library is a compiled collection of functions or other code that multiple programs can use.

On Linux, shared libraries commonly have filenames ending with .so, meaning shared object.

For example:

liblcmapi.so.1

The LCM team can distribute:

Public development files:
lcm_api.hpp
liblcmapi.so
Runtime files:
liblcmapi.so.1
liblcmapi.so.1.0

The unversioned .so name is typically used during development for linking. The numbered .so names are commonly used at runtime.

We will learn more about that distinction shortly.

5.9 How does IDS consume the LCM API?

Now the IDS team writes:

ids_demo.cpp

#include <iostream>
#include "lcm_api.hpp"
int main() {
if (lcm::isPlatformReady()) {
std::cout << "Platform ready. Start IDS.\n";
} else {
std::cout << "Platform not ready.\n";
} return 0;
}

Notice something important.

The IDS source includes the public header, but does not include lcm_api.cpp.

That’s because IDS can link against the compiled LCM library.

Press enter or click to view image in full size

5.10 Important distinction: calling a library vs calling another service

Suppose LCM is a separate daemon process.

Linking liblcmapi.so does not automatically execute code inside that daemon.

When IDS calls a function in the shared library, that function normally executes inside the IDS process.

If the function must communicate with the LCM daemon, the library needs to implement a communication mechanism such as a Unix domain socket, D-Bus, or another IPC protocol.

For example:

IDS Process
|
| Calls lcm::isPlatformReady()
v
LCM Client Library
|
| Sends IPC request
v
LCM Daemon Process
|
| Sends response
v
LCM Client Library
|
v
IDS receives result

This separation is fundamental in embedded Linux.

The public header defines what IDS can call, the client library implements the client-side behaviour, and the daemon may be responsible for the actual platform functionality.

Our small C++ example is only a direct shared-library call; it doesn’t implement IPC.

5.11 What happens if a header or library is missing?

There are three different failure points.

This gives you a powerful debugging technique.

Before fixing an error, identify which stage failed.

For example:

fatal error: lcm_api.hpp: No such file or directoryThis is a compiler error.
undefined reference to lcm::isPlatformReady()

This is typically a linker error.

error while loading shared libraries:
liblcmapi.so.1: cannot open shared object file

This is a runtime loader error.

Each requires a different investigation.

Part 3: Practical C++ example — Build LCM and IDS

We’ll first build the example on your Linux development machine, then see how the same source code can be cross-compiled using a Yocto SDK.

5.12 Create the project

Our project will contain these files:

platform-demo/
├── include/
│ └── lcm_api.hpp
├── src/
│ ├── lcm_api.cpp
│ └── ids_demo.cpp
└── build/

Use the three C++ files from Part 2.

Get Ankush Dhanda’s stories in your inbox

Join Medium for free to get updates from this writer.

Open a terminal in the platform-demo directory.

Step 1: Compile the LCM shared library

mkdir -p build
g++ -std=c++17 -fPIC -shared \
-Iinclude \
src/lcm_api.cpp \
-Wl,-soname,liblcmapi.so.1 \
-o build/liblcmapi.so.1.0

Next, create the library links:

cd build
ln -s liblcmapi.so.1.0 liblcmapi.so.1
ln -s liblcmapi.so.1 liblcmapi.so
cd ..

Your build directory now contains:

build/
├── liblcmapi.so
│ -> liblcmapi.so.1
├── liblcmapi.so.1
│ -> liblcmapi.so.1.0
└── liblcmapi.so.1.0

The actual implementation is stored in liblcmapi.so.1.0.

The other names are symbolic links.

Step 2: Compile IDS using that library

g++ -std=c++17 \
-Iinclude \
src/ids_demo.cpp \
-Lbuild \
-llcmapi \
-o build/ids-demo

Here:

  • -Iinclude helps the compiler find the public header.
  • -Lbuild tells the linker where to look for libraries.
  • -llcmapi tells it to link against the library named liblcmapi.so.

Notice that we did not compile lcm_api.cpp together with IDS.

The implementation was already compiled into the shared library.

Step 3: Run IDS

LD_LIBRARY_PATH="$PWD/build" ./build/ids-demo

Expected output:

Platform ready. Start IDS.

Why did we set LD_LIBRARY_PATH?

Because our library lives in a custom development directory rather than a normal system library directory.

We temporarily told the runtime loader where to find it.

This is suitable for the local demonstration. Production applications should use the platform’s controlled library installation and loader configuration.

Step 4: Inspect the executable

readelf -d build/ids-demo | grep NEEDED

You should see a dependency resembling:

Shared library: [liblcmapi.so.1]

You may also see dependencies on libstdc++, the C runtime, and other libraries.

This tells you that the executable references shared libraries which must be available in a compatible form at runtime.

What did we actually achieve?

This is the central idea behind a shared-library-based platform API.

The IDS team needs the public header and suitable linkable library during development. On the target board, IDS needs the compatible runtime library.

It doesn’t necessarily need the LCM source code.

Part 4: How Yocto creates and distributes the SDK

Now we’ll connect our example to your Qualcomm project.

5.13 Building a Yocto SDK

On the Yocto build host, after selecting the correct machine, distro, and image configuration, you can request an SDK:

bitbake <your-image-name> -c populate_sdk

This generates an SDK installer, normally under:

build/tmp/deploy/sdk/

The SDK corresponds to the configured image and build environment.

A platform or CI team can generate the SDK and distribute it to application developers.

This means the IDS developer does not need to perform a complete Yocto image build every time they change an application.

5.14 How does the SDK include LCM public headers?

Remember that Yocto builds recipes into packages.

Suppose the LCM library recipe is named lcm-api.

Its output may be split into:

Yocto has default packaging rules that separate development files from runtime shared libraries.

The platform team needs to ensure the recipe actually installs the public header and libraries, then includes the development package in the SDK.

For example, in an appropriate SDK configuration:

TOOLCHAIN_TARGET_TASK:append = " lcm-api-dev"

That tells the SDK generation process to include the development package in the target portion of the SDK.

The TOOLCHAIN_TARGET_TASK variable is one supported way of customizing a standard Yocto SDK.

Now the SDK can provide files resembling:

target-sysroot/
└── usr/
├── include/
│ └── lcm_api.hpp
│
└── lib/
├── liblcmapi.so
├── liblcmapi.so.1
└── liblcmapi.so.1.0

This is why installing the header as part of a recipe matters. Simply keeping the header in the LCM Git repository does not automatically make it available to other teams.

5.15 How does the IDS Yocto recipe declare its dependency?

Suppose IDS needs the LCM API.

A simplified IDS recipe would declare:

DEPENDS = "lcm-api"

This tells BitBake that the LCM API must be prepared as a build dependency before compiling IDS.

The required headers and libraries become available through Yocto’s recipe-specific sysroot.

At runtime, IDS also needs the LCM shared library.

Yocto can automatically identify and add runtime package dependencies for correctly packaged ELF shared-library references. Non-library runtime requirements, such as a separate daemon or configuration package, may require explicit dependency declarations.

This gives us an important distinction:

DEPENDS is about build-time availability. RDEPENDS is about runtime package requirements.

5.16 Using the generated SDK outside Yocto

Suppose your platform team has installed a matching SDK on your development machine.

It provides an environment setup script.

For example:

source /opt/platform-sdk/environment-setup-<target>

The exact filename will depend on your SDK.

After sourcing it, inspect the environment:

echo "$CXX"
echo "$SDKTARGETSYSROOT"

CXX identifies the target C++ compiler command, including any configured options.

SDKTARGETSYSROOT points to the target development sysroot.

Using our existing C++ example, and assuming its LCM headers and library are available in the SDK, the IDS program can be cross-compiled with:

mkdir -p build-target
${CXX} ${CXXFLAGS} \
-std=c++17 \
src/ids_demo.cpp \
-llcmapi \
${LDFLAGS} \
-o build-target/ids-demo

This assumes the SDK exposes lcm_api.hpp at a standard include location and the linker can find liblcmapi.so.

If your SDK places the public API in a custom subdirectory, you may need an additional include path.

Now inspect the resulting executable on the development machine:

file build-target/ids-demo
readelf -h build-target/ids-demo
readelf -d build-target/ids-demo | grep NEEDED

If configured for 32-bit ARM, the executable’s architecture should reflect that target. If configured for AArch64, it should reflect a 64-bit ARM target.

This demonstrates that you can create an ARM executable on your x86–64 development machine without building or flashing an entire Linux image.

5.17 What if the SDK and board versions don’t match?

Imagine two platform releases:

Platform Release 1
liblcmapi.so.1
lcm_api.hpp version 1
Platform Release 2
liblcmapi.so.2
lcm_api.hpp version 2

Your IDS application was compiled against release 2, but the Qualcomm board still runs release 1.

The application may fail because the required library is missing, the ABI changed, or some expected functionality is unavailable.

The ABI is the Application Binary Interface: the rules that let compiled pieces of software work together.

A source-level API can look similar across versions while its compiled binary interface differs.

For C++ libraries, changes to class layout, virtual functions, parameter types, symbol names, or compiler ABI settings can break compatibility.

That is why production platform teams should version their public APIs and SDKs carefully.

It is also why changing a shared library often requires more validation than changing one independent executable.

Part 5: Practical checks on your Qualcomm board

You already have physical hardware, so let’s use it to connect the theory to reality.

We will perform only read-only inspection.

Step 1: Check board architecture

On your Qualcomm board:

uname -m
getconf LONG_BIT

Typical results might include armv7l or aarch64, with a 32-bit or 64-bit userspace. These commands are useful clues, although complete ABI compatibility requires more than just these values.

Step 2: Identify an executable and its dependencies

If you know the path to an IDS executable:

file /usr/bin/ids-daemon

If readelf is installed:

readelf -d /usr/bin/ids-daemon | grep NEEDED

This helps identify the dynamic libraries required by the application.

Step 3: Inspect the library files

ls -l /usr/lib/liblcm*

The library may be stored in a different directory or have a different name. An empty result does not mean the platform provides no LCM API.

Use a known library path from your board if possible.

Step 4: Understand the difference between SDK files and board files

Development machine SDK

include/
lcm_api.hpp
lib/
liblcmapi.so
liblcmapi.so.1

Headers and linker inputs used while building software.

Production target rootfs

usr/bin/
ids-daemon
usr/lib/
liblcmapi.so.1

Runtime files required to execute installed software.

Production devices often omit development headers, compiler tools, and the unversioned library linker symlink.

This is normal.

The board needs to run the application, not necessarily compile it.

Part 3–5 recap

We’ve now connected the complete development flow:

Platform team writes LCM library
|
v
Public header + .so
|
v
Yocto recipes
|
v
Platform SDK generated
|
v
IDS developer gets SDK
|
v
Cross-compiles IDS application
|
v
ARM-compatible IDS executable
|
v
Deploy to Qualcomm board
|
v
Loader finds matching LCM library
|
v

Test yourself — Lesson 5

--

--

Ankush Dhanda
Ankush Dhanda

Written by Ankush Dhanda

Let’s Talk Systems, Processors & AI...