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 amain()
. 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.