How to Fix a Error Compiling Dynamic Shared C Library in Linux

I recently tried to build a shared library from rs232-linux.c (from github.com/marzac/rs232) on my 64-bit Linux machine using clang. What I expected was a smooth .so build, but instead, I ran into a series of warnings and errors that had me scratching my head.

My First Attempt

I started with this simple command:

clang rs232-linux.c -dynamiclib -o librs232.so

Immediately, clang threw back:

clang: warning: argument unused during compilation: '-dynamiclib'
rs232-linux.c:42:9: warning: '__USE_MISC' macro redefined [-Wmacro-redefined]
/usr/bin/.../crt1.o: In function `_start': undefined reference to `main'
clang: error: linker command failed with exit code 1

Okay so main was missing (but it’s a library, not an executable). I tried again, switching to -shared:

clang rs232-linux.c -shared -dynamiclib -o librs232.so

Now I got:

/usr/bin/ld: ... relocation R_X86_64_32S against `.bss' can not be used when making a shared object; recompile with -fPIC
clang: error: linker command failed with exit code 1

At this point, I knew I had to dig into what each error actually meant.

Breaking Down the Error

  • argument unused: '-dynamiclib'
    I learned that -dynamiclib is a macOS-only flag (produces .dylib). On Linux, it does nothing. The correct flag for Linux is -shared.
  • undefined reference to 'main'
    Without -shared, clang assumes I’m making an executable, which requires a main(). A library doesn’t, so the linker was confused.
  • recompile with -fPIC (relocation R_X86_64_32S)
    Shared libraries on x86_64 Linux must use Position Independent Code. That means compiling with -fPIC before linking.
  • '__USE_MISC' macro redefined
    I had a line in the source redefining __USE_MISC. This is an internal glibc feature macro and not meant to be set manually. The correct approach is to use _GNU_SOURCE or _DEFAULT_SOURCE.

The Correct Build

Here’s what finally worked for me:

# Step 1: Compile with PIC
clang -O2 -Wall -Wextra -fPIC -D_GNU_SOURCE -c rs232-linux.c -o rs232-linux.o

# Step 2: Link into a shared library
clang -shared -Wl,-soname,librs232.so -o librs232.so rs232-linux.o

A shorter, one-liner version (if your .c is standalone):

clang -O2 -Wall -Wextra -D_GNU_SOURCE -fPIC -shared rs232-linux.c -o librs232.so

And finally, no errors!

Fix the Source Code Header

If your original file had this:

#define __USE_MISC // For CRTSCTS

Delete it and replace it with:

#define _GNU_SOURCE 1

Placed before all includes:

#define _GNU_SOURCE 1
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

This way, the proper termios constants are enabled without clashing with glibc internals.

Makefile

To make rebuilding easy, I wrote a Makefile:

CC      := clang
CFLAGS  := -O2 -Wall -Wextra -fPIC -D_GNU_SOURCE
LDFLAGS := -shared -Wl,-soname,librs232.so
SRC     := rs232-linux.c
OBJ     := $(SRC:.c=.o)
TARGET  := librs232.so

all: $(TARGET)

$(OBJ): %.o : %.c
	$(CC) $(CFLAGS) -c $< -o $@

$(TARGET): $(OBJ)
	$(CC) $(LDFLAGS) -o $@ $^

clean:
	$(RM) $(OBJ) $(TARGET)

.PHONY: all clean

Now I just run:

make
ls -l librs232.so

Testing in C

Here’s a small test program (test_rs232.c):

#include <stdio.h>
#include <stdlib.h>

// Example API signatures
int rs232_open(const char* dev, int baud);
void rs232_close(int fd);

int main(void) {
    int fd = rs232_open("/dev/ttyS0", 115200);
    if (fd < 0) {
        perror("rs232_open");
        return 1;
    }
    printf("Opened serial port, fd=%d\n", fd);
    rs232_close(fd);
    return 0;
}

Build and run:

clang -O2 -Wall -Wextra test_rs232.c -L. -lrs232 -o test_rs232
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./test_rs232

Testing with Python (ctypes)

# test_rs232.py
import ctypes, os

lib = ctypes.CDLL(os.path.abspath("./librs232.so"))

lib.rs232_open.argtypes = [ctypes.c_char_p, ctypes.c_int]
lib.rs232_open.restype  = ctypes.c_int

fd = lib.rs232_open(b"/dev/ttyS0", 115200)
print("fd =", fd)

if fd >= 0:
    lib.rs232_close.argtypes = [ctypes.c_int]
    lib.rs232_close(fd)

Run:

python3 test_rs232.py

If you get an error loading the .so, don’t forget:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

Final Thought

This debugging journey reminded me how platform-specific build flags can be, and how small details (-fPIC, _GNU_SOURCE) make a huge difference in C shared library compilation. At first, the errors felt cryptic, but once I broke them down, the fix was straightforward. Now I can reliably build librs232.so, link it into C test programs, and even load it from Python. If you’re struggling with similar linker or PIC errors don’t panic. With the right flags and a bit of cleanup, your .so will compile cleanly.

Related blog posts