LibreOfficeCalcに関数候補表示機能を付けるまで 第2回 デバッガでメインループを探す

#前回のまとめ
LibreOfficeをgud-gdbで追えるようになった。バンザイ。
#デバッガで追えるところまで追ってみる
##とりあえずbt
前回やったようにLibreOfficeを起動してみたけど、恐ろしいくらい起動が遅い。2分くらいかかった。たぶんデバッグシンボルを付けたから遅いのだろう。毎回この時間待つのはかなり苦痛なのだが、まあ仕方ない…。
Emacs上でC-c C-cを押すと、実行を中断することができる。ここでbtを押すとどの関数が呼ばれているかわかる。
目的の関数とかを探すためにgrepで検索したいのだが、全ソースを検索していたら時間がいくらあっても足りない。calcに関係ありそうなところだけに絞って検索したい！
というわけで、btで見つけたファイルを手探ってみよう。

```text:gud-gdb
(gdb) bt
#0  0x00007ffff755c12d in poll () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007fffe30d4fe4 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x00007fffe30d50ec in g_main_context_iteration ()
   from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#3  0x00007fffe3b6a3e3 in GtkData::Yield (this=0x6284e0, bWait=true, 
    bHandleAllCurrentEvents=false)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/unx/gtk/app/gtkdata.cxx:575
#4  0x00007fffe3b6e34c in GtkInstance::Yield (this=0x628430, bWait=true, 
    bHandleAllCurrentEvents=false)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/unx/gtk/app/gtkinst.cxx:406
#5  0x00007ffff12a5208 in ImplYield (i_bWait=true, i_bAllEvents=false)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/source/app/svapp.cxx:360
#6  0x00007ffff12a1c69 in Application::Yield ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/source/app/svapp.cxx:392
#7  0x00007ffff12a1c17 in Application::Execute ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/source/app/svapp.cxx:341
#8  0x00007ffff784f29f in desktop::Desktop::Main (this=0x7fffffffe170)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/desktop/source/app/app.cxx:1638
#9  0x00007ffff12a92fa in ImplSVMain ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/source/app/svmain.cxx:163
#10 0x00007ffff12a93f0 in SVMain ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/vcl/source/app/svmain.cxx:196
#11 0x00007ffff7892a97 in soffice_main ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/desktop/source/app/sofficemain.cxx:101
#12 0x0000000000400964 in sal_main ()
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/desktop/source/app/main.c:48
#13 0x000000000040094a in main (argc=2, argv=0x7fffffffe418)
    at /home/denjo/[pass]/team_babumi_project/libreoffice-4.4.5.2/desktop/source/app/main.c:47
```
ためしに、`ImplYield ()`を見てみる。

```cpp:libreoffice-4.4.5.2/vcl/source/app/svapp.cxx
inline void ImplYield( bool i_bWait, bool i_bAllEvents )
{
    ImplSVData* pSVData = ImplGetSVData();

    // run timers that have timed out
    if ( !pSVData->mbNoCallTimer )
        while ( pSVData->mbNotAllTimerCalled )
            Timer::ImplTimerCallbackProc();

    pSVData->maAppData.mnDispatchLevel++;
    // do not wait for events if application was already quit; in that
    // case only dispatch events already available
    // do not wait for events either if the app decided that it is too busy for timers
    // (feature added for the slideshow)
    pSVData->mpDefInst->Yield( i_bWait && !pSVData->maAppData.mbAppQuit && !pSVData->maAppData.mbNoYield, i_bAllEvents );
    pSVData->maAppData.mnDispatchLevel--;

    DBG_TESTSOLARMUTEX(); // must be locked on return from Yield

    // flush lazy deleted objects
    if( pSVData->maAppData.mnDispatchLevel == 0 )
        vcl::LazyDelete::flush();

    // the system timer events will not necessarily come in non waiting mode
    // e.g. on OS X; need to trigger timer checks manually
    if( pSVData->maAppData.mbNoYield && !pSVData->mbNoCallTimer )
    {
        do
        {
            Timer::ImplTimerCallbackProc();
        }
        while( pSVData->mbNotAllTimerCalled );
    }

    // call post yield listeners
    if( pSVData->maAppData.mpPostYieldListeners )
        pSVData->maAppData.mpPostYieldListeners->callListeners( NULL );
}
```

##find-tag用のタグファイルを作成
`ImplSVData`クラスを探してみる…とその前に、先に`find-tag`を使えるようにする。

```sh:shell
$ find . -regex  ".*\.+\(c\|cxx\|h\|hxx\)" -print | etags -
```

