LoginSignup
0
0

More than 5 years have passed since last update.

APR DBDでデータベース操作するApacheモジュールをC++で書いた時のリンクエラーを回避

Posted at

TL;DR

  • APR DBDを使うApacheモジュールをC++で書いた
  • Apache起動時にリンクエラー発生!
  • 直した

APR DBDってなんだ

APR(Apache Portable Runtime)というApacheのサポートライブラリがあります。もともとはApacheの一部でしたが独立したプロジェクトとして便利関数群を抽出したものみたいです。
APRは大きく3種類に分けられています。

  • 基本的な関数をまとめたAPR
  • 便利関数をまとめたAPR-util
  • 文字コードを扱うためのAPR-iconv

APR DBDは、APR-utilに含まれる、各種RDBMSへの接続や操作を共通化されたAPIから利用できるようにするモジュールです。このAPIを使うことでMySQLとかPostgreSQLとかを意識せずにDB操作できるようになるわけですね。
このAPR DBDを使い、データベースを操作するApacheモジュールを書いてみようと思いました。C++で。

DockerでMySQLを動かす

前置きが長かったですが、本題はまだです。データベース操作をするためにはデータベースが必要です。
手っ取り早くDockerでMySQLサーバを動かしましょう。以下のコマンドでさくっとサーバを立てて接続できます。パスワードの入力を求められるのでpasswordと入力します。

$ sudo docker run --name mysqld -e MYSQL_ROOT_PASSWORD=password -d -p 13306:3306 mysql
$ sudo docker exec -it mysqld  mysql -u root -p

なお、MySQLコマンドラインツールが使えるなら以下のやり方でも接続できます。

$ mysql -u root -h 127.0.0.1 -P 13306 -p

ポートフォワードを使って13306->3306に転送されるようにしたのはこのためです(私が試した時は、ホストでmysqlが動ているとそっちにつなぎに行っちゃってました。もしかしたら勘違いかもしれないのでよく調べます)。
接続できたら、データベースとテーブルを作って何かをINSERTしておきます。

mysql> CREATE DATABASE testdb DEFAULT CHARACTER SET utf8;
mysql> USE testdb;
mysql> CREATE TABLE test (id INTEGER AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), body VARCHAR(255)) DEFAULT CHARSET=utf8;
mysql> INSERT INTO test(title, body) VALUES('aaa', 'bbb');
mysql> INSERT INTO test(title, body) VALUES('hoge', 'fuga');

これでデータベースが用意出来ました。

Apacheモジュールを作る

それではこのMySQLサーバのtestdbに接続し、SELECT * FROM testの結果を表示するようなApacheモジュールを書いてみます。
ぐぐって色々調べながら最初に書いたコードはこちらです。
https://gist.github.com/d9magai/763f936c4cbbabd08986ef387bcce8c5

下のようなconfを用意して/etc/httpd/conf.dとかに置いておきます。curl localhost/condbとコマンドを打てばモジュールの実行結果が見れるようにします。

mod_condb.conf
LoadModule condb_module    /usr/lib64/httpd/modules/mod_condb.so
<location /condb>
  SetHandler condb
</Location>

DBDriver mysql
DBDParams "host=127.0.0.1,user=root,pass=password,dbname=testdb,port=13306,sock=/tmp/mysql.sock"
DBDPersist ON
DBDKeep 5
DBDMax 5
DBDMin 2
DBDExptime 100

準備ができたらモジュールをコンパイルしてインストールします。

$ g++ -c -fPIC -I`apxs -q INCLUDEDIR` -I/usr/include/apr-1/ `apxs -q CFLAGS` `apxs -q CFLAGS_SHLIB` -Wall -o mod_condb.o mod_condb.cpp 
$ g++ -fPIC -shared -o mod_condb.so mod_condb.o `apxs -q LIBS_SHLIB`
$ sudo apxs -A -i -a -n 'condb' mod_condb.so

さて、満を持してApacheを起動するとエラーがでて動かないと思います。

httpd: Syntax error on line 360 of /etc/httpd/conf/httpd.conf: Syntax error on line 1 of /etc/httpd/conf.d/mod_condb.conf:Cannot load /usr/lib64/httpd/modules/mod_condb.so into server: /usr/lib64/httpd/modules/mod_condb.so: undefined symbol: _Z14ap_dbd_acquireP11request_rec

undefined symbol: _Z14ap_dbd_acquireP11request_recというのがエラーの原因ですね。詳しく調べてみました。

名前マングリングとシンボル名

