继续研究audit plugin,首先gdb源代码
启动mysqld,加上--debug选项
进入gdb:
(gdb) attach 19079 (mysqld的进程号)
(gdb) b mysql_audit_notify(THD*, unsigned int, unsigned int, ...) (猜测该函数会调用audit的notify函数)
(gdb) c
Continuing.
启动mysql客户端
Breakpoint 1, mysql_audit_notify (thd=0x16fe09b0, event_class=1, event_subtype=0)
at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_audit.cc:207
207 audit_handler_t *handlers= audit_handlers + event_class;
(gdb) bt
#0 mysql_audit_notify (thd=0x16fe09b0, event_class=1, event_subtype=0) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_audit.cc:207
#1 0x00000000006943d6 in thd_prepare_connection (thd=0x16fe09b0) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_connect.cc:715
#2 0x0000000000694c61 in do_handle_one_connection (thd_arg=0x16fe09b0) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_connect.cc:780
#3 0x0000000000694d3b in handle_one_connection (arg=0x16fe09b0) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_connect.cc:706
#4 0x00000034f7e064a7 in start_thread () from /lib64/libpthread.so.0
#5 0x00000034f72d3c2d in clone () from /lib64/libc.so.6
(gdb)
可见,在准备创建客户端连接时也会调用audit,在gdb过程中,发现该函数被调用了很多次:
Breakpoint 1, mysql_audit_notify (thd=0x1706c790, event_class=1, event_subtype=0)
Breakpoint 1, mysql_audit_notify (thd=0x16fe09b0, event_class=0, event_subtype=3)
查了一下notify函数的记录,写入了如下操作:
select @@version_comment limit 1;
select @@version_comment limit 1;
mysql > use yinfeng
SELECT DATABASE();
SELECT DATABASE();
看起来,似乎每次操作之前都会调用两次mysql_audit_notify函数,即使执行一次错误的操作(比如向一个不存在的表插入数据)
————————————————————————————————————————————————
但当执行select、create table之类的命令时,则是先完成命令,然后才会跳到断点
#0 mysql_audit_notify (thd=0x1706c790, event_class=0, event_subtype=2) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_audit.cc:207
#1 0x00000000005c7f84 in mysql_audit_general (thd=0x1706c790, event_subtype=2, error_code=0, msg=0x0)
at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_audit.h:131
#2 0x00000000005c9d22 in dispatch_command (command=COM_QUERY, thd=0x1706c790, packet=0x1706f601 "", packet_length=27)
at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_parse.cc:1398
#3 0x00000000005ca0f8 in do_command (thd=0x1706c790) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_parse.cc:771
#4 0x0000000000694c7e in do_handle_one_connection (thd_arg=0x1706c790) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_connect.cc:787
#5 0x0000000000694d3b in handle_one_connection (arg=0x1706c790) at /home/yinfeng.zwx/mysql-5.5.14/sql/sql_connect.cc:706
#6 0x00000034f7e064a7 in start_thread () from /lib64/libpthread.so.0
#7 0x00000034f72d3c2d in clone () from /lib64/libc.so.6
看起来dispatch_command函数似乎是audit的入口,简要的归纳一下流程:
1.do_handle_one_connection()
2. do_command
3.dispatch_command(sql_parse.cc)
1397 if (!thd->is_error() && !thd->killed_errno())
1398 mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0);
1399
1400 mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
1401 thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() : 0,
1402 command_name[command].str);
这里会调用两次mysql_audit_general函数,分别对应不同的事件类型
4.mysql_audit_general() (sql_audit.h)
100 static inline
101 void mysql_audit_general(THD *thd, uint event_subtype,
102 int error_code, const char *msg)
103 {
104 #ifndef EMBEDDED_LIBRARY
105 if (mysql_global_audit_mask[0] & MYSQL_AUDIT_GENERAL_CLASSMASK)
106 {
107 time_t time= my_time(0);
108 uint msglen= msg ? strlen(msg) : 0;
109 const char *user;
110 uint userlen;
111 char user_buff[MAX_USER_HOST_SIZE];
112 CSET_STRING query;
113 ha_rows rows;
114
115 if (thd)
116 {
117 query= thd->query_string;
118 user= user_buff;
119 userlen= make_user_name(thd, user_buff);
120 rows= thd->warning_info->current_row_for_warning();
121 }
122 else
123 {
124 user= 0;
125 userlen= 0;
126 rows= 0;
127 }
128
129 mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, event_subtype,
130 error_code, time, user, userlen, msg, msglen,
131 query.str(), query.length(), query.charset(), rows);
132 }
133 #endif
134 }
audit_handler_t *handlers= audit_handlers + event_class;
这里,以event_class作为偏移量,来调用相应的函数指针:
audit_handlers作为全局变量,定义:
114 static audit_handler_t audit_handlers[] =
115 {
116 general_class_handler, connection_class_handler
117 };
event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &event);
5.2 connection_class_handler()event_class_dispatch(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event);
可以看到上述两个函数都会调用event_class_dispatch,该函数的参数形式与自定义的插件函数参数完全一致,
深入代码可以发现,在创建连接的时候调用了上述两个函数都被调用了,其中conneciton_class_handler()在函数thd_prepare_connection中被调用:
710 bool thd_prepare_connection(THD *thd)
711 {
712 bool rc;
713 lex_start(thd);
714 rc= login_connection(thd);
715 MYSQL_AUDIT_NOTIFY_CONNECTION_CONNECT(thd);
…… ……
…
6.event_class_dispatch() (sql_audit.cc:466-491)
466 static void event_class_dispatch(THD *thd, unsigned int event_class,
467 const void *event)
468 {
469 struct st_mysql_event_generic event_generic;
470 event_generic.event_class= event_class;
471 event_generic.event= event;
472 /*
473 Check if we are doing a slow global dispatch. This event occurs when
474 thd == NULL as it is not associated with any particular thread.
475 */
476 if (unlikely(!thd))
477 {
478 plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic);
479 }
480 else
481 {
482 plugin_ref *plugins, *plugins_last;
483
484 /* Use the cached set of audit plugins */
485 plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
486 plugins_last= plugins + thd->audit_class_plugins.elements;
487
488 for (; plugins < plugins_last; plugins++)
489 plugins_dispatch(thd, *plugins, &event_generic);
490 }
491 }
7. plugins_dispatch()
439 static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg)
440 {
441 const struct st_mysql_event_generic *event_generic=
442 (const struct st_mysql_event_generic *) arg;
443 unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
444 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
445
446 set_audit_mask(event_class_mask, event_generic->event_class);
447
448 /* Check to see if the plugin is interested in this event */
449 if (check_audit_mask(data->class_mask, event_class_mask))
450 return 0;
451
452 /* Actually notify the plugin */
453 data->event_notify(thd, event_generic->event_class, event_generic->event);
454
455 return 0;
456 }
最终,我们到达了实际调用插件函数,使用函数指针来实现:
data->event_notify(thd, event_generic->event_class, event_generic->event);
————————————————————————————————————————————————————————————
下一步:
尝试对该audit审计插件的执行流程进行修改,来满足一些有趣的需求