Linux下编写动态链接库,如何定义一个init和finalize的方法?

本文深入探讨了Linux环境下通过_init/_fini函数进行共享库初始化与~初始化的过程,指出该方法受限于gcc的占用。文章进一步介绍了替代方案,如全局对象、gcc扩展等,实现初始化与~初始化的功能,并通过代码实例详细解析了其工作原理。
共享库的初始化和~初始化函数分析



转载时请注明出处:http://blog.youkuaiyun.com/absurd/



Win32下可以通过DllMain来初始化和~初始化动态库,而Linux下则没有与之完全对应的函数,但可以通过一些方法模拟它的部分功能。有人会说,很简单,实现_init/_fini两个函数就行了。好,我们来看看事实是不是这样的。



很多资料上都说可以利用_init/_fini来实现,而我从来没有测试成功过,原因是这两个函数都已经被gcc占用了。比如:

test.c

Code: Select all
#include <stdio.h>



void _init(void)

{

    printf("%s", __func__);

}



void _fini(void)

{

    printf("%s", __func__);

}




编译结果:

[root@localhost soinit]# gcc -g test.c -shared -o libtest.so

/tmp/cc4DUw68.o(.text+0x0): In function `_init':

/work/test/soinit/test.c:5: multiple definition of `_init'

/usr/lib/gcc/i386-redhat-linux/4.0.0/http://www.cnblogs.com/../crti.o(.init+0x0): first defined here

/tmp/cc4DUw68.o(.text+0x1d): In function `_fini':

/work/test/soinit/test.c:10: multiple definition of `_fini'

/usr/lib/gcc/i386-redhat-linux/4.0.0/http://www.cnblogs.com/../crti.o(.fini+0x0): first defined here

collect2: ld returned 1 exit status



由此可见,这两个符号已经被编译器的脚手架代码占用了,我们不能再使用。编译器用这两个函数做什么?我们能不能抢占这两个函数,不用编译器提供的,而用我们自己的呢?先看看这两个的实现:

00000594 <_fini>:

594: 55 push %ebp

595: 89 e5 mov %esp,%ebp

597: 53 push %ebx

598: 50 push %eax

599: e8 00 00 00 00 call 59e <_fini+0xa>

59e: 5b pop %ebx

59f: 81 c3 02 11 00 00 add $0x1102,%ebx

5a5: e8 de fe ff ff call 488 <__do_global_dtors_aux>

5aa: 58 pop %eax

5ab: 5b pop %ebx

5ac: c9 leave

5ad: c3 ret



0000041c <_init>:

41c: 55 push %ebp

41d: 89 e5 mov %esp,%ebp

41f: 83 ec 08 sub $0x8,%esp

422: e8 3d 00 00 00 call 464 <call_gmon_start>

427: e8 b8 00 00 00 call 4e4 <frame_dummy>

42c: e8 2b 01 00 00 call 55c <__do_global_ctors_aux>

431: c9 leave

432: c3 ret





从以上代码中可以看出,这两个函数是用来初始化/~初始化全局变量/对象的,抢占这两个函数可能导致初始化/~初始化全局变量/对象出错。所以不能再打_init/_fini的主意,那怎么办呢?



使用全局对象

test.cpp

Code: Select all
#include <stdio.h>

class InitFini

{

public:

    InitFini()

    {

        printf("%s\n", __func__);

    }

    ~InitFini()

    {

        printf("%s\n", __func__);

    }

};



static InitFini aInitFini;



extern "C" int test(int n)

{

    return n;

}



Main.c

int test(int n);



int main(int argc, char* argv[])

{

    test(1);

    return 0;

}




测试结果:

[root@localhost soinit]# ./t.exe

InitFini

~InitFini

那么这两个函数是怎么被调用的呢?我们在gdb里看看:



Breakpoint 3, InitFini (this=0xa507bc) at test.cpp:7

7 printf("%s\n", __func__);

Current language: auto; currently c++

(gdb) bt

#0 InitFini (this=0xa507bc) at test.cpp:7

#1 0x00a4f5e0 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at test.cpp:15

#2 0x00a4f611 in global constructors keyed to test () at test.cpp:21

#3 0x00a4f66a in __do_global_ctors_aux () from ./libtest.so

#4 0x00a4f4a9 in _init () from ./libtest.so

#5 0x002c8b4b in call_init () from /lib/ld-linux.so.2

#6 0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2

#7 0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2



Breakpoint 4, ~InitFini (this=0x0) at test.cpp:9

