Evinceの実行ファイルをいじってファイル履歴の数を増やす
仕事でけっこうな数のPDFファイルを開く.諸事情でパスが深くて場所も散らばってるので,がんばって一度開いた履歴はなるべく長く取っておいて省力のために活用したい.ところが現状では直近5個しかメニューで選べないので,これを20個程度に増やしたい.
とりあえずGitでソースを取ってくる.遅い...
- git://git.gnome.org/evince
今使ってるのは,この通り Evince 3.4.0 である.
$ dpkg -l evince 要望=(U)不明/(I)インストール/(R)削除/(P)完全削除/(H)維持 | 状態=(N)無/(I)インストール済/(C)設定/(U)展開/(F)設定失敗/(H)半インストール/(W)トリガ待ち/(T)トリガ保留 |/ エラー?=(空欄)無/(R)要再インストール (状態,エラーの大文字=異常) ||/ 名前 バージョン 説明 +++-===================-===================-====================================================== ii evince 3.4.0-0ubuntu1.7 Document (PostScript, PDF) viewer $ lsb_release -a LSB Version: core-2.0-amd64:(略) Distributor ID: Ubuntu Description: Ubuntu 12.04.4 LTS Release: 12.04 Codename: precise $ uname -a Linux kaidev01 3.2.0-67-generic #101-Ubuntu SMP Tue Jul 15 17:46:11 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
2014年基準では結構古いが... 仕方ない.当該バージョンをチェックアウト.
$ cd evince-HEAD $ git co 3.4.0 Note: checking out '3.4.0'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at 1267587... release: 3.4.0
ここでしばらく関連するソースコードを探すために試行錯誤grep*1.最初 "history" で調べたが,どうもページジャンプの履歴関連のコードがヒットするばかりだ.答えは "recent" の名前を持つ変数・関数群だった.
$ ack --cc -i recent shell/ev-window.c 72:#include "ev-open-recent-action.h" 167: GtkRecentManager *recent_manager; 168: GtkActionGroup *recent_action_group; 169: guint recent_ui_id; 267:#define MAX_RECENT_ITEM_LEN (40) 313:static void ev_window_add_recent (EvWindow *window, 1642: ev_window_add_recent (ev_window, ev_window->priv->uri); 2489:ev_window_cmd_recent_file_activate (GtkAction *action, 2492: GtkRecentInfo *info; 2495: info = g_object_get_data (G_OBJECT (action), "gtk-recent-info"); 2498: uri = gtk_recent_info_get_uri (info); 2506:ev_window_open_recent_action_item_activated (EvOpenRecentAction *action, 2516:ev_window_add_recent (EvWindow *window, const char *filename) 2518: gtk_recent_manager_add_item (window->priv->recent_manager, filename); 2522:compare_recent_items (GtkRecentInfo *a, GtkRecentInfo *b) 2527: has_ev_a = gtk_recent_info_has_application (a, evince); 2528: has_ev_b = gtk_recent_info_has_application (b, evince); 2533: time_a = gtk_recent_info_get_modified (a); 2534: time_b = gtk_recent_info_get_modified (b); (略)
ここで MAX_RECENT_ITEM_LEN
というCPP定数を見つけて「これか?」と一瞬色めき立ったが,どうも表示上の文字数の上限に過ぎないようだ.もうちょっと見ていくとGTKの提供する GtkRecentManager
というデータ構造を gtk_recent_...
という名前のAPIで操作するらしいことが分かった.
$ ack --cc gtk_recent shell/ev-window.c 2498: uri = gtk_recent_info_get_uri (info); 2518: gtk_recent_manager_add_item (window->priv->recent_manager, filename); 2527: has_ev_a = gtk_recent_info_has_application (a, evince); 2528: has_ev_b = gtk_recent_info_has_application (b, evince); 2533: time_a = gtk_recent_info_get_modified (a); 2534: time_b = gtk_recent_info_get_modified (b); 2632: items = gtk_recent_manager_get_items (ev_window->priv->recent_manager); 2646: if (!gtk_recent_info_has_application (info, evince) || 2647: (gtk_recent_info_is_local (info) && !gtk_recent_info_exists (info))) 2652: n_items + 1, gtk_recent_info_get_display_name (info)); 2654: mime_type = gtk_recent_info_get_mime_type (info); 2670: gtk_recent_info_ref (info), 2671: (GDestroyNotify) gtk_recent_info_unref); 2697: g_list_foreach (items, (GFunc) gtk_recent_info_unref, NULL); 7263: ev_window->priv->recent_manager = gtk_recent_manager_get_default (); shell/ev-open-recent-action.c 46: uri = gtk_recent_chooser_get_current_uri (chooser); 58: toolbar_recent_menu = gtk_recent_chooser_menu_new_for_manager (gtk_recent_manager_get_default ()); 59: gtk_recent_chooser_set_local_only (GTK_RECENT_CHOOSER (toolbar_recent_menu), FALSE); 60: gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (toolbar_recent_menu), GTK_RECENT_SORT_MRU); 61: gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (toolbar_recent_menu), 5); 66: filter = gtk_recent_filter_new (); 67: gtk_recent_filter_add_application (filter, g_get_application_name ()); 68: gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (toolbar_recent_menu), filter);
ミツケター
61: gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (toolbar_recent_menu), 5);
薄々予感してたが長さ決め打ちか... (確かに5個が上限となってる.)これはソースを書き換えて再コンパイルしないといけない感じ.ところが実際にやったことある人は分かると思うがGUIアプリ,特にEvinceのようなGNOMEアプリは依存性とかビルドオプションとか多くて色々めんどい.もちろん,そもそもUbuntuのパッケージとして入れたのだから .deb とかが手に入るはずで,apt-get source
から始まる手順に従えば再現性あるビルドができるはずだが... 色々めんどくてやだ.
ということでバイナリを書き換えることにする.
$ mkcd /tmp/hoge $ cp =evince . $ objdump -d evince LL
ここで mkcd
は mkdir -p
後に cd
する俺シェル関数,=cmd
はZsh標準の `which cmd`
の略記,LL
はZshの「グローバル」エイリアスとして "| less
" に展開されるよう設定してある.
検索すると一か所でしか使われてない(ソースで検索しても同じかもね).
2a19b: be 05 00 00 00 mov $0x5,%esi 2a1a0: 48 89 c7 mov %rax,%rdi 2a1a3: e8 38 13 ff ff callq 1b4e0 <gtk_recent_chooser_set_limit@plt>
うろ覚えのAMD64呼び出し規約*2でも %rdi
, %rsi
, ... に第1, 第2, ... 引数が詰まるはずなんで,
2a19b: be 05 00 00 00 mov $0x5,%esi
さすがにx86 MOV
のオペコードまでは覚えてないが((意味的なオペコードのベース値(と呼んでよかろう)とレジスタ番号を足し算した結果として上記の 0xbe
ができる.覚えられる訳がない...))この $0x5
を書き換えればよいと想像が付く.怖いので&不必要なので編集は1バイトの範囲内 (<= 255) に止め,切りよく 0x20
にしよう.
残念ながら sed(1)
はバイナリファイルに対しまともに動かないことが知られてるので,手順は
xxd(1)
でhexダンプのテキストを保存し,- それをエディタで編集し,
xxd -r
で実行ファイルに戻す
Vimだと直接あれこれできるが((:help using-xxd
辺りに解説されてるようだ.)),大差ないので地道に...
$ xxd evince > evince.xxd $ vim evince.xxd
アドレス "2a190:
" で /
検索すると次の行が見つかる(xxd(1)
はデフォルトで1行に16バイトを表示,つまりアドレス表記は最終桁の切り捨て)
10778 002a190: 4889 ee48 89df e845 17ff ffbe 0500 0000 H..H...E........
これをさくっと書き換え,
10778 002a190: 4889 ee48 89df e845 17ff ffbe 2000 0000 H..H...E........
逆変換(このとき,当然と言えば当然だが,ASCIIダンプ部は無視されるので律儀にいじらなくてもよい)
$ xxd -r evince.xxd > evince
さて実行... するとクラッシュした.あれー?? どこか別の場所を一貫して編集しないといけないのか.GUI関連と思しき shell/
ディレクトリで,一単語としての "5" を検索.ack(1)
はPerlベースなので単語境界の表現は \b
を使う.
$ ack --cc '\b5\b' shell/ shell/ev-properties-dialog.c 64: gtk_container_set_border_width (GTK_CONTAINER (properties), 5); 73: gtk_container_set_border_width (GTK_CONTAINER (properties->notebook), 5); shell/ev-sidebar-thumbnails.c 127: *width = MAX ((gint)(w * scale + 0.5), 1); 128: *height = MAX ((gint)(h * scale + 0.5), 1); shell/ev-window.c 113: EV_CHROME_SIDEBAR = 1 << 5, 1275: request_width = (gint)(width_ratio * document_width + 0.5); 1276: request_height = (gint)(height_ratio * document_height + 0.5); 2693: if (++n_items == 5) 4409: gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)), 5); 4417: gtk_container_set_border_width (GTK_CONTAINER (editor), 5); 4418: gtk_box_set_spacing (GTK_BOX (EGG_TOOLBAR_EDITOR (editor)), 5); shell/ev-message-area.c 108: gtk_misc_set_alignment (GTK_MISC (area->priv->label), 0.0, 0.5); 117: gtk_misc_set_alignment (GTK_MISC (area->priv->secondary_label), 0.0, 0.5); 122: gtk_misc_set_alignment (GTK_MISC (area->priv->image), 0.5, 0.0); shell/ev-progress-message-area.c 94: gtk_misc_set_alignment (GTK_MISC (area->priv->label), 0.0, 0.5); shell/eggfindbar.c 307: alignment = gtk_alignment_new (0.0, 0.5, 1.0, 0.0); 349: gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5); 656: * be something like "5 results on this page" or "No results" shell/ev-properties-license.c 110: gtk_misc_set_alignment (GTK_MISC (title), 0.0, 0.5); 118: alignment = gtk_alignment_new (0.5, 0.5, 1., 1.); shell/ev-file-monitor.c 113: g_timeout_add_seconds (5, (GSourceFunc)timeout_cb, ev_monitor); shell/ev-password-view.c 122: align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); 256: gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); 257: gtk_box_set_spacing (GTK_BOX (content_area), 2); /* 2 * 5 + 2 = 12 */ 258: gtk_container_set_border_width (GTK_CONTAINER (action_area), 5); 281: gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); 288: gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0); 297: gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); 333: gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); shell/ev-annotation-properties-dialog.c 95: gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5); 96: gtk_grid_attach (GTK_GRID (grid), label, 0, 5, 1, 1); 111: gtk_grid_attach (GTK_GRID (grid), dialog->icon, 1, 5, 1, 1); 136: gtk_container_set_border_width (GTK_CONTAINER (annot_dialog), 5); 155: gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5); 166: gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5); 176: gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5); 181: 0, 100, 5); 208: gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5); shell/ev-utils.c 95: x_offset = (blur_radius * 4) / 5; 98: y_offset = (blur_radius * 4) / 5; shell/ev-open-recent-action.c 61: gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (toolbar_recent_menu), 5);
数が大したことないので,がんばって目で探すと,
shell/ev-window.c ... 2693: if (++n_items == 5)
これだろ.周辺コードはこんな感じ:
2605 static void 2606 ev_window_setup_recent (EvWindow *ev_window) 2607 { 2608 GList *items, *l; 2609 guint n_items = 0; ... 2632 items = gtk_recent_manager_get_items (ev_window->priv->recent_manager); 2633 items = g_list_sort (items, (GCompareFunc) compare_recent_items); 2634 2635 for (l = items; l && l->data; l = g_list_next (l)) { ... 2646 if (!gtk_recent_info_has_application (info, evince) || 2647 (gtk_recent_info_is_local (info) && !gtk_recent_info_exists (info))) 2648 continue; ... 2681 gtk_ui_manager_add_ui (ev_window->priv->ui_manager, 2682 ev_window->priv->recent_ui_id, 2683 "/MainMenu/FileMenu/RecentFilesMenu", 2684 label, 2685 action_name, 2686 GTK_UI_MANAGER_MENUITEM, 2687 FALSE); 2688 g_free (action_name); 2689 g_free (label); 2690 if (icon != NULL) 2691 g_object_unref (icon); 2692 2693 if (++n_items == 5) 2694 break; 2695 } ... 2699 }
なるほど,実際にメニューにファイル履歴の項目を並べるコードか.どうやら GtkRecentManager
は私がGNOME系アプリで開いたファイル履歴を全て返すので,Evince内で「Evinceで開いたもの」をフィルタしており,それがいつ5個に達するかは最初から予測はできないからループ内で数を数えているみたい.
ローカル変数であるループ・カウンタの上限はAPI呼び出しの引数よりやや探し辛いが,そのちょっと前にある特徴的な gtk_ui_manager_add_ui()
の呼び出しを逆アセンブリで検索.
$ objdump -d evince | ack -C10 'callq .*gtk_ui_manager_add_ui@plt' 2d512: 49 89 c6 mov %rax,%r14 2d515: e8 06 f8 fe ff callq 1cd20 <gtk_action_get_label@plt> 2d51a: 48 8b 53 38 mov 0x38(%rbx),%rdx 2d51e: 41 b9 20 00 00 00 mov $0x20,%r9d 2d524: 4d 89 f0 mov %r14,%r8 2d527: 48 89 c1 mov %rax,%rcx 2d52a: 8b b2 10 01 00 00 mov 0x110(%rdx),%esi 2d530: 48 8b ba 18 01 00 00 mov 0x118(%rdx),%rdi 2d537: 48 8d 15 0a 89 02 00 lea 0x2890a(%rip),%rdx # 55e48 <_IO_stdin_used+0x2f08> 2d53e: c7 04 24 00 00 00 00 movl $0x0,(%rsp) 2d545: e8 86 fa fe ff callq 1cfd0 <gtk_ui_manager_add_ui@plt> 2d54a: 48 89 ef mov %rbp,%rdi 2d54d: e8 4e 10 ff ff callq 1e5a0 <g_object_unref@plt> 2d552: 4d 8b 64 24 08 mov 0x8(%r12),%r12 2d557: 4d 85 e4 test %r12,%r12 2d55a: 0f 85 60 ff ff ff jne 2d4c0 <ev_gui_menu_position_tree_selection+0x2b70> 2d560: 48 83 c4 10 add $0x10,%rsp 2d564: 4c 89 ef mov %r13,%rdi 2d567: 5b pop %rbx 2d568: 5d pop %rbp 2d569: 41 5c pop %r12 -- 2df52: e8 49 06 ff ff callq 1e5a0 <g_object_unref@plt> 2df57: 48 8b 54 24 20 mov 0x20(%rsp),%rdx 2df5c: 4c 8b 44 24 38 mov 0x38(%rsp),%r8 2df61: 41 b9 20 00 00 00 mov $0x20,%r9d 2df67: 4c 89 f1 mov %r14,%rcx 2df6a: 48 8b 42 38 mov 0x38(%rdx),%rax 2df6e: 48 8d 15 2b 7f 02 00 lea 0x27f2b(%rip),%rdx # 55ea0 <_IO_stdin_used+0x2f60> 2df75: 48 8b b8 18 01 00 00 mov 0x118(%rax),%rdi 2df7c: 8b b0 00 01 00 00 mov 0x100(%rax),%esi 2df82: c7 04 24 00 00 00 00 movl $0x0,(%rsp) 2df89: e8 42 f0 fe ff callq 1cfd0 <gtk_ui_manager_add_ui@plt> 2df8e: 48 8b 7c 24 38 mov 0x38(%rsp),%rdi 2df93: e8 88 f8 fe ff callq 1d820 <g_free@plt> 2df98: 4c 89 f7 mov %r14,%rdi 2df9b: e8 80 f8 fe ff callq 1d820 <g_free@plt> 2dfa0: 4d 85 e4 test %r12,%r12 2dfa3: 74 08 je 2dfad <ev_gui_menu_position_tree_selection+0x365d> 2dfa5: 4c 89 e7 mov %r12,%rdi 2dfa8: e8 f3 05 ff ff callq 1e5a0 <g_object_unref@plt> 2dfad: 83 7c 24 34 05 cmpl $0x5,0x34(%rsp) 2dfb2: 0f 85 80 fd ff ff jne 2dd38 <ev_gui_menu_position_tree_selection+0x33e8> -- 45d18: 74 37 je 45d51 <ev_gui_menu_position_tree_selection+0x1b401> 45d1a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 45d20: 8b 73 30 mov 0x30(%rbx),%esi 45d23: 4c 8d 84 24 80 00 00 lea 0x80(%rsp),%r8 45d2a: 00 45d2b: c7 04 24 00 00 00 00 movl $0x0,(%rsp) 45d32: 49 8b 14 24 mov (%r12),%rdx 45d36: 48 8b 3b mov (%rbx),%rdi 45d39: 41 b9 20 00 00 00 mov $0x20,%r9d 45d3f: 4c 89 c1 mov %r8,%rcx 45d42: e8 89 72 fd ff callq 1cfd0 <gtk_ui_manager_add_ui@plt> 45d47: 4d 8b 64 24 08 mov 0x8(%r12),%r12 45d4c: 4d 85 e4 test %r12,%r12 45d4f: 75 cf jne 45d20 <ev_gui_menu_position_tree_selection+0x1b3d0> 45d51: 4c 89 f7 mov %r14,%rdi 45d54: e8 c7 7a fd ff callq 1d820 <g_free@plt> 45d59: 48 83 44 24 30 01 addq $0x1,0x30(%rsp) 45d5f: 8b 44 24 30 mov 0x30(%rsp),%eax 45d63: 39 44 24 54 cmp %eax,0x54(%rsp) 45d67: 0f 8f 93 fc ff ff jg 45a00 <ev_gui_menu_position_tree_selection+0x1b0b0> 45d6d: 8b 74 24 54 mov 0x54(%rsp),%esi
うーん3か所か... さらに特徴的なAPI呼び出しに注目, g_free()
を2個と g_object_unref()
を1個呼び出してるのは2個目のみで,それだと同定できた.よく見ると
2dfad: 83 7c 24 34 05 cmpl $0x5,0x34(%rsp) 2dfb2: 0f 85 80 fd ff ff jne 2dd38 <ev_gui_menu_position_tree_selection+0x33e8>
お,これは
2693 if (++n_items == 5) 2694 break;
に対応するだろう.この $0x5
を書き換えればよさそう.Hexダンプのアドレス "2dfa0:
" を探すと,当該インストラクションは2行に泣き別れになっており,やや見辛いが,
11771 002dfa0: 4d85 e474 084c 89e7 e8f3 05ff ff83 7c24 M..t.L........|$ 11772 002dfb0: 3405 0f85 80fd ffff 0f1f 8400 0000 0000 4...............
やることは同じ.
11771 002dfa0: 4d85 e474 084c 89e7 e8f3 05ff ff83 7c24 M..t.L........|$ 11772 002dfb0: 3420 0f85 80fd ffff 0f1f 8400 0000 0000 4...............
改めて逆変換.
$ xxd -r evince.xxd > evince
これで動きました.まぁソースが読めるんだから簡単だわな... 初級編ということで.
*1:正確には,最近はPerlで実装されたack ( http://beyondgrep.com/ ) を使うことが多い.ギガバイト級を舐めるんでなければフツーに便利.
*2:最近,ほんとStackOverflowには助かってるわ... http://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-x86-64