##タグ検索でどんどん奥へ
```cpp:libreoffice-4.4.5.2/vcl/inc/svdata.hxx
struct ImplSVData
{
    SalData*                mpSalData;
    SalInstance*            mpDefInst;          // Default SalInstance
    Application*            mpApp;              // pApp
    WorkWindow*             mpDefaultWin;       // Default-Window
    bool                    mbDeInit;             // Is VCL deinitializing
    sal_uLong                   mnThreadCount;      // is VCL MultiThread enabled
    ImplConfigData*         mpFirstConfigData;  // Zeiger auf ersten Config-Block
    ImplTimerData*          mpFirstTimerData;   // list of all running timers
    SalTimer*               mpSalTimer;         // interface to sal event loop/timers
    SalI18NImeStatus*       mpImeStatus;        // interface to ime status window
    SalSystem*              mpSalSystem;        // SalSystem interface
    ResMgr*                 mpResMgr;           // SV-Resource-Manager
    sal_uLong                   mnTimerPeriod;      // current timer period
    sal_uLong                   mnTimerUpdate;      // TimerCallbackProcs on stack
    bool                    mbNotAllTimerCalled;// true: Es muessen noch Timer abgearbeitet werden
    bool                    mbNoCallTimer;      // true: No Timeout calls
    ImplSVAppData           maAppData;          // indepen data for class Application
    ImplSVGDIData           maGDIData;          // indepen data for Output classes
    ImplSVWinData           maWinData;          // indepen data for Windows classes
    ImplSVCtrlData          maCtrlData;         // indepen data for Control classes
    ImplSVHelpData          maHelpData;         // indepen data for Help classes
    ImplSVNWFData           maNWFData;
    UnoWrapperBase*         mpUnoWrapper;
    vcl::Window*                 mpIntroWindow;      // the splash screen
    DockingManager*         mpDockingManager;
    BlendFrameCache*        mpBlendFrameCache;
    bool                mbIsTestTool;

    oslThreadIdentifier                     mnMainThreadId;
    rtl::Reference< vcl::DisplayConnection >            mxDisplayConnection;

    ::com::sun::star::uno::Reference< ::com::sun::star::lang::XComponent > mxAccessBridge;
    ::vcl::SettingsConfigItem*          mpSettingsConfigItem;
    std::list< vcl::DeleteOnDeinitBase* >*   mpDeinitDeleteList;
    boost::unordered_map< int, OUString >*     mpPaperNames;

    Link maDeInitHook;
};
```
`Yield()`関数を持っているmpDefInstメンバは、SalInstanceというクラス？のインスタンスらしい。そこで、こんどはこれを検索してみる。

```cpp:libreoffice-4.4.5.2/vcl/headless/headlessinst.cxx
// This is our main entry point:
SalInstance *CreateSalInstance()
{
    HeadlessSalInstance* pInstance = new HeadlessSalInstance( new SalYieldMutex() );
    new HeadlessSalData( pInstance );
    pInstance->AcquireYieldMutex(1);
    return pInstance;
}
```
「This is our main entry point :」という言葉に感慨を覚えつつ、さらに奥へ。
HeadlessSalInstanceクラスがSalInstanceクラスの実体(?)のようなので、今度はこっちをfind-tag。

```cpp:libreoffice-4.4.5.2/vcl/headless/headlessinst.cxx
class HeadlessSalInstance : public SvpSalInstance
{
public:
    HeadlessSalInstance( SalYieldMutex *pMutex );
    virtual ~HeadlessSalInstance();

    virtual SalSystem* CreateSalSystem();
};
```
え、これだけ？
コンストラクタの実装は以下

```cpp:libreoffice-4.4.5.2/vcl/headless/headlessinst.cxx
HeadlessSalInstance::HeadlessSalInstance( SalYieldMutex *pMutex ) :
    SvpSalInstance( pMutex)
{
}
```
このコロンについて調べたところ（C++全然わからない）、どうやらメンバ変数を初期化する構文らしい。

```cpp:
HogeClass () :
    var (value)
{
    // 内容
}
```

で、`var`に`value`を代入するコンストラクタになる。

##btのデータを元に、上の方へ進んでみる

