萬盛學電腦網

 萬盛學電腦網 >> 數據庫 >> mysql教程 >> 修復 disable myisam 插件的 crash 問題

修復 disable myisam 插件的 crash 問題

本文章為各位介紹修復 disable myisam 插件的 crash 問題,希望此例子能夠為各位帶來幫助。

之前寫了一個 禁止創建 MyISAM 表的插件(blog),有報 issue,在 MySQL 重啟後 uninstall plugin 會引發 crash。

測試了一下果然可以復現。掛上 gdb 後,backstace 如下:

(gdb) bt
#0  0x00000000006f7658 in column_bitmaps_set (write_set_arg=0x189ad98, read_set_arg=0x189ad98, this=0x7fc3a800fa60)
    at /root/mysql-5.6.24-tp/sql/table.h:1228
#1  use_all_columns (this=0x7fc3a800fa60) at /root/mysql-5.6.24-tp/sql/table.h:1238
#2  mysql_uninstall_plugin (thd=thd@entry=0x1d1b030, name=0x1d1d858) at /root/mysql-5.6.24-tp/sql/sql_plugin.cc:2077
#3  0x00000000006e8daf in mysql_execute_command (thd=thd@entry=0x1d1b030) at /root/mysql-5.6.24-tp/sql/sql_parse.cc:4910
#4  0x00000000006ed9d8 in mysql_parse (thd=thd@entry=0x1d1b030, rawbuf=, length=,
    parser_state=parser_state@entry=0x7fc3c61b02f0) at /root/mysql-5.6.24-tp/sql/sql_parse.cc:6391
#5  0x00000000006ef1cd in dispatch_command (command=COM_QUERY, thd=0x1d1b030, packet=, packet_length=)
    at /root/mysql-5.6.24-tp/sql/sql_parse.cc:1340
#6  0x00000000006f0f24 in do_command (thd=) at /root/mysql-5.6.24-tp/sql/sql_parse.cc:1037
#7  0x00000000006bd662 in do_handle_one_connection (thd_arg=thd_arg@entry=0x1d1b030) at /root/mysql-5.6.24-tp/sql/sql_connect.cc:982
#8  0x00000000006bd710 in handle_one_connection (arg=arg@entry=0x1d1b030) at /root/mysql-5.6.24-tp/sql/sql_connect.cc:898
#9  0x000000000095dea3 in pfs_spawn_thread (arg=0x1dbb410) at /root/mysql-5.6.24-tp/storage/perfschema/pfs.cc:1860
#10 0x00007fc3f7258182 in start_thread (arg=0x7fc3c61b1700) at pthread_create.c:312
#11 0x00007fc3f676530d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111
進一步追蹤這個 column_bitmaps_set 的調用

  inline void column_bitmaps_set(MY_BITMAP *read_set_arg,
                                 MY_BITMAP *write_set_arg)
  {
    read_set= read_set_arg;
    write_set= write_set_arg;
    if (file && created)
      file->column_bitmaps_signal();
  }
發現是在 file->column_bitmaps_signal 這一步出錯,反匯編以後,也確實是在 callq 指令處。注意到反匯編後,gdb 在 callq 指令所調用函數的名字沒有能顯示出來,說明這個地址很可能已經無效。這裡 file 成員變量即是插件生成的替換了 create 操作的 ha_mysiam 的 wrapper。打印其內容發現 _vptr.handler = 0x7fc3d44768d0 ,這裡也未能指出指向的類名。

閱讀了 sql_handler.cc 中 mysql_uninstall_plugin 的代碼後,明白了原因。卸載插件會刪除 mysql.plugin 表中的相應記錄。在 mysql_uninstall_plugin 中,先打開了 mysql.plugin 表,然後卸載插件,然後刪除相應記錄。問題就出在 plugin 表也是一個 MyISAM 表。如果打開的時候插件是啟用的,那麼其對應的 handler 是插件中的 wrapper。而插件卸載以後,對應地址的代碼已經不復存在,因此就造成了 crash。那麼既然是這個原因,又想到了,如果在插件加載期間打開過表,卸載插件後訪問這些表應該也會出錯。試了一下果然如此,一陣冷汗。

接著就是考慮如何解決這個問題。既然插件卸載後無法內存,那就需要在卸載時將原有的指向 wrapper 的 handler 都換回原有的 ha_myisam。handler 本身的內存是由 mysql 管理的,並不會因為插件卸載而釋放。所以可以修改。wrapper 是 ha_myisam 的派生類,而且沒有增加成員,因此內存布局和 ha_myisam 是完全相同的。唯一差異就在虛函數表。C++ 似乎沒有這種強制轉換到父類的語法。於是就考慮直接替換虛函數表指針。而 C++ 虛函數指針中總是對象最前面。於是直接建一個真的 ha_myisam 對象,然後把它的虛函數指針復制回去。試下來果然可以。接著就是在 wrapper 的構造和析構函數中維持一個當前現有 handler 的集合,用於在卸載時處理即可

copyright © 萬盛學電腦網 all rights reserved