即使具有显式可见性,C 共享库中的某些 C 符号也不会导出



前言

我正在用C开发一个面向对象的库,它同时面向Linux和Windows。

目前,我正在Linux VM(来宾(上进行开发,因为我正在使用clang消毒剂。

我正在使用-fvisibility=hidden构建,所以我使用__attribute__((visibility("default")))仅导出所需的函数。

据我了解,该属性需要与函数定义一起使用,因此我将其放在源文件中,使标头保持干净。

我的库依赖于一种非常简单的面向对象的方法,可以在SO上找到,该方法使用定义函数的struct base,这些函数在内部调用函数指针vtable

问题

构建共享库成功,而构建测试则不成功。

链接器抱怨对某些函数的未定义引用:

[ 17%] Built target clib
Scanning dependencies of target test_undefined_san
[ 20%] Building C object CMakeFiles/test_undefined_san.dir/test/allocator/test_default_allocator.c.o
[ 23%] Linking C executable test_undefined_san
/usr/bin/ld: /tmp/lto-llvm-b74224.o: in function `test_default_allocator':
<path>/test_default_allocator.c:17: undefined reference to `clib_init'
/usr/bin/ld: <path>/test_default_allocator.c:21: undefined reference to `clib_allocate'
/usr/bin/ld: <path>/test_default_allocator.c:23: undefined reference to `clib_deallocate'
/usr/bin/ld: <path>/test_default_allocator.c:26: undefined reference to `clib_finalize'
clang-8: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [CMakeFiles/test_undefined_san.dir/build.make:160: test_undefined_san] Error 1
make[1]: *** [CMakeFiles/Makefile2:328: CMakeFiles/test_undefined_san.dir/all] Error 2
make: *** [Makefile:95: all] Error 2
The terminal process terminated with exit code: 2

事实是,这些函数的定义用__attribute__((visibility("default")))标记

。执行nm libname.so会带来有趣的结果。符号不存在!没有隐藏,甚至没有出现在物体内部!

0000000000001140 T clib_timer_end   <--| other tests, exported correctly
0000000000001110 T clib_timer_start <--| 
U clock@@GLIBC_2.2.5
0000000000004028 b completed.7383
w __cxa_finalize@@GLIBC_2.2.5
0000000000001040 t deregister_tm_clones
00000000000010b0 t __do_global_dtors_aux
0000000000003df8 t __do_global_dtors_aux_fini_array_entry
0000000000004020 d __dso_handle
0000000000003e00 d _DYNAMIC
0000000000001170 t _fini
0000000000001100 t frame_dummy
0000000000003df0 t __frame_dummy_init_array_entry
00000000000020a8 r __FRAME_END__
0000000000004000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000002004 r __GNU_EH_FRAME_HDR
0000000000001000 t _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000001070 t register_tm_clones
0000000000004028 d __TMC_END__

实际代码

省略了标题保护、断言和各种不相关的东西。

allocator.h| 这是基类

#include <stdbool.h>
#include <stddef.h>
struct clib_allocator;
typedef bool (*clib_alloc_init_t)(struct clib_allocator *);
typedef bool (*clib_alloc_finalize_t)(struct clib_allocator *);
typedef void *(*clib_alloc_allocate_t)(struct clib_allocator *, size_t, size_t);
typedef void (*clib_alloc_deallocate_t)(struct clib_allocator *, void *, size_t, size_t);
struct clib_allocator_vtable
{
clib_alloc_init_t init;
clib_alloc_finalize_t finalize;
clib_alloc_allocate_t allocate;
clib_alloc_deallocate_t deallocate;
};
struct clib_allocator
{
struct clib_allocator_vtable vtable;
};
bool clib_init(struct clib_allocator *alloc);
bool clib_finalize(struct clib_allocator *alloc);
void*clib_allocate(struct clib_allocator *alloc, size_t sz, size_t align);
void clib_deallocate(struct clib_allocator *alloc, void *p, size_t sz, size_t align);

分配器.c

// Just calls alloc->vtable.method
#include <clib/allocator/allocator.h>
#define EX __attribute__((visibility("default"))) // just to reduce code
bool EX clib_init(struct clib_allocator *alloc)
{
return alloc->vtable.init(alloc);
}
bool EX clib_finalize(struct clib_allocator *alloc)
{
return alloc->vtable.finalize(alloc);
}
void* EX clib_allocate(struct clib_allocator *alloc, size_t sz, size_t align)
{
return alloc->vtable.allocate(alloc, sz, align);
}
void EX clib_deallocate(struct clib_allocator *alloc, void *p, size_t sz, size_t align)
{
alloc->vtable.deallocate(alloc, p, sz, align);
}

default_allocator.h| 实现分配器

#include <clib/allocator/allocator.h>
struct clib_allocator* clib_get_default_allocator();

default_allocator.c| aligned_alloc/免费包装器

// Only clib_get_default_allocator should be exported
#include <clib/allocator/default_allocator.h>
#include <stdlib.h>
#include <assert.h>
#include <threads.h>
static bool nop(struct clib_allocator *CLIB_UNUSED alloc)
{
return true;
}
static void *aligned_alloc_w(struct clib_allocator *CLIB_UNUSED alloc, size_t sz, size_t align)
{
return aligned_alloc(align, sz);
}
static void free_w(struct clib_allocator *CLIB_UNUSED alloc, void *p, size_t CLIB_UNUSED sz, size_t CLIB_UNUSED align)
{
free(p);
}
static once_flag clib_init_default_alloc_flag = ONCE_FLAG_INIT;
static struct clib_allocator clib_default_alloc;
static void clib_init_default_allocator()
{
clib_default_alloc.vtable.init = nop;
clib_default_alloc.vtable.finalize = nop;
clib_default_alloc.vtable.allocate = aligned_alloc_w;
clib_default_alloc.vtable.deallocate = free_w;
}
#define EX __attribute__((visibility("default"))) // just to reduce code
struct clib_allocator* EX clib_get_default_allocator()
{
call_once(&clib_init_default_alloc_flag, clib_init_default_allocator);
return &clib_default_alloc;
}

test_default_allocator.c

#include <cute.h> // test assert library
#include <clib/allocator/default_allocator.h>
void test_default_allocator()
{
SET_SIMPLE_SCENARIO("Testing defaut allocator");
TEST_CASE()
{
// this is exported if I comment out the other functions
struct clib_allocator *alloc = clib_get_default_allocator();
ASSERT(alloc);
void *p = NULL;
ASSERT(clib_init(alloc)); // undefined ref
for (size_t align = 2; align > 4096; align <<= 1)
{
p = clib_allocate(alloc, 4096, align); // undefined ref
ASSERT(p);
clib_deallocate(alloc, p, 4096, align); // undefined ref
}
ASSERT(clib_finalize(alloc)); // undefined ref
}
}

建筑标志和信息

共享库标志

-Wall
-Wextra
-Werror
-pedantic-errors
-fno-omit-frame-pointer
-fPIC
-m64
-fcolor-diagnostics
-fvisibility=hidden

测试可执行标志

-Wall
-Wextra
-pedantic-errors
-Wno-unused-function
-fsanitize={address, thread, memory, undefined} // 4 different executables

系统信息

Linux Manjaro with kernel 5.13
glibc 2.29
Clang 8.0.1
GCC 9.1.0

忘记将allocator.c添加到构建系统中...

相关内容

最新更新