复习 Linux ELF 共享库的版本概念

  • soname for a shared library: lib<library name>.so.<major ver>
    • 例: libev.so.4
  • fully-qualified soname for a shared library: <path>/lib<library name>.so.<major ver>
    • 例: /usr/lib/libev.so.4
  • real name for a shared library: lib<library name>.so.<major ver>.<minor ver>.<release>
    • 例: libev.so.4.0.0
  • linker name for a shared library: lib<library name>.so

一般 soname 对应的是一个符号链接,是在运行 ldconfig 时由其根据共享库 header 中的 SONAME 域创建的。如果创建共享库时未通过 -Wl,-soname,... 指定其 SONAME ,则 ldconfig 不会为其创建对应的 soname 符号链接。linker name 对应的符号链接主要用于开发链接使用,一般是创建一个指向 soname 而不是 real name 的链接,以便减少版本更替时需要改变的链接数量(当然共享库数量较少时指向 soname 或 real name 均可,系统软件包也是两种方式都有采用的例子)。

<major ver> 主要表明接口 ABI 兼容性,一般如果共享库接口产生了非向前兼容的更改就要升级 <major ver>

CentOS/RHEL 上 Apache 一个诡异问题的解决(都是浮云)

最近帮同事检查一个 Apache 的问题,现象如下:

  • 原本配置了多个 VirtualHostDocumentRoot 指向 /var/www/ 下不同的子目录,都能正常工作
  • 新加了一个 VirtualHost ,将 DocumentRoot 指向 /home/aa/ ,重启 Apache 后无法访问该 VirtualHost 下的内容,提示 403 Forbidden ,而此时访问原先的几个 VirtualHost 还是没有问题
  • Apache 错误日志里没有什么奇怪的输出
  • 操作系统 CentOS 4.x

首先考虑是不是新加的 VirtualHost 配置有误导致文件路径映射错误,所以用 strace 跟了一下 Apache 看它在哪个阶段出问题的,发现 Apache 去访问的文件路径在 /home/aa/ 下,该文件也确实存在,从 /home/aa/ 直到该文件的各级目录都有 a+rx 权限, Apache 应该能直接访问。诡异的是, Apache 进程调用的 lstat64 系统调用对 /home/home/aa 进行权限检查时都是 OK,但对 /home/aa 下的目标文件进行权限检查时却是 EACCESS ,让人百思不解。

后来才发现原来该系统上未关闭 SELinux 子系统,而 SELinux 默认会对 Apache 进程 httpd 进行若干保护,其中一种就是只允许 httpd 访问特定路径下的文件,系统默认的 /var/www 在允许之列,而其他目录都是禁止访问的。解决办法有二:

  • 临时关闭 SELinux 对 httpd 的保护:
    1. setsebool -P httpd_disable_trans 1
    2. 重启 Apache
  • 永久关闭 SELinux 子系统:
    1. 修改 /etc/sysconfig/selinux ,将其中的 SELINUX=... 改为 SELINUX=disabled
    2. 重启操作系统

LD_PRELOAD 对 PHP extension 失效的原因

最近写了一个主机健康检测和软负载均衡用的软件 ZFOR ,可以通过 LD_PRELOAD 预载入的方式拦截系统的域名解析调用( gethostbynamegetaddrinfo 等)。但发现对某些发行版的 PHP CURL extension 光加载 zfor 动态库没有用,还必须同时加载 libcurl.so 才能生效。

经过一番搜索,发现原来是 PHP 编译时让 Zend 引擎使用了 RTLD_DEEPBIND 标志来加载扩展,该标志的作用是约束动态符号解析的查找范围为载入的动态库及其依赖项,而不是从所有已加载项的符号表中寻找。PHP 启用该选项的原意是避免同时加载多个具有共同依赖库的扩展时产生多版本符号解析冲突的问题,但在这里就影响了 zfor 的 preload 拦截过程。因为 PHP CURL 扩展只依赖 libcurl.so 而不依赖 libzfor.so ,故在不预加载 libcurl.solibzfor.so 不会列入 PHP CURL 扩展符号解析的查找范围,这样在 libzfor.so 中定义的 getaddrinfo 别名自然无法生效。

在同时预加载 libcurl.solibzfor.so 时,由于是系统的动态库加载器 ld-linux.so 按默认解析策略进行符号解析,故会将 libcurl.so 中引用的 getaddrinfo 符号解析到 libzfor.so 中提供的实现上,而由于动态库都是单实例加载的,这样当 PHP CURL 扩展被载入时,其依赖的 libcurl.so 就会用之前预加载的那个实例来解决,不需要再进行符号查找,也就不用受到 RTLD_DEEPBIND 标志的约束了。

