首页 > 解决方案 > 如何避免多重定义?链接器忽略了定义的符号,但有一些例外

问题描述

我正在开发一个嵌入式系统,它有自己的库(例如libc_alt)从标准 libc (fopen、fclose、fread、fseek、ftell)实现一些功能,但不是全部(我需要memset、memcpy、longjmp、setjmp等)标准的libc)。当我尝试将两个库都提供给编译器时

提供libc_alt

# makefile.mk 
(SRC_C += \
    $(COMP_PATH_libc_alt)/stdio.c \
) 

提供标准库

STD_LIBS += -lc -lgcc

在链接器步骤中,编译器 (arm-eabi-gcc) 正确地抱怨 fclose() 的多个定义。我的问题是,是否可以指示编译器排除标准 libc 定义 fclose() 并使用我在 $(COMP_PATH_libc_alt)/stdio.c 中编写的定义?

或者,我如何指示编译器先使用我的stdio.c,然后使用标准 libc 的 stdio.c而忽略重复的函数定义?

例如,在$(COMP_PATH_libc_alt)/stdio.c中找到 fopen() 定义之后;它将忽略标准 libc 中的 fopen()。这样我就可以同时使用 libc_alt fopen()和标准的 libc memcpy(), memset()

更新:谢谢大家的回答。@artless_noise 我之前确实放置了我的 stdio.c -lc。关于使用--wrap symbol,如果我理解正确,引用fclose()将更改为__wrap_fclose(),我需要将我fclose()stdio.c中的名称更改为__wrap__fclose(); 不幸的是,修改“我的” stdio.c是不可能的。实际上,最奇怪的是,由于我将stdio.c 放在-lc 之前,LinaroGCC arm-eabi-gcc能够为fopen(), fseek(), ftell(), fread()(nice) 选择我的定义。但它给出了multiple definition错误fclose()

根据https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Link-Options.html;上ld -l; 链接器应该忽略-lc在 my 之后出现的重复符号libc_alt.a。它确实做到了,除了一个例外fclose(),我不知道为什么?

在命令中编写此选项的位置有所不同;链接器按照指定的顺序搜索和处理库和目标文件。因此,在文件 foo.o 之后但在 bar.o 之前foo.o -lz bar.o搜索库。如果 bar.o 引用 中的函数,则可能不会加载这些函数。链接器在标准目录列表中搜索该库,该库实际上是一个名为 liblibrary.a 的文件。然后,链接器使用这个文件,就好像它已经按名称精确指定了一样。zz 搜索的目录包括几个标准系统目录以及您使用 -L 指定的任何目录。通常以这种方式找到的文件是库文件——其成员是目标文件的归档文件。链接器通过扫描归档文件来处理归档文件,其中的成员定义了迄今为止已被引用但尚未定义的符号。但是如果找到的文件是一个普通的目标文件,它会以通常的方式链接。使用 -l 选项和指定文件名之间的唯一区别是 -l 用 and 包围库lib.a搜索多个目录。

我如何调用链接器(libc_alt.a包含“我的”stdio.o)

gcc-linaro-7.1.1-2017.08-x86_64_arm-eabi/bin/arm-eabi-gcc 
-Xlinker --wrap=fclose -nostdlib -Xlinker --gc-sections 
-Xlinker --fatal-warnings -Xlinker --no-wchar-size-warning 
-Xlinker --no-enum-size-warning -Xlinker --build-id=none -T
/home/alice/Tools/Out/trusted_application.ld -Xlinker -pie -o 
/home/alice/Out/Bin/taMbedTLS.axf  /home/alice/Locals/Code/aes.o 
/home/alice/Locals/Code/sha1.o /home/alice/libraries/libc_alt.a -lc -lgcc

使用nm我的内容libc_alt.a

stdio.o:
         U __aeabi_uidiv
000000d0 t $d
0000004c t $d
00000030 t $d
00000010 N $d
00000001 T fclose
00000001 T fopen
00000001 T fread
00000001 T fseek
00000001 T ftell
         U memset
         U __stack_chk_fail
         U __stack_chk_guard
         U strchr
         U strcmp
         U strlen

stdlib.o:

使用nmLinaro LinaroGCC arm-eabi-gcc 的内容 libc.a

lib_a-fclose.o:
00000000 t $a
00000110 t $d
00000010 N $d
00000100 T fclose
00000000 T _fclose_r
         U _free_r
         U _impure_ptr
         U __sflush_r
         U __sfp_lock_acquire
         U __sfp_lock_release
         U __sinit

lib_a-fopen.o:
00000000 t $a
000000fc t $a
000000e8 t $d
00000110 t $d
00000010 N $d
000000fc T fopen
00000000 T _fopen_r
         U _fseek_r
         U _impure_ptr
         U _open_r
         U __sclose
         U __sflags
         U __sfp
         U __sfp_lock_acquire
         U __sfp_lock_release
         U __sread
         U __sseek
         U __swrite

标签: carmembeddedlibclinaro

解决方案


你能行的。您不需要以任何特殊方式指示编译器/链接器,只需链接您自己的东西,它就会被使用。

这是一个玩具示例,展示了如何获得自己的 fread() 实现。

像这样创建文件fopen_test.c

#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode) {
  printf("My own fopen implementation, just returning NULL.\n");
  return NULL;
}

现在可以这样编译:

gcc -c fopen_test.c

生成目标文件fopen_test.o

如果你有以下主程序fopen_test_main.c

#include <stdio.h>
int main() {
  printf("Calling fopen()\n");
  FILE* f = fopen("file.txt", "rb");
  printf("After fopen()\n");
  return 0;
}

然后您可以看到该程序的行为如何不同,具体取决于您是否与fopen_test.o目标文件链接。

首先,尝试使用标准 fopen() 实现:

$ gcc fopen_test_main.c
$ ./a.out

给出以下输出:

Calling fopen()
After fopen()

现在,尝试相同的方法,但链接包含特殊 fopen 实现的目标文件:

$ gcc fopen_test_main.c fopen_test.o
$ ./a.out

这给出了以下内容:

Calling fopen()
My own fopen implementation, just returning NULL.
After fopen()

因此,现在使用 fopen() 实现fopen_test.c而不是标准实现。

(我不知道你为什么会抱怨“多重定义”,需要更多关于你做了什么来解决这个问题的细节。)


推荐阅读