C 共享库中的某些 C 符号即使具有显式可见性也不会导出
Some C symbols in a C shared library are not exported even with explicit visibility
前言
我正在用 C 开发一个面向 object 的库,它同时针对 Linux 和 Windows。
目前我正在 Linux 虚拟机(来宾)上进行开发,因为我正在使用 clang 消毒剂。
我正在使用 -fvisibility=hidden
构建,所以我使用 __attribute__((visibility("default")))
仅导出所需的函数。
据我所知,该属性需要与函数定义一起使用,所以我将它放在源文件中,让 headers 保持干净。
我的库依赖于一种非常简单的面向 object 的方法,在 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
会产生有趣的结果。符号不存在!没有隐藏,甚至没有出现在 object!
中
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__
实际代码
Header guards, asserts and various non-related stuff omitted.
allocator.h |这是基础 class
#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);
allocator.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/free 包装
// 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
添加到构建系统...
前言
我正在用 C 开发一个面向 object 的库,它同时针对 Linux 和 Windows。
目前我正在 Linux 虚拟机(来宾)上进行开发,因为我正在使用 clang 消毒剂。
我正在使用 -fvisibility=hidden
构建,所以我使用 __attribute__((visibility("default")))
仅导出所需的函数。
据我所知,该属性需要与函数定义一起使用,所以我将它放在源文件中,让 headers 保持干净。
我的库依赖于一种非常简单的面向 object 的方法,在 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
会产生有趣的结果。符号不存在!没有隐藏,甚至没有出现在 object!
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__
实际代码
Header guards, asserts and various non-related stuff omitted.
allocator.h |这是基础 class
#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);
allocator.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/free 包装
// 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
添加到构建系统...