在 Xen 虚拟机下修改系统当前时间

Xen 虚拟机默认不允许不同的虚拟机使用不同的系统时间,因此所有虚拟机的系统时间都会同宿主机的系统时间严格同步,用 date 命令修改虚拟机系统时间时虽然提示成功但其实系统时间还是没变。若有独立修改 Xen 虚拟机的特殊需要,可以通过如下方法进行:

  • 在 Xen 虚拟机的 root 提示符下输入如下命令之一以启用虚拟机独立的系统时间:
    1
    echo 1 > /proc/sys/xen/independent_wallclock
    1
    sysctl xen.independent_wallclock=1
  • 然后用 date -s <target date> 应该就可以设置系统时间了

这样的设置在虚拟机重启以后就会失效,若要使该设置永久生效,可以进行如下改动之一:

  • 修改 /etc/sysctl.conf 文件,增加如下内容:
1
2
# Set independent wall clock time
xen.independent_wallclock=1
  • 在虚拟机启动时的 kernel 命令行中加入选项 independent_wallclock=1

参考资料

Accessing EXT2/3 partition under Windows XP

After some googling I found several ways to do the job:

  1. Explore2fs
    • Pros: Open-source; No installing needed
    • Cons: Read only; Exporting file is too slow...
  2. Ext2 IFS
    • Pros: Mount EXT2/3 partition to a real disk letter; High accessing speed
    • Cons: Installing needed; No source available
  3. Ext2Fsd
    • Pros: Opensource; Mount EXT2/3 partition to a real disk letter
    • Cons: Installing needed; Complex operations
  4. Diskinternal Linux Reader
    • Pros: User-friendly interface with lots of whistles
    • Cons: Installing needed; No source available; Not support UTF-8 charset
  5. Virtual Volumes
    • Pros: Opensource
    • Cons: Installing needed; Not completed yet...

Finally I chose Ext2 IFS 😃

如何避免 boost::shared_ptr 管理的共享对象被意外删除

boost::shared_ptr (已进入C++ TR1标准)是管理共享对象的好帮手,但由于其 get() 方法能获取原对象裸指针,因此存在其管理的对象被人为意外删除的危险。最近看 boost 相关资料时发现一些方法能避免该问题。

方案1 - 使用包装类

1
2
3
4
5
6
7
8
9
10
11
12
class T {
protected:
~T() {...}
public:
...
};

class TWrapper {
};

boost::shared_ptr<T> p(new TWrapper);
delete p.get(); // Oops...Compile-time error!

这是因为 shared_ptr 对象内部指向的共享引用计数对象保存的是原始指针(这里就是 TWrapper* ),引用计数为 0 时可以用该指针正常调用 delete ,而从 get() 方法获得的是 shared_ptr 对象缓存的指针,类型为 T* ,而 class T 的析构函数被保护了,故不能显式被 delete 。该方案缺点是需要引入一个新类,且当原类构造函数有多种形式时,包装类也要对应提供这些形式的构造函数,二者是紧耦合的;优点是创建对象方式同以前没有变化。

方案2 - 使用自定义析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class T {
struct deleter {
void operator() (T *p) { delete p; }
};
friend class deleter;
protected:
T() { ... }
~T() { ... }
public:
static boost::shared_ptr<T> createObject()
{
return boost::shared_ptr<T>(new T(), T::deleter());
}
};

boost::shared_ptr<T> p = T::createObject();
delete p.get(); // Oops...Compile-time error!

该方案利用 shared_ptr 的自定义析构函数形式实现,将析构函子类作为私有类封装在 class T 内部,以避免被外部直接利用;创建对象时也不再使用标准 new 形式,而是封装了对应的静态工厂方法,为了避免绕过工厂方法创建对象,还要将 class T 的构造函数也保护起来。该方案的缺点是创建对象方法彻底改变,且工厂方法接口要同原有类构造函数的形式一致;优点是所有的机制都封装在原有类内部,不需要额外引入新类型。

shared_ptr 内部的共享引用计数对象保存原始指针这一特性可知,若创建 shared_ptr 对象时传入的是派生类指针,即使基类没有将析构函数定义为 virtual 的,共享对象被删除时也会正常调用派生类的析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base {
public:
~Base() { cout << "Base dtor\n"; }
};

class Derived : virtual public Base {
public:
~Derived() { cout << "Derived dtor\n"; }
};

{
boost::shared_ptr<Base> p(new Derived);
// shared_ptr object release managed-object here, will call dtors of class Derived and Base in order
}

Base *q = new Derived;
delete q; // Here only dtor of class Base is called for it's not declared to be virtual