9 ~InitFini()

(gdb) bt

#0 ~InitFini (this=0x0) at test.cpp:9

#1 0x00a4f5b3 in __tcf_0 () at test.cpp:15

#2 0x00303e6f in __cxa_finalize () from /lib/libc.so.6

#3 0x00a4f532 in __do_global_dtors_aux () from ./libtest.so

#4 0x00a4f692 in _fini () from ./libtest.so

#5 0x002c9058 in _dl_fini () from /lib/ld-linux.so.2

#6 0x00303c69 in exit () from /lib/libc.so.6

#7 0x002eddee in __libc_start_main () from /lib/libc.so.6

#8 0x080483b5 in _start ()

从以上信息可以看出,正是从_init/_fini两个函数调用过来的。



使用gcc扩展

毫无疑问,以上方法可行,但是在C++中才行。而C语言中,根本没有构造和析构函数。怎么办呢?这时我们可以使用gcc的扩展:

Code: Select all
#include <stdio.h>



__attribute ((constructor)) void test_init(void)

{

    printf("%s\n", __func__);

}



__attribute ((destructor)) void test_fini(void)

{

    printf("%s\n", __func__);

}



int test(int n)

{

    return n;

}




测试结果:

[root@localhost soinit]# ./t.exe

test_init

test_fini



我们不防也看看这两个函数是怎么被调到的:

Breakpoint 3, test_init () at test.c:6

6 printf("%s\n", __func__);

(gdb) bt

#0 test_init () at test.c:6

#1 0x00860586 in __do_global_ctors_aux () from ./libtest.so

#2 0x00860439 in _init () from ./libtest.so

#3 0x002c8b4b in call_init () from /lib/ld-linux.so.2

#4 0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2

#5 0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2

(gdb) c



Breakpoint 4, test_fini () at test.c:11

11 printf("%s\n", __func__);

(gdb) bt

#0 test_fini () at test.c:11

#1 0x008604d3 in __do_global_dtors_aux () from ./libtest.so

#2 0x008605ae in _fini () from ./libtest.so

#3 0x002c9058 in _dl_fini () from /lib/ld-linux.so.2

#4 0x00303c69 in exit () from /lib/libc.so.6

#5 0x002eddee in __libc_start_main () from /lib/libc.so.6

#6 0x080483b5 in _start ()



从以上信息可以看出,也是从_init/_fini两个函数调用过来的。



