dyld: lazy symbol binding failed
Well, I had a really good one today. Thankfully, I only wasted about an hour.
Here's the outline: I have a userspace driver that does some relatively generic things that I compiled as a dynamic library (let's call it libmydev_ctrl.dylib). On top of that, I have a device-specific userspace driver for specific functionality that I compiled as a dynamic library (let's call it libmydev_ctrl_spec.dylib). On top of that, I have binaries that use device-specific drivers (let's call it myexecutable).
Compiling is fairly straightforward. First, the "generic" dylib:
12345678910 | DYLIB_ALL_NAME := libmydev_ctrl.dylib
TARGET_DYLIB_ALL := $(TARGET_DIR)/$(DYLIB_ALL_NAME)
INST_DYLIB_ALL := $(INSTALL_DIR)/$(DYLIB_ALL_NAME)
$(TARGET_DYLIB_ALL): $(OBJECTS_ALL) | $(TARGET_DIR)
@printf "%-16s %-8s %s\n" "link" "LLD" $(notdir $@)
@$(COMPILER) -o $@ \
-install_name $(INST_DYLIB_ALL) -dynamiclib \
$^ $(FLAGS) $(INCLUDES) $(LIBRARIES)
|
And then we do something very similar to compile the device-specific dylib; as you can see, the only real difference in the makefile is that the "generic" dylib is a pre-requisite of the "specific" dylib:
12345678910 | DYLIB_PLAT_NAME := libmydev_ctrl_$(TARGET_NAME).dylib
TARGET_DYLIB_PLAT := $(TARGET_DIR)/$(DYLIB_PLAT_NAME)
INST_DYLIB_PLAT := $(INSTALL_DIR)/$(DYLIB_PLAT_NAME)
$(TARGET_DYLIB_PLAT): $(OBJECTS_PLAT) $(TARGET_DYLIB_ALL) | $(TARGET_DIR)
@printf "%-16s %-8s %s\n" "link" "LLD" $(notdir $@)
@$(COMPILER) -o $@ \
-install_name $(INST_DYLIB_PLAT) -dynamiclib \
$^ $(FLAGS) $(INCLUDES) $(LIBRARIES)
|
Finally, we compile the binary/executable; we make the "generic" and "specific" dylibs pre-requisites, along with the source file with the main function:
1234 | $(TARGET): $(TARGET_DYLIB_ALL) $(TARGET_DYLIB_PLAT) $(SRC)
@printf "%-16s %-8s %s\n" "compile/link" "C++/LLD" $(notdir $@)
@$(COMPILER) -o $@ $^ $(FLAGS) $(INCLUDES) $(LIBRARIES)
|
If you're trying to follow along without make, the commands look like this:
1234567891011121314151617181920 | c++ -o libmydev_ctrl.dylib \
-install_name /usr/local/opt/libmydev_ctrl.dylib -dynamiclib \
source1.o source2.o source3.o \
-Wall -Werror -Wextra -Wno-unused-variable \
-Wno-unused-parameter -Wno-unused-function \
-Wno-unused-private-field \
-std=c++1y -g
c++ -o libmydev_ctrl_spec.dylib \
-install_name /usr/local/opt/libmydev_ctrl_spec.dylib -dynamiclib \
spec_source.o libmydev_ctrl.dylib \
-Wall -Werror -Wextra -Wno-unused-variable \
-Wno-unused-parameter -Wno-unused-function \
-Wno-unused-private-field \
-std=c++1y -g
c++ -o myexecutable libmydev_ctrl.dylib libmydev_ctrl_spec.dylib dostuff.cpp \
-Wall -Werror -Wextra -Wno-unused-variable -Wno-unused-parameter \
-Wno-unused-function -Wno-unused-private-field -std=c++1y -g
|
But what falls out? Not good things. Solution after the jump:
1234567 | dyld: lazy symbol binding failed: Symbol not found: __Z4init8dev_type
Referenced from: myexecutable
Expected in: /usr/local/opt/libmydev_ctrl.dylib
dyld: Symbol not found: __Z4init8dev_type
Referenced from: myexecutable
Expected in: /usr/local/opt/libmydev_ctrl.dylib
|
But this makes no sense, because
does exist, except it's in
libmydev_ctrl_spec.dylib.
Easy to check that it was linked properly:
123456 | $ otool -L myexecutable
myexecutable:
/usr/local/opt/libmydev_ctrl.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/local/opt/libmydev_ctrl_spec.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)
|
And of course using
shows that the files definitely exist. Next, we use
:
12345678 | $ nm -gU /usr/local/opt/libmydev_ctrl_spec.dylib
00000000000016a0 T __Z10read_adc_vh
00000000000016e0 T __Z10set_dac_mvht
...snip...
0000000000000b40 T __Z4init8dev_type
...snip....
00000000000018d0 T __Z9set_dac_vhd
0000000000001020 T __ZN12PlatformCtrl8is_validEv
|
And of course it does not exist in libmydev_ctrl.dylib because that's not where it belongs.
So the next thing I tried was to change the compile order and the same thing happened, just in the other direction - it became clear that it was successfully looking into the first dylib but not the second.
Here's the solution:
The files libmydev_ctrl.dylib and libmydev_ctrl_spec.dylib are named similarly, so based on the standard dylib way of finding libraries, the "name.dylib" and "name_xxx.dylib" pattern makes it look like the second dylib is another version of the first; that is, they're the same library, and if one works there's no need to look further.
The answer is then obvious: just change the names a bit. EG, libmydev_ctrl.dylib and libmydev_spec_ctrl.dylib. That was easy.
Ches Koblents
April 16, 2018