シンボルが見つからないというエラーですが、そもそもシンボルってなんでしょう。
シンボルの説明の前に、C/C++のコードが実行可能になるまでの処理の流れを調べたので説明します。大きく分けて以下の4つになるみたいです。

  • プリプロセス(gcc -E main.cでプリプロセス後のソースを見れる)
  • コンパイル(gcc -S main.cでコンパイル直後のアセンブラソースに変換した段階で処理を止める。.sファイルができる)
  • アセンブル(as -o main.o main.sアセンブラソースをアセンブルして、オブジェクトファイル(バイナリ)を生成する)
  • リンク(gcc -o main main.oオブジェクトファイルを実行可能にする)

アセンブルとリンクの間に生成されファイルがオブジェクトファイルです。CやC++において関数名は、オブジェクトファイルの中ではシンボル名として扱われています。
ちょっと実験をしてみます。オブジェクトファイルはバイナリといいましたが、中を覗けるコマンドがあります。nmです。

オブジェクトファイルを覗いてみる

実験に使うコードはこちらです。

main.c
#include<stdio.h>

void hoge() {
    printf("Hello World\n");
}

int main(void) {
    hoge();
    return 0;
}

先ずはgccを使ってオブジェクトファイルを作った時です。

bash-4.2# gcc -c main.c -o main.o
bash-4.2# nm main.o 
0000000000000000 T hoge
0000000000000010 T main
                 U puts

hogemainというシンボル名ができているのがわかります。関数名がそのままシンボル名になっています。わかりやすいですね。

次にg++を使ってみます。

bash-4.2# g++ -c main.c -o main.o
bash-4.2# nm main.o 
0000000000000000 T _Z4hogev
0000000000000010 T main
                 U puts

mainは分かりますが_Z4hogevという感じで、hogeになんだか変な文字がくっついてごちゃごちゃしています。

どうしてシンボル名がごちゃごちゃしてる?

これは、C++には関数のオーバーロードや名前空間、クラスのメンバ関数という概念があることと関係しています。C++はCと異り、同じ名前を持つ関数が複数存在することが有り得ます。関数名をシンボル名に使ってしまっては衝突してしまいます。
C++コンパイラはそういった付加情報をシンボル名に埋め込み、リンク時に適切な関数と結合できるようにしています。このような処理を名前マングリング(name mangling)と言います。

もう一度実験してみます。今度は次のようなコードです。

main.c
#include<stdio.h>

void hoge() {
    printf("Hello World\n");
}

void hoge(int n) {
    printf("Hello World\n");
}

int main(void){
    hoge();
    return 0;
}

引数の違うhoge関数が2つあります。さて、オブジェクトファイルを作って中身を覗いてみましょう。

bash-4.2# g++ -c main.c -o main.o
bash-4.2# nm main.o 
0000000000000010 T _Z4hogei
0000000000000000 T _Z4hogev
0000000000000027 T main
                 U puts

2つの異なるシンボル名があるのが確認できました。

extern "C"を使って回避

名前マングリングの解説をしてきました。改めて問題のエラーを見返してみます。

undefined symbol: _Z14ap_dbd_acquireP11request_rec

このエラーは、ap_dbd_acquire()と関数をコールしている箇所をC++コンパイラが名前マングリングでシンボル名に付加情報を埋め込み、Cコンパイラでコンパイルしたライブラリをダイナミックリンクをしようしたら_Z14ap_dbd_acquireP11request_recというシンボルは見つからなかった、という意味になるかと思います。
Cコンパイラによってap_dbd_acquireという感じの素直なシンボル名で定義されているにも関わらず、モジュールは_Z14ap_dbd_acquireP11request_recという複雑なシンボル名を期待して、undefined symbolと言ってるわけですね。

Cで書いた関数をC++から呼びたい、もしくはその逆の場合、C++コンパイラに対して名前マングリングせずにC言語と同じく関数名をそのままシンボル名にしてほしい、ということを教えなければなりません。そこでextern "C"の出番です。これは名前マングリングを回避したいのでC言語として解釈して下さい、とg++に伝えたい時に使われます。
問題のap_dbd_acquireという関数はmod_dbd.hで宣言されていました。
https://ci.apache.org/projects/httpd/trunk/doxygen/mod__dbd_8h_source.html
ですので、#include <mod_dbd.h>extern "C"で囲んであげます。

extern "C"{
#include <mod_dbd.h>
}

こうすることでap_dbd_acquireはマングルしなくなるため、モジュールはap_dbd_acquireというシンボルを探すようになります。

名前マングリングを回避したら動いた

これでApacheを実行してもリンクエラーは発生しなくなりました。動作確認しますと、ちゃんとDB操作できていました。

$ curl localhost/condb
1,aaa,bbb,
2,hoge,fuga,
The sample page from mod_condb.c

Dockerで再現できるようにした

もしも、自分のとこでも動かしてみたい、と思われた方が居た時に簡単に試せるようにDockerで再現できるようにしてみました。
https://github.com/d9magai/docker-mod_condb
./run.shを実行しますとデータベース作成からモジュールをビルドしてApacheを起動するところまで動きます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0