总结:正如一些资料上所说的,在linux下,_init/_fini是共享库的初始化和~初始化函数。但这两个函数是给gcc用的,我们不能直接使用它们,但可以用本文中提到另外两种方法来实现。
<think>好的,我现在需要帮用户将之前用Python实现的审计日志功能转换成基于libsqlite3的Linux C代码。首先,我得回顾一下用户的需求,确保C代码满足IEC 62443-3-3标准的所有要求。 首先,用户提到的功能包括:可审计事件类型、存储容量管理、处理失败响应、时间戳日志的只读访问。Python代码中使用了SQLite的触发器定期维护任务来清理日志,C代码需要实现类似的功能。 接下来,我需要考虑如何在C语言中使用SQLite3接口。记得在C中,需要使用sqlite3_open来打开数据库,然后执行SQL语句。同时,错误处理很重要,因为审计失败时需要防止服务中断,所以每个数据库操作都要检查返回值,并记录错误到系统日志。 然后,初始化数据库部分。Python代码中创建了表、索引触发器。在C中,需要编写对应的CREATE TABLE语句,并确保表结构一致。索引可以提升查询效率,所以也需要创建。触发器部分可能需要调整语法,确保在C中正确执行。 写入日志的函数需要处理并发访问。Python使用了with conn:来管理事务,C中需要手动提交事务,或者使用sqlite3_exec来执行插入语句。同时,审计失败时要记录到系统日志,这里可能需要使用syslog函数,所以在代码中包含syslog.h并适当打开日志。 查询日志的函数需要提供只读接口,使用sqlite3_prepare_v2来执行查询,并绑定参数处理时间范围、事件类型等过滤条件。结果通过回调函数处理,或者逐条获取。 日志滚动存储方面,Python用了触发器定期任务。C代码中可能需要定期执行清理操作,比如在应用中启动一个定时器,或者依赖外部cron任务。不过用户可能希望代码内部处理,所以可能需要一个维护函数,定期删除旧日志并执行VACUUM。 另外,多线程或并发访问的问题。SQLite在多线程中的行为需要注意,可能需要使用sqlite3_config(SQLITE_CONFIG_MULTITHREAD)来设置线程模式,确保线程安全。或者在C代码中使用互斥锁来保护数据库连接,因为SQLite的连接不能在多个线程中共享。 还需要考虑错误处理的完善性,比如数据库连接失败、执行SQL语句出错等情况,都要记录日志但不影响主服务运行。例如,在写入日志失败时,不能终止程序,而是记录错误并继续。 时间戳的生成,Python用了datetime('now'),C中可以用strftime获取当前时间,或者直接使用SQLite的datetime函数。这里可能更推荐使用SQLite的内置函数,保持一致性,或者在C中生成时间戳,需要考虑时区问题。 最后,代码结构需要模块化,提供初始化、写入、查询、维护等函数,并给出示例的main函数展示用法。同时,编译时需要链接sqlite3库,用户可能需要知道如何编译,比如gcc -o audit audit.c -lsqlite3。 可能还需要考虑数据库文件的权限设置,确保只有授权用户可以访问,符合只读访问的要求。在C代码中可能需要设置文件权限,或者在应用层控制。 测试方面,需要验证各个功能是否正常,比如插入日志、查询是否返回正确结果、触发器是否清理旧数据等。但用户可能不需要测试部分,只需要代码实现。 总结步骤: 1. 创建数据库表结构,包括索引触发器。 2. 实现写入日志的函数,处理错误事务。 3. 实现查询函数,支持过滤只读。 4. 维护函数清理旧日志。 5. 处理并发线程安全问题。 6. 错误处理日志记录。 7. 示例用法编译方法。</think>以下是基于IEC 62443-3-3标准要求的C语言实现方案(使用libsqlite3): ```c #include <sqlite3.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <syslog.h> #include <pthread.h> #define DB_NAME "audit_log.db" #define MAX_LOGS 10000 #define MAX_AGE_DAYS 7 // 线程安全数据库连接 typedef struct { sqlite3 *db; pthread_mutex_t lock; } safe_db; // 初始化数据库 safe_db* init_audit_db() { safe_db *sdb = malloc(sizeof(safe_db)); pthread_mutex_init(&sdb->lock, NULL); int rc = sqlite3_open(DB_NAME, &sdb->db); if (rc) { syslog(LOG_ERR, "无法打开数据库: %s", sqlite3_errmsg(sdb->db)); free(sdb); return NULL; } pthread_mutex_lock(&sdb->lock); const char *sql = "CREATE TABLE IF NOT EXISTS audit_logs (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "timestamp TEXT NOT NULL," "event_type TEXT NOT NULL," "user TEXT," "action TEXT," "ip_address TEXT," "success BOOLEAN," "details TEXT);" // 创建索引 const char *index_sql[] = { "CREATE INDEX IF NOT EXISTS idx_timestamp ON audit_logs(timestamp)", "CREATE INDEX IF NOT EXISTS idx_event_type ON audit_logs(event_type)" }; // 创建日志滚动触发器 const char *trigger_sql = "CREATE TRIGGER IF NOT EXISTS purge_old_logs " "AFTER INSERT ON audit_logs " "WHEN (SELECT COUNT(*) FROM audit_logs) > %d " "BEGIN " "DELETE FROM audit_logs WHERE timestamp < datetime('now', '-%d days'); " "VACUUM; " "END;"; char *err_msg; rc = sqlite3_exec(sdb->db, sql, NULL, 0, &err_msg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "创建表失败: %s", err_msg); sqlite3_free(err_msg); } for(int i=0; i<2; i++) { rc = sqlite3_exec(sdb->db, index_sql[i], NULL, 0, &err_msg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "创建索引失败: %s", err_msg); sqlite3_free(err_msg); } } // 动态生成触发器SQL char final_trigger_sql[1024]; snprintf(final_trigger_sql, sizeof(final_trigger_sql), trigger_sql, MAX_LOGS, MAX_AGE_DAYS); rc = sqlite3_exec(sdb->db, final_trigger_sql, NULL, 0, &err_msg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "创建触发器失败: %s", err_msg); sqlite3_free(err_msg); } pthread_mutex_unlock(&sdb->lock); return sdb; } // 写入审计日志 void write_audit_log(safe_db *sdb, const char *event_type, const char *user, const char *action, const char *ip_address, int success, const char *details) { pthread_mutex_lock(&sdb->lock); const char *sql = "INSERT INTO audit_logs (timestamp, event_type, user, action, " "ip_address, success, details) VALUES (datetime('now'), ?, ?, ?, ?, ?, ?);"; sqlite3_stmt *stmt; int rc = sqlite3_prepare_v2(sdb->db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { syslog(LOG_ERR, "准备语句失败: %s", sqlite3_errmsg(sdb->db)); pthread_mutex_unlock(&sdb->lock); return; } sqlite3_bind_text(stmt, 1, event_type, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, user, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, action, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 4, ip_address, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 5, success); sqlite3_bind_text(stmt, 6, details, -1, SQLITE_STATIC); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { syslog(LOG_ERR, "插入日志失败: %s", sqlite3_errmsg(sdb->db)); } sqlite3_finalize(stmt); pthread_mutex_unlock(&sdb->lock); } // 查询审计日志 int query_audit_logs(safe_db *sdb, const char *start_time, const char *end_time, const char **event_types, int event_count, const char *user, int limit, void (*callback)(void*,int,char**,char**)) { pthread_mutex_lock(&sdb->lock); char sql[2048] = "SELECT * FROM audit_logs WHERE 1=1"; // 构建查询条件 if (start_time) strcat(sql, " AND timestamp >= ?"); if (end_time) strcat(sql, " AND timestamp <= ?"); if (event_count > 0) { strcat(sql, " AND event_type IN ("); for(int i=0; i<event_count; i++) { strcat(sql, i == 0 ? "?" : ",?"); } strcat(sql, ")"); } if (user) strcat(sql, " AND user = ?"); strcat(sql, " ORDER BY timestamp DESC LIMIT ?"); sqlite3_stmt *stmt; int rc = sqlite3_prepare_v2(sdb->db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { syslog(LOG_ERR, "准备查询失败: %s", sqlite3_errmsg(sdb->db)); pthread_mutex_unlock(&sdb->lock); return -1; } int idx = 1; if (start_time) sqlite3_bind_text(stmt, idx++, start_time, -1, SQLITE_STATIC); if (end_time) sqlite3_bind_text(stmt, idx++, end_time, -1, SQLITE_STATIC); for(int i=0; i<event_count; i++) { sqlite3_bind_text(stmt, idx++, event_types[i], -1, SQLITE_STATIC); } if (user) sqlite3_bind_text(stmt, idx++, user, -1, SQLITE_STATIC); sqlite3_bind_int(stmt, idx++, limit); rc = sqlite3_exec(sdb->db, sql, callback, NULL, NULL); pthread_mutex_unlock(&sdb->lock); return rc; } // 定期维护任务 void maintenance_task(safe_db *sdb) { pthread_mutex_lock(&sdb->lock); const char *sql = "DELETE FROM audit_logs WHERE timestamp < datetime('now', '-7 days');" "VACUUM;"; char *err_msg; int rc = sqlite3_exec(sdb->db, sql, NULL, 0, &err_msg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "维护任务失败: %s", err_msg); sqlite3_free(err_msg); } pthread_mutex_unlock(&sdb->lock); } // 示例回调函数 int print_row(void *NotUsed, int argc, char **argv, char **azColName) { for(int i=0; i<argc; i++) { printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); } printf("\n"); return 0; } // 示例使用 int main(int argc, char *argv[]) { openlog("audit_daemon", LOG_PID, LOG_AUTH); safe_db *sdb = init_audit_db(); if (!sdb) return 1; // 记录审计日志 write_audit_log(sdb, "access_control", "admin", "login", "192.168.1.1", 1, "成功登录"); // 查询日志 const char *event_types[] = {"access_control", "config_change"}; query_audit_logs(sdb, NULL, NULL, event_types, 2, NULL, 10, print_row); closelog(); return 0; } ``` ### 符合IEC 62443-3-3标准的实现说明: 1. **线程安全**:通过pthread_mutex实现多线程安全访问 2. **事件覆盖**:支持五类安全事件记录(通过event_type字段) 3. **存储管理**: - 自动清理7天前日志(触发器+维护任务) - 使用VACUUM优化存储空间 - 最大记录数限制(触发器实现) 4. **容错机制**: - 日志写入失败记录到系统日志 - 所有数据库操作进行错误检查 5. **时间戳**:使用SQLite datetime('now')生成UTC时间戳 6. **访问控制**:通过回调函数实现只读查询接口 ### 编译方式: ```bash gcc -o audit audit.c -lsqlite3 -lpthread ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值