```cpp:libreoffice-4.4.5.2/vcl/source/app/svmain.cxx
bool InitVCL()
{
    if( pExceptionHandler != NULL )
        return false;

    EmbeddedFontsHelper::clearTemporaryFontFiles();

    if( ! ImplGetSVData() )
        ImplInitSVData();

    if( !ImplGetSVData()->mpApp )
    {
        pOwnSvApp = new DummyApplication();
    }
    InitSalMain();

    ImplSVData* pSVData = ImplGetSVData();

    // remember Main-Thread-Id
    pSVData->mnMainThreadId = ::osl::Thread::getCurrentIdentifier();

    // Initialize Sal
    pSVData->mpDefInst = CreateSalInstance();
    if ( !pSVData->mpDefInst )
        return false;

    // Desktop Environment context (to be able to get value of "system.desktop-environment" as soon as possible)
    com::sun::star::uno::setCurrentContext(
        new DesktopEnvironmentContext( com::sun::star::uno::getCurrentContext() ) );

    // Initialize application instance (should be done after initialization of VCL SAL part)
    if( pSVData->mpApp )
        // call init to initialize application class
        // soffice/sfx implementation creates the global service manager
        pSVData->mpApp->Init();

    pSVData->mpDefInst->AfterAppInit();

    // Fetch AppFileName and make it absolute before the workdir changes...
    OUString aExeFileName;
    osl_getExecutableFile( &aExeFileName.pData );

    // convert path to native file format
    OUString aNativeFileName;
    osl::FileBase::getSystemPathFromFileURL( aExeFileName, aNativeFileName );
    pSVData->maAppData.mpAppFileName = new OUString( aNativeFileName );

    // Initialize global data
    pSVData->maGDIData.mpScreenFontList     = new PhysicalFontCollection;
    pSVData->maGDIData.mpScreenFontCache    = new ImplFontCache;
    pSVData->maGDIData.mpGrfConverter       = new GraphicConverter;

    // Set exception handler
    pExceptionHandler = osl_addSignalHandler(VCLExceptionSignal_impl, NULL);

    DBGGUI_INIT_SOLARMUTEXCHECK();

#if OSL_DEBUG_LEVEL > 0
    DebugEventInjector::getCreate();
#endif

    return true;
}
```
これが`CreateSalInstance()`を呼び出してた関数。いかにも初期化しますって顔してて、めちゃくちゃ怪しいけど、どうにも何やってるかよくわかんない…。
`pSVData->mpApp->Init();`を実行すると、新しいスレッドが2つ立ち上がった。その1つ下の行を実行すると、さっき立ち上がったスレッドのうち先に立ち上がったほうが閉じて、2つ新規スレッドが立ち上がる。

```cpp:libreoffice-4.4.5.2/vcl/source/app/svmain.cxx
int ImplSVMain()
{
    // The 'real' SVMain()
    ImplSVData* pSVData = ImplGetSVData();

    DBG_ASSERT( pSVData->mpApp, "no instance of class Application" );

    int nReturn = EXIT_FAILURE;

    bool bInit = InitVCL();

    if( bInit )
    {
        // call application main
        pSVData->maAppData.mbInAppMain = true;
        nReturn = pSVData->mpApp->Main();
        pSVData->maAppData.mbInAppMain = false;
    }

    if( pSVData->mxDisplayConnection.is() )
    {
        pSVData->mxDisplayConnection->terminate();
        pSVData->mxDisplayConnection.clear();
    }

    // This is a hack to work around the problem of the asynchronous nature
    // of bridging accessibility through Java: on shutdown there might still
    // be some events in the AWT EventQueue, which need the SolarMutex which
    // - on the other hand - is destroyed in DeInitVCL(). So empty the queue
    // here ..
    if( pSVData->mxAccessBridge.is() )
    {
      sal_uLong nCount = Application::ReleaseSolarMutex();
      pSVData->mxAccessBridge->dispose();
      Application::AcquireSolarMutex(nCount);
      pSVData->mxAccessBridge.clear();
    }

    DeInitVCL();
    return nReturn;
}
```

こいつがInitVCLを呼び出してたやつ。
`nReturn = pSVData->mpApp->Main();`をnextでとびこえると…あ！　LibreOfficeが開いた！

```cpp:libreoffice-4.4.5.2/desktop/source/app/app.cxx
int Desktop::Main()
{
 // (長いので略)
}
```
結局、ここがメインということらしい。ここまで長かった…。

#次回予告
次回はこの`Main()`関数をいじって、初めて進捗を生んでみる。

#リンク
##LibreOfficeCalcに関数候補表示機能を付けるまで

- [第1回：目的の設定と準備](http://qiita.com/KRiver1/items/2188d71d316a28ad23ac)
- [第2回：デバッガでメインループを探す](http://qiita.com/KRiver1/items/1f93efcf47d2146c78b8)
- [第3回：メインループと全探索](http://qiita.com/KRiver1/items/d7ab9614d5bbd409c541)
- [第4回：フォルダ名でアタリを付ける](http://qiita.com/KRiver1/items/3531679de3c262332de7)
- [第5回：関数候補の集合を返す](http://qiita.com/KRiver1/items/08ef4586866833048805)
- [第6回：関数候補・説明表示機能を実装する](http://qiita.com/KRiver1/items/c33d62f4bcc73e76b359)
- [第7回：LibreOfficeにコミットしてみる](http://qiita.com/KRiver1/items/7aba2a32cc42672c105d)